diff --git a/src/game.ts b/src/game.ts index 1cec7df..623e933 100644 --- a/src/game.ts +++ b/src/game.ts @@ -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; @@ -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(event: keyof typeof GameEvent, cb: EventCallback) { - 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(event: keyof typeof GameEvent, eventData: IEventInfo) { - const subscribers = this.eventListenersMap.get(event) - - if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData)) - } - - public processCustomEvent(event: string, eventData: IEventInfo) { - const subscribers = this.customEventListenersMap.get(event) - - if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData)) - } - - public registerCustomEvent(event: string, cb: CustomEventCallback) { - 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(name: string, factory: T) { - this.factories.set(name, factory) - - return factory - } - - public getFactory(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) => { @@ -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(event: keyof typeof GameEvent, cb: EventCallback) { + 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(event: keyof typeof GameEvent, eventData: IEventInfo) { + const subscribers = this.eventListenersMap.get(event) + + if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData)) + } + + public processCustomEvent(event: string, eventData: IEventInfo) { + const subscribers = this.customEventListenersMap.get(event) + + if (subscribers) subscribers.forEach((cb) => cb(this.options, event, eventData)) + } + + public registerCustomEvent(event: string, cb: CustomEventCallback) { + 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(name: string, factory: T) { + this.factories.set(name, factory) + + return factory + } + + public getFactory(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) } diff --git a/src/interfaces/core/engine/classes-engine.interfaces.ts b/src/interfaces/core/engine/classes-engine.interfaces.ts index 0d06811..f9e7f29 100644 --- a/src/interfaces/core/engine/classes-engine.interfaces.ts +++ b/src/interfaces/core/engine/classes-engine.interfaces.ts @@ -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 diff --git a/src/interfaces/core/engine/engine-options.intefaces.ts b/src/interfaces/core/engine/engine-options.intefaces.ts index c1102f2..e843eb7 100644 --- a/src/interfaces/core/engine/engine-options.intefaces.ts +++ b/src/interfaces/core/engine/engine-options.intefaces.ts @@ -38,6 +38,11 @@ export interface IGameOptions { */ readonly usingObjectMiddlewares?: boolean; + /** + * Flag to disable built-in ConflictResolverPlugin + */ + readonly disableConflictResolver?: boolean; + /** * Optional command bus options */ diff --git a/src/plugins/conflict-resolver.plugin.ts b/src/plugins/conflict-resolver.plugin.ts new file mode 100644 index 0000000..2115537 --- /dev/null +++ b/src/plugins/conflict-resolver.plugin.ts @@ -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 + } +} \ No newline at end of file diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 0d93d32..f538074 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -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" \ No newline at end of file +export * from "./graphic.plugin.js" +export * from "./conflict-resolver.plugin.js" \ No newline at end of file diff --git a/test/gametest.ts b/test/gametest.ts index 6d9aa69..550e459 100644 --- a/test/gametest.ts +++ b/test/gametest.ts @@ -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: {