Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 91 additions & 84 deletions src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { GlobalStore } from "@store";
import { baseChecksMiddleware, DropItemGuard, EntityInteractGuard, EquipItemGuard, MovementGuard, OpenChestGuard, PickUpGuard, ShootGuard, UseItemGuard } from "@middlewares";
import { createPluginProto, extractMethodFromPlugin, extractPropertyFromPlugin } from "@utils";
import type { Entity, GameObject } from "@world";
import { ConflictResolverPlugin } from "@plugins";

export class Game implements IGame {
readonly options: IGameOptions;
Expand Down Expand Up @@ -200,90 +201,7 @@ export class Game implements IGame {
}
}

public constructor(
options?: IInitGameOptions
) {
const manager = new EntityManager([], this)

this.options = {
manager,
map: manager.gameMap,
store: new GlobalStore({ game: this }),
undoManager: new UndoManager({ game: this }),
...options
}
this.connectFactory(FactoryKeys.EFFECTS, new EffectFactory())
this.connectFactory(FactoryKeys.BLUEPRINTS, new BluePrintsFactory({ game: this }))
this.connectFactory(FactoryKeys.QUESTS, new QuestsFactory({ game: this }))
this.connectFactory(FactoryKeys.ITERACTIONS, new IteractionsFactory({ game: this }))
this.connectFactory(FactoryKeys.SOUNDS, new SoundsFactory({ game: this }))

if (!(options?.disableBaseMiddleware)) this.use(baseChecksMiddleware)
if (options?.usingEntityMiddlewares) this.use([DropItemGuard, EntityInteractGuard, EquipItemGuard, MovementGuard, OpenChestGuard, PickUpGuard, UseItemGuard])
if (options?.usingObjectMiddlewares) this.use([ShootGuard])
}

public on<T>(event: keyof typeof GameEvent, cb: EventCallback<T>) {
const events = this.eventListenersMap.get(event) ?? []

events.push(cb)

this.eventListenersMap.set(event, events)

return () => {
const events = this.eventListenersMap.get(event)

if (events) {
const filtrated = events.filter((subscriber) => subscriber !== cb)

if (filtrated.length !== 0) this.eventListenersMap.set(event, filtrated)
else this.eventListenersMap.delete(event)
}
}
}

public processEvent<T>(event: keyof typeof GameEvent, eventData: IEventInfo<T>) {
const subscribers = this.eventListenersMap.get(event)

if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData))
}

public processCustomEvent<T>(event: string, eventData: IEventInfo<T>) {
const subscribers = this.customEventListenersMap.get(event)

if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData))
}

public registerCustomEvent<T>(event: string, cb: CustomEventCallback<T>) {
const events = this.customEventListenersMap.get(event) ?? []

events.push(cb)

this.customEventListenersMap.set(event, events)

return () => {
const events = this.customEventListenersMap.get(event)

if (events) {
const filtrated = events.filter((subscriber) => subscriber !== cb)

if (filtrated.length !== 0) this.customEventListenersMap.set(event, filtrated)
else this.customEventListenersMap.delete(event)
}
}
}

public connectFactory<T = any>(name: string, factory: T) {
this.factories.set(name, factory)

return factory
}

public getFactory<T>(name: string) {
return this.factories.get(name) as T
}

