1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-14 10:41:52 +00:00

Allow mods to register game systems and draw stuff

This commit is contained in:
tobspr 2022-01-14 13:09:05 +01:00
parent 4176621b05
commit ebda1c5024
4 changed files with 172 additions and 2 deletions

View File

@ -1,4 +1,5 @@
/* typehints:start */ /* typehints:start */
import { GameSystem } from "./game_system";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
/* typehints:end */ /* typehints:end */
@ -30,6 +31,11 @@ import { ZoneSystem } from "./systems/zone";
const logger = createLogger("game_system_manager"); const logger = createLogger("game_system_manager");
/**
* @type {Object<string, Array<{ id: string; systemClass: new (any) => GameSystem}>>}
*/
export const MODS_ADDITIONAL_SYSTEMS = {};
export class GameSystemManager { export class GameSystemManager {
/** /**
* *
@ -123,7 +129,15 @@ export class GameSystemManager {
* Initializes all systems * Initializes all systems
*/ */
internalInitSystems() { internalInitSystems() {
const addBefore = id => {
const systems = MODS_ADDITIONAL_SYSTEMS[id];
if (systems) {
systems.forEach(({ id, systemClass }) => add(id, systemClass));
}
};
const add = (id, systemClass) => { const add = (id, systemClass) => {
addBefore(id);
this.systems[id] = new systemClass(this.root); this.systems[id] = new systemClass(this.root);
this.systemUpdateOrder.push(id); this.systemUpdateOrder.push(id);
}; };
@ -187,6 +201,14 @@ export class GameSystemManager {
add("zone", ZoneSystem); 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"); logger.log("📦 There are", this.systemUpdateOrder.length, "game systems");
} }

View File

@ -5,10 +5,20 @@ import { Entity } from "./entity";
import { MapChunk } from "./map_chunk"; import { MapChunk } from "./map_chunk";
import { GameRoot } from "./root"; import { GameRoot } from "./root";
import { THEME } from "./theme"; import { THEME } from "./theme";
import { drawSpriteClipped } from "../core/draw_utils";
export const CHUNK_OVERLAY_RES = 3; export const CHUNK_OVERLAY_RES = 3;
export const MOD_CHUNK_DRAW_HOOKS = {
backgroundLayerBefore: [],
backgroundLayerAfter: [],
foregroundDynamicBefore: [],
foregroundDynamicAfter: [],
staticBefore: [],
staticAfter: [],
};
export class MapChunkView extends MapChunk { export class MapChunkView extends MapChunk {
/** /**
* *
@ -42,6 +52,11 @@ export class MapChunkView extends MapChunk {
*/ */
drawBackgroundLayer(parameters) { drawBackgroundLayer(parameters) {
const systems = this.root.systemMgr.systems; const systems = this.root.systemMgr.systems;
MOD_CHUNK_DRAW_HOOKS.backgroundLayerBefore.forEach(systemId =>
systems[systemId].drawChunk(parameters, this)
);
if (systems.zone) { if (systems.zone) {
systems.zone.drawChunk(parameters, this); systems.zone.drawChunk(parameters, this);
} }
@ -52,6 +67,10 @@ export class MapChunkView extends MapChunk {
systems.beltUnderlays.drawChunk(parameters, this); systems.beltUnderlays.drawChunk(parameters, this);
systems.belt.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) { drawForegroundDynamicLayer(parameters) {
const systems = this.root.systemMgr.systems; const systems = this.root.systemMgr.systems;
MOD_CHUNK_DRAW_HOOKS.foregroundDynamicBefore.forEach(systemId =>
systems[systemId].drawChunk(parameters, this)
);
systems.itemEjector.drawChunk(parameters, this); systems.itemEjector.drawChunk(parameters, this);
systems.itemAcceptor.drawChunk(parameters, this); systems.itemAcceptor.drawChunk(parameters, this);
systems.miner.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) { drawForegroundStaticLayer(parameters) {
const systems = this.root.systemMgr.systems; const systems = this.root.systemMgr.systems;
MOD_CHUNK_DRAW_HOOKS.staticBefore.forEach(systemId => systems[systemId].drawChunk(parameters, this));
systems.staticMapEntities.drawChunk(parameters, this); systems.staticMapEntities.drawChunk(parameters, this);
systems.lever.drawChunk(parameters, this); systems.lever.drawChunk(parameters, this);
systems.display.drawChunk(parameters, this); systems.display.drawChunk(parameters, this);
@ -80,6 +109,8 @@ export class MapChunkView extends MapChunk {
systems.constantProducer.drawChunk(parameters, this); systems.constantProducer.drawChunk(parameters, this);
systems.goalAcceptor.drawChunk(parameters, this); systems.goalAcceptor.drawChunk(parameters, this);
systems.itemProcessorOverlays.drawChunk(parameters, this); systems.itemProcessorOverlays.drawChunk(parameters, this);
MOD_CHUNK_DRAW_HOOKS.staticAfter.forEach(systemId => systems[systemId].drawChunk(parameters, this));
} }
/** /**

View File

@ -1,4 +1,22 @@
registerMod(shapez => { 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 { class MetaDemoModBuilding extends shapez.MetaBuilding {
constructor() { constructor() {
super("demoModBuilding"); super("demoModBuilding");
@ -8,7 +26,58 @@ registerMod(shapez => {
return "red"; 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 { 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 // Register the new building
this.modInterface.registerNewBuilding({ this.modInterface.registerNewBuilding({
metaClass: MetaDemoModBuilding, metaClass: MetaDemoModBuilding,

View File

@ -1,5 +1,6 @@
/* typehints:start */ /* typehints:start */
import { ModLoader } from "./modloader"; import { ModLoader } from "./modloader";
import { GameSystem } from "../game/game_system";
import { Component } from "../game/component"; import { Component } from "../game/component";
import { MetaBuilding } from "../game/meta_building"; import { MetaBuilding } from "../game/meta_building";
/* typehints:end */ /* typehints:end */
@ -19,6 +20,8 @@ import { matchDataRecursive, T } from "../translations";
import { gBuildingVariants, registerBuildingVariant } from "../game/building_codes"; import { gBuildingVariants, registerBuildingVariant } from "../game/building_codes";
import { gComponentRegistry, gMetaBuildingRegistry } from "../core/global_registries"; import { gComponentRegistry, gMetaBuildingRegistry } from "../core/global_registries";
import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../game/map_chunk"; 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"); const LOG = createLogger("mod-interface");
@ -115,6 +118,40 @@ export class ModInterface {
gComponentRegistry.register(component); 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 * @param {object} param0