1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-13 18:21:51 +00:00

Refactor how mods are loaded to resolve circular dependencies and prepare for future mod loading

This commit is contained in:
tobspr 2022-01-14 07:18:25 +01:00
parent 01b9bf561c
commit 16ecbf9c6d
10 changed files with 159 additions and 139 deletions

View File

@ -1,7 +1,7 @@
import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters";
import { Signal } from "../../core/signal";
import { MODS } from "../../mods/modloader";
import { MOD_SIGNALS } from "../../mods/mod_signals";
import { KEYMAPPINGS } from "../key_action_mapper";
import { MetaBuilding } from "../meta_building";
import { GameRoot } from "../root";
@ -92,7 +92,7 @@ export class GameHUD {
const frag = document.createDocumentFragment();
for (const key in this.parts) {
MODS.signals.hudElementInitialized.dispatch(this.parts[key]);
MOD_SIGNALS.hudElementInitialized.dispatch(this.parts[key]);
this.parts[key].createElements(frag);
}
@ -100,7 +100,7 @@ export class GameHUD {
for (const key in this.parts) {
this.parts[key].initialize();
MODS.signals.hudElementFinalized.dispatch(this.parts[key]);
MOD_SIGNALS.hudElementFinalized.dispatch(this.parts[key]);
}
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.toggleHud).add(this.toggleUi, this);

View File

@ -10,10 +10,14 @@ import { COLOR_ITEM_SINGLETONS } from "./items/color_item";
import { GameRoot } from "./root";
import { enumSubShape } from "./shape_definition";
import { Rectangle } from "../core/rectangle";
import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../mods/mod_interface";
const logger = createLogger("map_chunk");
/**
* @type {Object<string, (distanceToOriginInChunks: number) => number>}
*/
export const MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS = {};
export class MapChunk {
/**
*

View File

@ -38,7 +38,7 @@ import { HUDSandboxController } from "../hud/parts/sandbox_controller";
import { queryParamOptions } from "../../core/query_parameters";
import { MetaBlockBuilding } from "../buildings/block";
import { MetaItemProducerBuilding } from "../buildings/item_producer";
import { MODS } from "../../mods/modloader";
import { MOD_SIGNALS } from "../../mods/mod_signals";
/** @typedef {{
* shape: string,
@ -512,7 +512,7 @@ export function generateLevelDefinitions(limitedVersion = false) {
]),
];
MODS.signals.modifyLevelDefinitions.dispatch(levelDefinitions);
MOD_SIGNALS.modifyLevelDefinitions.dispatch(levelDefinitions);
if (G_IS_DEV) {
levelDefinitions.forEach(({ shape }) => {

View File

@ -3,11 +3,23 @@ import { globalConfig } from "../core/config";
import { smoothenDpi } from "../core/dpi_manager";
import { DrawParameters } from "../core/draw_parameters";
import { Vector } from "../core/vector";
import { MODS_ADDITIONAL_SUB_SHAPE_DRAWERS } from "../mods/mod_interface";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { enumColors, enumColorsToHexCode, enumColorToShortcode, enumShortcodeToColor } from "./colors";
import { THEME } from "./theme";
/**
* @typedef {{
* context: CanvasRenderingContext2D,
* quadrantSize: number,
* layerScale: number,
* }} SubShapeDrawOptions
*/
/**
* @type {Object<string, (options: SubShapeDrawOptions) => void>}
*/
export const MODS_ADDITIONAL_SUB_SHAPE_DRAWERS = {};
/**
* @typedef {{
* subShape: enumSubShape,

View File

@ -1,4 +1,4 @@
import { MODS } from "../mods/modloader";
import { MOD_SIGNALS } from "../mods/mod_signals";
export const THEMES = {
dark: require("./themes/dark.json"),
@ -9,5 +9,5 @@ export let THEME = THEMES.light;
export function applyGameTheme(id) {
THEME = THEMES[id];
MODS.signals.preprocessTheme.dispatch({ id, theme: THEME });
MOD_SIGNALS.preprocessTheme.dispatch({ id, theme: THEME });
}

View File

@ -2,119 +2,118 @@
import { Entity } from "../game/entity";
/* typehints:end */
import { Mod } from "./mod";
import { MetaBuilding } from "../game/meta_building";
export default function ({ Mod, MetaBuilding }) {
class MetaDemoModBuilding extends MetaBuilding {
constructor() {
super("demoModBuilding");
}
export class MetaDemoModBuilding extends MetaBuilding {
constructor() {
super("demoModBuilding");
getSilhouetteColor() {
return "red";
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {}
}
getSilhouetteColor() {
return "red";
}
return class ModImpl extends Mod {
constructor(modLoader) {
super(
{
authorContact: "tobias@tobspr.io",
authorName: "tobspr",
name: "Demo Mod",
version: "1",
id: "demo-mod",
},
modLoader
);
}
/**
* Creates the entity at the given location
* @param {Entity} entity
*/
setupEntityComponents(entity) {}
}
init() {
// Add some custom css
this.modInterface.registerCss(`
* {
color: red !important;
}
`);
export class DemoMod extends Mod {
constructor(modLoader) {
super(
{
authorContact: "tobias@tobspr.io",
authorName: "tobspr",
name: "Demo Mod",
version: "1",
id: "demo-mod",
},
modLoader
);
}
// Replace a builtin sprite
this.modInterface.registerSprite("sprites/colors/red.png", RESOURCES["red.png"]);
init() {
// Add some custom css
this.modLoader.modInterface.registerCss(`
* {
color: red !important;
}
`);
// Add a new type of sub shape ("Line", short code "L")
this.modInterface.registerSubShapeType({
id: "line",
shortCode: "L",
weightComputation: distanceToOriginInChunks =>
Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
// Replace a builtin sprite
this.modLoader.modInterface.registerSprite("sprites/colors/red.png", RESOURCES["red.png"]);
draw: ({ context, quadrantSize, layerScale }) => {
const quadrantHalfSize = quadrantSize / 2;
context.beginPath();
context.moveTo(-quadrantHalfSize, quadrantHalfSize);
context.arc(
-quadrantHalfSize,
quadrantHalfSize,
quadrantSize * layerScale,
-Math.PI * 0.25,
0
);
context.closePath();
},
});
// Add a new type of sub shape ("Line", short code "L")
this.modLoader.modInterface.registerSubShapeType({
id: "line",
shortCode: "L",
weightComputation: distanceToOriginInChunks =>
Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
// Modify the theme colors
this.signals.preprocessTheme.add(({ theme }) => {
theme.map.background = "#eee";
theme.items.outline = "#000";
});
draw: ({ context, quadrantSize, layerScale }) => {
const quadrantHalfSize = quadrantSize / 2;
context.beginPath();
context.moveTo(-quadrantHalfSize, quadrantHalfSize);
context.arc(
-quadrantHalfSize,
quadrantHalfSize,
quadrantSize * layerScale,
-Math.PI * 0.25,
0
);
context.closePath();
},
});
// Modify the goal of the first level
this.signals.modifyLevelDefinitions.add(definitions => {
definitions[0].shape = "LuCuLuCu";
});
// Modify the theme colors
this.modLoader.signals.preprocessTheme.add(({ theme }) => {
theme.map.background = "#eee";
theme.items.outline = "#000";
});
// Modify the goal of the first level
this.modLoader.signals.modifyLevelDefinitions.add(definitions => {
definitions[0].shape = "LuCuLuCu";
});
this.modLoader.modInterface.registerTranslations("en", {
ingame: {
interactiveTutorial: {
title: "Hello",
hints: {
"1_1_extractor": "World!",
this.modInterface.registerTranslations("en", {
ingame: {
interactiveTutorial: {
title: "Hello",
hints: {
"1_1_extractor": "World!",
},
},
},
},
});
});
// Register the new building
this.modLoader.modInterface.registerNewBuilding({
metaClass: MetaDemoModBuilding,
buildingIconBase64: RESOURCES["demoBuilding.png"],
// Register the new building
this.modInterface.registerNewBuilding({
metaClass: MetaDemoModBuilding,
buildingIconBase64: RESOURCES["demoBuilding.png"],
variantsAndRotations: [
{
description: "A test building",
name: "A test name",
variantsAndRotations: [
{
description: "A test building",
name: "A test name",
regularImageBase64: RESOURCES["demoBuilding.png"],
blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
},
],
});
regularImageBase64: RESOURCES["demoBuilding.png"],
blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
},
],
});
// Add it to the regular toolbar
this.modLoader.signals.hudElementInitialized.add(element => {
if (element.constructor.name === "HUDBuildingsToolbar") {
// @ts-ignore
element.primaryBuildings.push(MetaDemoModBuilding);
}
});
}
// Add it to the regular toolbar
this.signals.hudElementInitialized.add(element => {
if (element.constructor.name === "HUDBuildingsToolbar") {
// @ts-ignore
element.primaryBuildings.push(MetaDemoModBuilding);
}
});
}
};
}
////////////////////////////////////////////////////////////////////////

View File

@ -2,6 +2,8 @@
import { ModLoader } from "./modloader";
/* typehints:end */
import { MOD_SIGNALS } from "./mod_signals";
export class Mod {
/**
*
@ -17,6 +19,9 @@ export class Mod {
constructor(metadata, modLoader) {
this.metadata = metadata;
this.modLoader = modLoader;
this.signals = MOD_SIGNALS;
this.modInterface = modLoader.modInterface;
}
init() {}

View File

@ -6,33 +6,21 @@ import { MetaBuilding } from "../game/meta_building";
import { defaultBuildingVariant } from "../game/meta_building";
import { createLogger } from "../core/logging";
import { AtlasSprite, SpriteAtlasLink } from "../core/sprites";
import { enumShortcodeToSubShape, enumSubShape, enumSubShapeToShortcode } from "../game/shape_definition";
import {
enumShortcodeToSubShape,
enumSubShape,
enumSubShapeToShortcode,
MODS_ADDITIONAL_SUB_SHAPE_DRAWERS,
} from "../game/shape_definition";
import { Loader } from "../core/loader";
import { LANGUAGES } from "../languages";
import { matchDataRecursive, T } from "../translations";
import { registerBuildingVariant } from "../game/building_codes";
import { gMetaBuildingRegistry } from "../core/global_registries";
import { MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS } from "../game/map_chunk";
const LOG = createLogger("mod-interface");
/**
* @type {Object<string, (distanceToOriginInChunks: number) => number>}
*/
export const MODS_ADDITIONAL_SHAPE_MAP_WEIGHTS = {};
/**
* @typedef {{
* context: CanvasRenderingContext2D,
* quadrantSize: number,
* layerScale: number,
* }} SubShapeDrawOptions
*/
/**
* @type {Object<string, (options: SubShapeDrawOptions) => void>}
*/
export const MODS_ADDITIONAL_SUB_SHAPE_DRAWERS = {};
export class ModInterface {
/**
*
@ -91,7 +79,7 @@ export class ModInterface {
* @param {string} param0.id
* @param {string} param0.shortCode
* @param {(distanceToOriginInChunks: number) => number} param0.weightComputation
* @param {(options: SubShapeDrawOptions) => void} param0.draw
* @param {(options: import("../game/shape_definition").SubShapeDrawOptions) => void} param0.draw
*/
registerSubShapeType({ id, shortCode, weightComputation, draw }) {
if (shortCode.length !== 1) {

View File

@ -0,0 +1,16 @@
import { Signal } from "../core/signal";
/* typehints:start */
import { BaseHUDPart } from "../game/hud/base_hud_part";
/* typehints:end */
// Single file to avoid circular deps
export const MOD_SIGNALS = {
postInit: new Signal(),
injectSprites: new Signal(),
preprocessTheme: /** @type {TypedSignal<[Object]>} */ (new Signal()),
modifyLevelDefinitions: /** @type {TypedSignal<[Array[Object]]>} */ (new Signal()),
hudElementInitialized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()),
hudElementFinalized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()),
};

View File

@ -1,9 +1,8 @@
import { createLogger } from "../core/logging";
import { Signal } from "../core/signal";
import { DemoMod } from "./demo_mod";
import { Mod } from "./mod";
import { ModInterface } from "./mod_interface";
import { BaseHUDPart } from "../game/hud/base_hud_part";
import { MetaBuilding } from "../game/meta_building";
import { MOD_SIGNALS } from "./mod_signals";
const LOG = createLogger("mods");
@ -21,17 +20,14 @@ export class ModLoader {
this.initialized = false;
this.signals = {
postInit: new Signal(),
injectSprites: new Signal(),
preprocessTheme: /** @type {TypedSignal<[Object]>} */ (new Signal()),
modifyLevelDefinitions: /** @type {TypedSignal<[Array[Object]]>} */ (new Signal()),
this.signals = MOD_SIGNALS;
hudElementInitialized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()),
hudElementFinalized: /** @type {TypedSignal<[BaseHUDPart]>} */ (new Signal()),
};
this.registerMod(DemoMod);
this.registerMod(
/** @type {any} */ (require("./demo_mod").default({
Mod,
MetaBuilding,
}))
);
this.initMods();
}