diff --git a/src/js/game/game_system_manager.js b/src/js/game/game_system_manager.js index 08609f89..05085b70 100644 --- a/src/js/game/game_system_manager.js +++ b/src/js/game/game_system_manager.js @@ -1,4 +1,5 @@ /* typehints:start */ +import { GameSystem } from "./game_system"; import { GameRoot } from "./root"; /* typehints:end */ @@ -30,6 +31,11 @@ import { ZoneSystem } from "./systems/zone"; const logger = createLogger("game_system_manager"); +/** + * @type {Object GameSystem}>>} + */ +export const MODS_ADDITIONAL_SYSTEMS = {}; + export class GameSystemManager { /** * @@ -123,7 +129,15 @@ export class GameSystemManager { * Initializes all systems */ internalInitSystems() { + const addBefore = id => { + const systems = MODS_ADDITIONAL_SYSTEMS[id]; + if (systems) { + systems.forEach(({ id, systemClass }) => add(id, systemClass)); + } + }; + const add = (id, systemClass) => { + addBefore(id); this.systems[id] = new systemClass(this.root); this.systemUpdateOrder.push(id); }; @@ -187,6 +201,14 @@ export class GameSystemManager { add("zone", ZoneSystem); } + addBefore("end"); + + for (const key in MODS_ADDITIONAL_SYSTEMS) { + if (!this.systems[key]) { + logger.error("Mod system not attached due to invalid 'before': ", key); + } + } + logger.log("📦 There are", this.systemUpdateOrder.length, "game systems"); } diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index 947b7a9f..86c14fb8 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -5,10 +5,20 @@ import { Entity } from "./entity"; import { MapChunk } from "./map_chunk"; import { GameRoot } from "./root"; import { THEME } from "./theme"; -import { drawSpriteClipped } from "../core/draw_utils"; export const CHUNK_OVERLAY_RES = 3; +export const MOD_CHUNK_DRAW_HOOKS = { + backgroundLayerBefore: [], + backgroundLayerAfter: [], + + foregroundDynamicBefore: [], + foregroundDynamicAfter: [], + + staticBefore: [], + staticAfter: [], +}; + export class MapChunkView extends MapChunk { /** * @@ -42,6 +52,11 @@ export class MapChunkView extends MapChunk { */ drawBackgroundLayer(parameters) { const systems = this.root.systemMgr.systems; + + MOD_CHUNK_DRAW_HOOKS.backgroundLayerBefore.forEach(systemId => + systems[systemId].drawChunk(parameters, this) + ); + if (systems.zone) { systems.zone.drawChunk(parameters, this); } @@ -52,6 +67,10 @@ export class MapChunkView extends MapChunk { systems.beltUnderlays.drawChunk(parameters, this); systems.belt.drawChunk(parameters, this); + + MOD_CHUNK_DRAW_HOOKS.backgroundLayerAfter.forEach(systemId => + systems[systemId].drawChunk(parameters, this) + ); } /** @@ -61,9 +80,17 @@ export class MapChunkView extends MapChunk { drawForegroundDynamicLayer(parameters) { const systems = this.root.systemMgr.systems; + MOD_CHUNK_DRAW_HOOKS.foregroundDynamicBefore.forEach(systemId => + systems[systemId].drawChunk(parameters, this) + ); + systems.itemEjector.drawChunk(parameters, this); systems.itemAcceptor.drawChunk(parameters, this); systems.miner.drawChunk(parameters, this); + + MOD_CHUNK_DRAW_HOOKS.foregroundDynamicAfter.forEach(systemId => + systems[systemId].drawChunk(parameters, this) + ); } /** @@ -73,6 +100,8 @@ export class MapChunkView extends MapChunk { drawForegroundStaticLayer(parameters) { const systems = this.root.systemMgr.systems; + MOD_CHUNK_DRAW_HOOKS.staticBefore.forEach(systemId => systems[systemId].drawChunk(parameters, this)); + systems.staticMapEntities.drawChunk(parameters, this); systems.lever.drawChunk(parameters, this); systems.display.drawChunk(parameters, this); @@ -80,6 +109,8 @@ export class MapChunkView extends MapChunk { systems.constantProducer.drawChunk(parameters, this); systems.goalAcceptor.drawChunk(parameters, this); systems.itemProcessorOverlays.drawChunk(parameters, this); + + MOD_CHUNK_DRAW_HOOKS.staticAfter.forEach(systemId => systems[systemId].drawChunk(parameters, this)); } /** diff --git a/src/js/mods/demo_mod.nobuild/index.js b/src/js/mods/demo_mod.nobuild/index.js index 2188defc..99ea39b7 100644 --- a/src/js/mods/demo_mod.nobuild/index.js +++ b/src/js/mods/demo_mod.nobuild/index.js @@ -1,4 +1,22 @@ registerMod(shapez => { + class DemoModComponent extends shapez.Component { + static getId() { + return "DemoMod"; + } + + static getSchema() { + return { + magicNumber: shapez.types.uint, + }; + } + + constructor(magicNumber) { + super(); + + this.magicNumber = magicNumber; + } + } + class MetaDemoModBuilding extends shapez.MetaBuilding { constructor() { super("demoModBuilding"); @@ -8,7 +26,58 @@ registerMod(shapez => { return "red"; } - setupEntityComponents(entity) {} + setupEntityComponents(entity) { + entity.addComponent(new DemoModComponent(Math.floor(Math.random() * 100.0))); + } + } + + class DemoModSystem extends shapez.GameSystemWithFilter { + constructor(root) { + super(root, [DemoModComponent]); + } + + update() { + // nothing to do here + } + + drawChunk(parameters, chunk) { + const contents = chunk.containedEntitiesByLayer.regular; + for (let i = 0; i < contents.length; ++i) { + const entity = contents[i]; + const demoComp = entity.components.DemoMod; + if (!demoComp) { + continue; + } + + const staticComp = entity.components.StaticMapEntity; + + const context = parameters.context; + const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace(); + + // Culling for better performance + if (parameters.visibleRect.containsCircle(center.x, center.y, 40)) { + // Circle + context.fillStyle = "#53cf47"; + context.strokeStyle = "#000"; + context.lineWidth = 2; + + const timeFactor = 5.23 * this.root.time.now(); + context.beginCircle( + center.x + Math.cos(timeFactor) * 10, + center.y + Math.sin(timeFactor) * 10, + 7 + ); + context.fill(); + context.stroke(); + + // Text + context.fillStyle = "#fff"; + context.textAlign = "center"; + context.font = "12px GameFont"; + context.fillText(demoComp.magicNumber, center.x, center.y + 4); + } + } + } } return class ModImpl extends shapez.Mod { @@ -87,6 +156,17 @@ registerMod(shapez => { }, }); + // Register a new component + this.modInterface.registerComponent(DemoModComponent); + + // Register a new game system which can update and draw stuff + this.modInterface.registerGameSystem({ + id: "demo_mod", + systemClass: DemoModSystem, + before: "belt", + drawHooks: ["staticAfter"], + }); + // Register the new building this.modInterface.registerNewBuilding({ metaClass: MetaDemoModBuilding, diff --git a/src/js/mods/mod_interface.js b/src/js/mods/mod_interface.js index 89330d3d..40f1cb13 100644 --- a/src/js/mods/mod_interface.js +++ b/src/js/mods/mod_interface.js @@ -1,5 +1,6 @@ /* typehints:start */ import { ModLoader } from "./modloader"; +import { GameSystem } from "../game/game_system"; import { Component } from "../game/component"; import { MetaBuilding } from "../game/meta_building"; /* typehints:end */ @@ -19,6 +20,8 @@ import { matchDataRecursive, T } from "../translations"; import { gBuildingVariants, registerBuildingVariant } from "../game/building_codes"; import { gComponentRegistry, gMetaBuildingRegistry } from "../core/global_registries"; import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../game/map_chunk"; +import { MODS_ADDITIONAL_SYSTEMS } from "../game/game_system_manager"; +import { MOD_CHUNK_DRAW_HOOKS } from "../game/map_chunk_view"; const LOG = createLogger("mod-interface"); @@ -115,6 +118,40 @@ export class ModInterface { gComponentRegistry.register(component); } + /** + * + * @param {Object} param0 + * @param {string} param0.id + * @param {new (any) => GameSystem} param0.systemClass + * @param {string=} param0.before + * @param {string[]=} param0.drawHooks + */ + registerGameSystem({ id, systemClass, before, drawHooks }) { + const key = before || "key"; + const payload = { id, systemClass }; + + if (MODS_ADDITIONAL_SYSTEMS[key]) { + MODS_ADDITIONAL_SYSTEMS[key].push(payload); + } else { + MODS_ADDITIONAL_SYSTEMS[key] = [payload]; + } + if (drawHooks) { + drawHooks.forEach(hookId => this.registerGameSystemDrawHook(hookId, id)); + } + } + + /** + * + * @param {string} hookId + * @param {string} systemId + */ + registerGameSystemDrawHook(hookId, systemId) { + if (!MOD_CHUNK_DRAW_HOOKS[hookId]) { + throw new Error("bad game system draw hook: " + hookId); + } + MOD_CHUNK_DRAW_HOOKS[hookId].push(systemId); + } + /** * * @param {object} param0