public registerPlugin(plugin: IPlugin) {
private processPluginRegister(plugin: IPlugin) {
const proto = createPluginProto(plugin)

if (proto.events) proto.events.forEach((e: OnEventDecoratorProperties) => this.on(e.event, (options, event, data) => {
Expand Down Expand Up @@ -438,6 +356,95 @@ export class Game implements IGame {
else return false
}

public constructor(
options?: IInitGameOptions
) {
const manager = new EntityManager([], this)

this.options = {
manager,
map: manager.gameMap,
store: new GlobalStore({ game: this }),
undoManager: new UndoManager({ game: this }),
...options
}
this.connectFactory(FactoryKeys.EFFECTS, new EffectFactory())
this.connectFactory(FactoryKeys.BLUEPRINTS, new BluePrintsFactory({ game: this }))
this.connectFactory(FactoryKeys.QUESTS, new QuestsFactory({ game: this }))
this.connectFactory(FactoryKeys.ITERACTIONS, new IteractionsFactory({ game: this }))
this.connectFactory(FactoryKeys.SOUNDS, new SoundsFactory({ game: this }))

if (!(options?.disableBaseMiddleware)) this.use(baseChecksMiddleware)
if (options?.usingEntityMiddlewares) this.use([DropItemGuard, EntityInteractGuard, EquipItemGuard, MovementGuard, OpenChestGuard, PickUpGuard, UseItemGuard])
if (options?.usingObjectMiddlewares) this.use([ShootGuard])
if (!(options?.disableConflictResolver)) this.registerPlugin(new ConflictResolverPlugin())
}

public on<T>(event: keyof typeof GameEvent, cb: EventCallback<T>) {
const events = this.eventListenersMap.get(event) ?? []

events.push(cb)

this.eventListenersMap.set(event, events)

return () => {
const events = this.eventListenersMap.get(event)

if (events) {
const filtrated = events.filter((subscriber) => subscriber !== cb)

if (filtrated.length !== 0) this.eventListenersMap.set(event, filtrated)
else this.eventListenersMap.delete(event)
}
}
}

public processEvent<T>(event: keyof typeof GameEvent, eventData: IEventInfo<T>) {
const subscribers = this.eventListenersMap.get(event)

if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData))
}

public processCustomEvent<T>(event: string, eventData: IEventInfo<T>) {
const subscribers = this.customEventListenersMap.get(event)

if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData))
}

public registerCustomEvent<T>(event: string, cb: CustomEventCallback<T>) {
const events = this.customEventListenersMap.get(event) ?? []

events.push(cb)

this.customEventListenersMap.set(event, events)

return () => {
const events = this.customEventListenersMap.get(event)

if (events) {
const filtrated = events.filter((subscriber) => subscriber !== cb)

if (filtrated.length !== 0) this.customEventListenersMap.set(event, filtrated)
else this.customEventListenersMap.delete(event)
}
}
}

public connectFactory<T = any>(name: string, factory: T) {
this.factories.set(name, factory)

return factory
}

public getFactory<T>(name: string) {
return this.factories.get(name) as T
}

public registerPlugin(plugin: IPlugin | IPlugin[]): boolean {
if (Array.isArray(plugin)) return plugin.map((p: IPlugin) => this.registerPlugin(p)).every((v) => v === true)
else return this.processPluginRegister(plugin)
}

public getPlugin(name: string) {
return this.plugins.get(name)
}
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/core/engine/classes-engine.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export interface IGame {
* @param plugin - Plugin to install
* @returns { boolean } - True if success install, else false
*/
readonly registerPlugin: (plugin: IPlugin) => boolean;
readonly registerPlugin: (plugin: IPlugin | IPlugin[]) => boolean;

/**
* Get plugin by name
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces/core/engine/engine-options.intefaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface IGameOptions {
*/
readonly usingObjectMiddlewares?: boolean;

/**
* Flag to disable built-in ConflictResolverPlugin
*/
readonly disableConflictResolver?: boolean;

/**
* Optional command bus options
*/
Expand Down
27 changes: 27 additions & 0 deletions src/plugins/conflict-resolver.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Game } from "@";
import type { IPlugin } from "@interfaces";
import { CanvasPlugin, ConsolePlugin, GraphicPlugin } from "@plugins";

export class ConflictResolverPlugin implements IPlugin {
public readonly name = ConflictResolverPlugin.name;

public install(game: Game) {
setTimeout(() => {
const plugins = game.getAllPlugins()
const conflicts: string[] = []
const error = (reason: string) => {
throw new Error(reason)
}

plugins.forEach(plugin => {

const condition = (plugin.name === CanvasPlugin.name || plugin.name === ConsolePlugin.name || plugin.name === GraphicPlugin.name)

if (condition && conflicts.length !== 0) error(`[${ConflictResolverPlugin.name}]: Founded error in activating two plugins, game allows 1: ${plugin.name}-${conflicts[0]}`)
if (condition) conflicts.push(plugin.name)
})
}, 1000)

return true
}
}
3 changes: 2 additions & 1 deletion src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from "./network.plgugin.js"
export * from "./canvas.plugin.js"
export * from "./async.plugin.js"
export * from "./console.plugin.js"
export * from "./graphic.plugin.js"
export * from "./graphic.plugin.js"
export * from "./conflict-resolver.plugin.js"
7 changes: 4 additions & 3 deletions test/gametest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import type { CreateChestMetadata, CreateItemMetadata, CreateTowerMetadata, Netw
import { BASE_SEARCH_RADIUS, USE_VALIDATION_EVENT_PREFIX, USE_VISIBILITY_EVENT } from '@const'
import { BluePrintsFactory, EffectFactory, IteractionsFactory, QuestsFactory, SoundsFactory } from "@factories";
import { loggerMiddleware } from "@middlewares";
import { RegenerationPlugin, NetworkPlguin, AsyncPlugin, ConsolePlugin, GraphicPlugin } from "@plugins";
import { RegenerationPlugin, NetworkPlguin, AsyncPlugin, GraphicPlugin } from "@plugins";
import { useVisibility, checkTwoPositions, useValidation } from "@utils";

const [game, manager, map] = createGame({
usingEntityMiddlewares: true,
usingObjectMiddlewares: true
usingObjectMiddlewares: true,
disableConflictResolver: true
})

game.registerPlugin(new RegenerationPlugin(20))
game.registerPlugin([new RegenerationPlugin(20)])
game.registerPlugin(new GraphicPlugin({
appName: "Моя первая игра на stemma",
assets: {
Expand Down
Loading