diff --git a/mod_examples/add_building_basic.js b/mod_examples/add_building_basic.js
index b7c1a6bb..5147613c 100644
--- a/mod_examples/add_building_basic.js
+++ b/mod_examples/add_building_basic.js
@@ -3,69 +3,62 @@
* If you are interested in adding more logic to the game, you should also check out
* the advanced example
*/
-registerMod(() => {
- class MetaDemoModBuilding extends shapez.ModMetaBuilding {
- constructor() {
- super("demoModBuilding");
- }
- static getAllVariantCombinations() {
- return [
- {
- variant: shapez.defaultBuildingVariant,
- name: "A test name",
- description: "A test building",
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Add new basic building",
+ version: "1",
+ id: "add-building-basic",
+ description: "Shows how to add a new basic building",
+};
- regularImageBase64: RESOURCES["demoBuilding.png"],
- blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
- tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
- },
- ];
- }
-
- getSilhouetteColor() {
- return "red";
- }
-
- setupEntityComponents(entity) {
- // Here you can add components, for example an ItemProcessorComponent.
- // To get an idea what you can do with the builtin components, have a look
- // at the builtin buildings in
- }
+class MetaDemoModBuilding extends shapez.ModMetaBuilding {
+ constructor() {
+ super("demoModBuilding");
}
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Add new basic building",
- version: "1",
- id: "add-building-basic",
- description: "Shows how to add a new basic building",
- },
- modLoader
- );
- }
+ static getAllVariantCombinations() {
+ return [
+ {
+ variant: shapez.defaultBuildingVariant,
+ name: "A test name",
+ description: "A test building",
- init() {
- // Register the new building
- this.modInterface.registerNewBuilding({
- metaClass: MetaDemoModBuilding,
- buildingIconBase64: RESOURCES["demoBuilding.png"],
- });
+ regularImageBase64: RESOURCES["demoBuilding.png"],
+ blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
+ tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
+ },
+ ];
+ }
- // Add it to the regular toolbar
- this.modInterface.addNewBuildingToToolbar({
- toolbar: "regular",
- location: "primary",
- metaClass: MetaDemoModBuilding,
- });
- }
- };
-});
+ getSilhouetteColor() {
+ return "red";
+ }
+
+ setupEntityComponents(entity) {
+ // Here you can add components, for example an ItemProcessorComponent.
+ // To get an idea what you can do with the builtin components, have a look
+ // at the builtin buildings in
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register the new building
+ this.modInterface.registerNewBuilding({
+ metaClass: MetaDemoModBuilding,
+ buildingIconBase64: RESOURCES["demoBuilding.png"],
+ });
+
+ // Add it to the regular toolbar
+ this.modInterface.addNewBuildingToToolbar({
+ toolbar: "regular",
+ location: "primary",
+ metaClass: MetaDemoModBuilding,
+ });
+ }
+}
////////////////////////////////////////////////////////////////////////
diff --git a/mod_examples/add_building_flipper.js b/mod_examples/add_building_flipper.js
index f216f31b..88093392 100644
--- a/mod_examples/add_building_flipper.js
+++ b/mod_examples/add_building_flipper.js
@@ -2,135 +2,127 @@
* This shows how to add a new extended building (A building which flips shapes).
* Be sure to check out the "add_building_basic" example first
*/
-registerMod(() => {
- // Declare a new type of item processor
- shapez.enumItemProcessorTypes.flipper = "flipper";
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Add a flipper building",
+ version: "1",
+ id: "add-building-extended",
+ description:
+ "Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
+};
- // For now, flipper always has the same speed
- shapez.MOD_ITEM_PROCESSOR_SPEEDS.flipper = () => 10;
+// Declare a new type of item processor
+shapez.enumItemProcessorTypes.flipper = "flipper";
- // Declare a handler for the processor so we define the "flip" operation
- shapez.MOD_ITEM_PROCESSOR_HANDLERS.flipper = function (payload) {
- const shapeDefinition = payload.items.get(0).definition;
+// For now, flipper always has the same speed
+shapez.MOD_ITEM_PROCESSOR_SPEEDS.flipper = () => 10;
- // Flip bottom with top on a new, cloned item (NEVER modify the incoming item!)
- const newLayers = shapeDefinition.getClonedLayers();
- newLayers.forEach(layer => {
- const tr = layer[shapez.TOP_RIGHT];
- const br = layer[shapez.BOTTOM_RIGHT];
- const bl = layer[shapez.BOTTOM_LEFT];
- const tl = layer[shapez.TOP_LEFT];
+// Declare a handler for the processor so we define the "flip" operation
+shapez.MOD_ITEM_PROCESSOR_HANDLERS.flipper = function (payload) {
+ const shapeDefinition = payload.items.get(0).definition;
- layer[shapez.BOTTOM_LEFT] = tl;
- layer[shapez.BOTTOM_RIGHT] = tr;
+ // Flip bottom with top on a new, cloned item (NEVER modify the incoming item!)
+ const newLayers = shapeDefinition.getClonedLayers();
+ newLayers.forEach(layer => {
+ const tr = layer[shapez.TOP_RIGHT];
+ const br = layer[shapez.BOTTOM_RIGHT];
+ const bl = layer[shapez.BOTTOM_LEFT];
+ const tl = layer[shapez.TOP_LEFT];
- layer[shapez.TOP_LEFT] = bl;
- layer[shapez.TOP_RIGHT] = br;
- });
+ layer[shapez.BOTTOM_LEFT] = tl;
+ layer[shapez.BOTTOM_RIGHT] = tr;
- const newDefinition = new shapez.ShapeDefinition({ layers: newLayers });
- payload.outItems.push({
- item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(newDefinition),
- });
- };
+ layer[shapez.TOP_LEFT] = bl;
+ layer[shapez.TOP_RIGHT] = br;
+ });
- // Create the building
- class MetaModFlipperBuilding extends shapez.ModMetaBuilding {
- constructor() {
- super("modFlipperBuilding");
- }
+ const newDefinition = new shapez.ShapeDefinition({ layers: newLayers });
+ payload.outItems.push({
+ item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(newDefinition),
+ });
+};
- static getAllVariantCombinations() {
- return [
- {
- name: "Flipper",
- description: "Flipps/Mirrors shapez from top to bottom",
- variant: shapez.defaultBuildingVariant,
-
- regularImageBase64: RESOURCES["flipper.png"],
- blueprintImageBase64: RESOURCES["flipper.png"],
- tutorialImageBase64: RESOURCES["flipper.png"],
- },
- ];
- }
-
- getSilhouetteColor() {
- return "red";
- }
-
- getAdditionalStatistics(root) {
- const speed = root.hubGoals.getProcessorBaseSpeed(shapez.enumItemProcessorTypes.flipper);
- return [[shapez.T.ingame.buildingPlacement.infoTexts.speed, shapez.formatItemsPerSecond(speed)]];
- }
-
- getIsUnlocked(root) {
- return true;
- }
-
- setupEntityComponents(entity) {
- // Accept shapes from the bottom
- entity.addComponent(
- new shapez.ItemAcceptorComponent({
- slots: [
- {
- pos: new shapez.Vector(0, 0),
- directions: [shapez.enumDirection.bottom],
- filter: "shape",
- },
- ],
- })
- );
-
- // Process those shapes with tye processor type "flipper" (which we added above)
- entity.addComponent(
- new shapez.ItemProcessorComponent({
- inputsPerCharge: 1,
- processorType: shapez.enumItemProcessorTypes.flipper,
- })
- );
-
- // Eject the result to the top
- entity.addComponent(
- new shapez.ItemEjectorComponent({
- slots: [{ pos: new shapez.Vector(0, 0), direction: shapez.enumDirection.top }],
- })
- );
- }
+// Create the building
+class MetaModFlipperBuilding extends shapez.ModMetaBuilding {
+ constructor() {
+ super("modFlipperBuilding");
}
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Add a flipper building",
- version: "1",
- id: "add-building-extended",
- description:
- "Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
- },
- modLoader
- );
- }
+ static getAllVariantCombinations() {
+ return [
+ {
+ name: "Flipper",
+ description: "Flipps/Mirrors shapez from top to bottom",
+ variant: shapez.defaultBuildingVariant,
- init() {
- // Register the new building
- this.modInterface.registerNewBuilding({
- metaClass: MetaModFlipperBuilding,
- buildingIconBase64: RESOURCES["flipper.png"],
- });
+ regularImageBase64: RESOURCES["flipper.png"],
+ blueprintImageBase64: RESOURCES["flipper.png"],
+ tutorialImageBase64: RESOURCES["flipper.png"],
+ },
+ ];
+ }
- // Add it to the regular toolbar
- this.modInterface.addNewBuildingToToolbar({
- toolbar: "regular",
- location: "primary",
- metaClass: MetaModFlipperBuilding,
- });
- }
- };
-});
+ getSilhouetteColor() {
+ return "red";
+ }
+
+ getAdditionalStatistics(root) {
+ const speed = root.hubGoals.getProcessorBaseSpeed(shapez.enumItemProcessorTypes.flipper);
+ return [[shapez.T.ingame.buildingPlacement.infoTexts.speed, shapez.formatItemsPerSecond(speed)]];
+ }
+
+ getIsUnlocked(root) {
+ return true;
+ }
+
+ setupEntityComponents(entity) {
+ // Accept shapes from the bottom
+ entity.addComponent(
+ new shapez.ItemAcceptorComponent({
+ slots: [
+ {
+ pos: new shapez.Vector(0, 0),
+ directions: [shapez.enumDirection.bottom],
+ filter: "shape",
+ },
+ ],
+ })
+ );
+
+ // Process those shapes with tye processor type "flipper" (which we added above)
+ entity.addComponent(
+ new shapez.ItemProcessorComponent({
+ inputsPerCharge: 1,
+ processorType: shapez.enumItemProcessorTypes.flipper,
+ })
+ );
+
+ // Eject the result to the top
+ entity.addComponent(
+ new shapez.ItemEjectorComponent({
+ slots: [{ pos: new shapez.Vector(0, 0), direction: shapez.enumDirection.top }],
+ })
+ );
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register the new building
+ this.modInterface.registerNewBuilding({
+ metaClass: MetaModFlipperBuilding,
+ buildingIconBase64: RESOURCES["flipper.png"],
+ });
+
+ // Add it to the regular toolbar
+ this.modInterface.addNewBuildingToToolbar({
+ toolbar: "regular",
+ location: "primary",
+ metaClass: MetaModFlipperBuilding,
+ });
+ }
+}
////////////////////////////////////////////////////////////////////////
diff --git a/mod_examples/base.js b/mod_examples/base.js
index c60f4b87..94776d5e 100644
--- a/mod_examples/base.js
+++ b/mod_examples/base.js
@@ -1,25 +1,18 @@
/**
* This is the minimal structure of a mod
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Base",
- version: "1",
- id: "base",
- description: "The most basic mod",
- },
- modLoader
- );
- }
- init() {
- // Start the modding here
- }
- };
-});
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Base",
+ version: "1",
+ id: "base",
+ description: "The most basic mod",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Start the modding here
+ }
+}
diff --git a/mod_examples/buildings_have_cost.js b/mod_examples/buildings_have_cost.js
index 7fda49c6..96f47865 100644
--- a/mod_examples/buildings_have_cost.js
+++ b/mod_examples/buildings_have_cost.js
@@ -1,39 +1,32 @@
/**
* This shows how to patch existing methods by making belts have a cost
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Patch Methods",
- version: "1",
- id: "patch-methods",
- description:
- "Shows how to patch existing methods to change the game by making the belts cost shapes",
- },
- modLoader
- );
- }
- init() {
- // Define our currency
- const CURRENCY = "CyCyCyCy:--------:CuCuCuCu";
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Patch Methods",
+ version: "1",
+ id: "patch-methods",
+ description: "Shows how to patch existing methods to change the game by making the belts cost shapes",
+};
- // Make sure the currency is always pinned
- this.modInterface.runAfterMethod(shapez.HUDPinnedShapes, "rerenderFull", function () {
- this.internalPinShape({
- key: CURRENCY,
- canUnpin: false,
- className: "currency",
- });
+class Mod extends shapez.Mod {
+ init() {
+ // Define our currency
+ const CURRENCY = "CyCyCyCy:--------:CuCuCuCu";
+
+ // Make sure the currency is always pinned
+ this.modInterface.runAfterMethod(shapez.HUDPinnedShapes, "rerenderFull", function () {
+ this.internalPinShape({
+ key: CURRENCY,
+ canUnpin: false,
+ className: "currency",
});
+ });
- // Style it
- this.modInterface.registerCss(`
+ // Style it
+ this.modInterface.registerCss(`
#ingame_HUD_PinnedShapes .shape.currency::after {
content: " ";
position: absolute;
@@ -57,41 +50,40 @@ registerMod(() => {
}
`);
- // Make the player start with some currency
- this.modInterface.runAfterMethod(shapez.GameCore, "initNewGame", function () {
- this.root.hubGoals.storedShapes[CURRENCY] = 100;
- });
+ // Make the player start with some currency
+ this.modInterface.runAfterMethod(shapez.GameCore, "initNewGame", function () {
+ this.root.hubGoals.storedShapes[CURRENCY] = 100;
+ });
- // Make belts have a cost
- this.modInterface.replaceMethod(shapez.MetaBeltBuilding, "getAdditionalStatistics", function (
- $original,
- [root, variant]
- ) {
- const oldStats = $original(root, variant);
- oldStats.push(["Cost", "1 x "]);
- return oldStats;
- });
+ // Make belts have a cost
+ this.modInterface.replaceMethod(shapez.MetaBeltBuilding, "getAdditionalStatistics", function (
+ $original,
+ [root, variant]
+ ) {
+ const oldStats = $original(root, variant);
+ oldStats.push(["Cost", "1 x "]);
+ return oldStats;
+ });
- // Only allow placing an entity when there is enough currency
- this.modInterface.replaceMethod(shapez.GameLogic, "checkCanPlaceEntity", function (
- $original,
- [entity, offset]
- ) {
- const storedCurrency = this.root.hubGoals.storedShapes[CURRENCY] || 0;
- return storedCurrency > 0 && $original(entity, offset);
- });
+ // Only allow placing an entity when there is enough currency
+ this.modInterface.replaceMethod(shapez.GameLogic, "checkCanPlaceEntity", function (
+ $original,
+ [entity, offset]
+ ) {
+ const storedCurrency = this.root.hubGoals.storedShapes[CURRENCY] || 0;
+ return storedCurrency > 0 && $original(entity, offset);
+ });
- // Take shapes when placing a building
- this.modInterface.replaceMethod(shapez.GameLogic, "tryPlaceBuilding", function ($original, args) {
- const result = $original(...args);
- if (result && result.components.Belt) {
- this.root.hubGoals.storedShapes[CURRENCY]--;
- }
- return result;
- });
- }
- };
-});
+ // Take shapes when placing a building
+ this.modInterface.replaceMethod(shapez.GameLogic, "tryPlaceBuilding", function ($original, args) {
+ const result = $original(...args);
+ if (result && result.components.Belt) {
+ this.root.hubGoals.storedShapes[CURRENCY]--;
+ }
+ return result;
+ });
+ }
+}
const RESOURCES = {
"currency.png":
diff --git a/mod_examples/combined.js b/mod_examples/combined.js
index c7d6c6b6..b74d13a1 100644
--- a/mod_examples/combined.js
+++ b/mod_examples/combined.js
@@ -1,231 +1,189 @@
-registerMod(() => {
- class DemoModComponent extends shapez.Component {
- static getId() {
- return "DemoMod";
- }
-
- static getSchema() {
- return {
- magicNumber: shapez.types.uint,
- };
- }
-
- constructor(magicNumber) {
- super();
-
- this.magicNumber = magicNumber;
- }
+class DemoModComponent extends shapez.Component {
+ static getId() {
+ return "DemoMod";
}
- class MetaDemoModBuilding extends shapez.MetaBuilding {
- constructor() {
- super("demoModBuilding");
- }
-
- getSilhouetteColor() {
- return "red";
- }
-
- setupEntityComponents(entity) {
- entity.addComponent(new DemoModComponent(Math.floor(Math.random() * 100.0)));
- }
+ static getSchema() {
+ return {
+ magicNumber: shapez.types.uint,
+ };
}
- class DemoModSystem extends shapez.GameSystemWithFilter {
- constructor(root) {
- super(root, [DemoModComponent]);
- }
+ constructor(magicNumber) {
+ super();
- update() {
- // nothing to do here
- }
+ this.magicNumber = magicNumber;
+ }
+}
- 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;
- }
+class MetaDemoModBuilding extends shapez.MetaBuilding {
+ constructor() {
+ super("demoModBuilding");
+ }
- const staticComp = entity.components.StaticMapEntity;
+ getSilhouetteColor() {
+ return "red";
+ }
- const context = parameters.context;
- const center = staticComp.getTileSpaceBounds().getCenter().toWorldSpace();
+ setupEntityComponents(entity) {
+ entity.addComponent(new DemoModComponent(Math.floor(Math.random() * 100.0)));
+ }
+}
- // Culling for better performance
- if (parameters.visibleRect.containsCircle(center.x, center.y, 40)) {
- // Circle
- context.fillStyle = "#53cf47";
- context.strokeStyle = "#000";
- context.lineWidth = 2;
+class DemoModSystem extends shapez.GameSystemWithFilter {
+ constructor(root) {
+ super(root, [DemoModComponent]);
+ }
- 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();
+ update() {
+ // nothing to do here
+ }
- // Text
- context.fillStyle = "#fff";
- context.textAlign = "center";
- context.font = "12px GameFont";
- context.fillText(demoComp.magicNumber, center.x, center.y + 4);
- }
+ 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 {
- constructor(app, modLoader) {
- super(
- app,
+class Mod extends shapez.Mod {
+ constructor(app, modLoader) {
+ super(
+ app,
+ {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Demo Mod",
+ version: "1",
+ id: "demo-mod",
+ description: "A simple mod to demonstrate the capatibilities of the mod loader.",
+ },
+ modLoader
+ );
+ }
+
+ init() {
+ // Add some custom css
+ // this.modInterface.registerCss(`
+ // * {
+ // font-family: "Comic Sans", "Comic Sans MS", Tahoma !important;
+ // }
+ // `);
+
+ // Add an atlas
+ this.modInterface.registerAtlas(RESOURCES["demoAtlas.png"], RESOURCES["demoAtlas.json"]);
+
+ this.modInterface.registerTranslations("en", {
+ ingame: {
+ interactiveTutorial: {
+ title: "Hello",
+ hints: {
+ "1_1_extractor": "World!",
+ },
+ },
+ },
+ });
+
+ // 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,
+ buildingIconBase64: RESOURCES["demoBuilding.png"],
+
+ variantsAndRotations: [
{
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Demo Mod",
- version: "1",
- id: "demo-mod",
- description: "A simple mod to demonstrate the capatibilities of the mod loader.",
+ description: "A test building",
+ name: "A test name",
+
+ regularImageBase64: RESOURCES["demoBuilding.png"],
+ blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
+ tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
},
- modLoader
- );
- }
+ ],
+ });
- init() {
- // Add some custom css
- // this.modInterface.registerCss(`
- // * {
- // font-family: "Comic Sans", "Comic Sans MS", Tahoma !important;
- // }
- // `);
+ // Add it to the regular toolbar
+ this.modInterface.addNewBuildingToToolbar({
+ toolbar: "regular",
+ location: "primary",
+ metaClass: MetaDemoModBuilding,
+ });
- // Replace a builtin sprite
- ["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
- this.modInterface.registerSprite(
- "sprites/colors/" + color + ".png",
- RESOURCES[color + ".png"]
- );
- });
+ // Register keybinding
+ this.modInterface.registerIngameKeybinding({
+ id: "demo_mod_binding",
+ keyCode: shapez.keyToKeyCode("F"),
+ translation: "mymod: Do something (always with SHIFT)",
+ modifiers: {
+ shift: true,
+ },
+ handler: root => {
+ this.dialogs.showInfo("Mod Message", "It worked!");
+ return shapez.STOP_PROPAGATION;
+ },
+ });
- // Add an atlas
- this.modInterface.registerAtlas(RESOURCES["demoAtlas.png"], RESOURCES["demoAtlas.json"]);
+ // Add fancy sign to main menu
+ this.signals.stateEntered.add(state => {
+ if (state.key === "MainMenuState") {
+ const element = document.createElement("div");
+ element.id = "demo_mod_hello_world_element";
+ document.body.appendChild(element);
- // 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)),
+ const button = document.createElement("button");
+ button.classList.add("styledButton");
+ button.innerText = "Hello!";
+ button.addEventListener("click", () => {
+ this.dialogs.showInfo("Mod Message", "Button clicked!");
+ });
+ element.appendChild(button);
+ }
+ });
- 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 theme colors
- this.signals.preprocessTheme.add(({ theme }) => {
- theme.map.background = "#eee";
- theme.items.outline = "#000";
- });
-
- // Modify the goal of the first level
- this.signals.modifyLevelDefinitions.add(definitions => {
- definitions[0].shape = "LuCuLuCu";
- });
-
- this.modInterface.registerTranslations("en", {
- ingame: {
- interactiveTutorial: {
- title: "Hello",
- hints: {
- "1_1_extractor": "World!",
- },
- },
- },
- });
-
- // 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,
- buildingIconBase64: RESOURCES["demoBuilding.png"],
-
- variantsAndRotations: [
- {
- description: "A test building",
- name: "A test name",
-
- regularImageBase64: RESOURCES["demoBuilding.png"],
- blueprintImageBase64: RESOURCES["demoBuildingBlueprint.png"],
- tutorialImageBase64: RESOURCES["demoBuildingBlueprint.png"],
- },
- ],
- });
-
- // Add it to the regular toolbar
- this.modInterface.addNewBuildingToToolbar({
- toolbar: "regular",
- location: "primary",
- metaClass: MetaDemoModBuilding,
- });
-
- // Register keybinding
- this.modInterface.registerIngameKeybinding({
- id: "demo_mod_binding",
- keyCode: shapez.keyToKeyCode("F"),
- translation: "mymod: Do something (always with SHIFT)",
- modifiers: {
- shift: true,
- },
- handler: root => {
- this.dialogs.showInfo("Mod Message", "It worked!");
- return shapez.STOP_PROPAGATION;
- },
- });
-
- // Add fancy sign to main menu
- this.signals.stateEntered.add(state => {
- if (state.key === "MainMenuState") {
- const element = document.createElement("div");
- element.id = "demo_mod_hello_world_element";
- document.body.appendChild(element);
-
- const button = document.createElement("button");
- button.classList.add("styledButton");
- button.innerText = "Hello!";
- button.addEventListener("click", () => {
- this.dialogs.showInfo("Mod Message", "Button clicked!");
- });
- element.appendChild(button);
- }
- });
-
- this.modInterface.registerCss(`
+ this.modInterface.registerCss(`
#demo_mod_hello_world_element {
position: absolute;
top: calc(10px * var(--ui-scale));
@@ -235,9 +193,8 @@ registerMod(() => {
}
`);
- }
- };
-});
+ }
+}
////////////////////////////////////////////////////////////////////////
// @notice: Later this part will be autogenerated
diff --git a/mod_examples/custom_css.js b/mod_examples/custom_css.js
index a5dfcc08..5aa1219b 100644
--- a/mod_examples/custom_css.js
+++ b/mod_examples/custom_css.js
@@ -1,28 +1,21 @@
/**
* This example shows how to add custom css
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Add custom CSS",
- version: "1",
- id: "custom-css",
- description: "Shows how to add custom css",
- },
- modLoader
- );
- }
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Add custom CSS",
+ version: "1",
+ id: "custom-css",
+ description: "Shows how to add custom css",
+};
- init() {
- // Notice that, since the UI is scaled dynamically, every pixel value
- // should be wrapped in '$scaled()' (see below)
+class Mod extends shapez.Mod {
+ init() {
+ // Notice that, since the UI is scaled dynamically, every pixel value
+ // should be wrapped in '$scaled()' (see below)
- this.modInterface.registerCss(`
+ this.modInterface.registerCss(`
* {
font-family: "Comic Sans", "Comic Sans MS", "ComicSans", Tahoma !important;
}
@@ -39,9 +32,8 @@ registerMod(() => {
border: $scaled(5px) solid #000 !important;
}
`);
- }
- };
-});
+ }
+}
const RESOURCES = {
"cat.png":
diff --git a/mod_examples/custom_drawing.js b/mod_examples/custom_drawing.js
new file mode 100644
index 00000000..23158911
--- /dev/null
+++ b/mod_examples/custom_drawing.js
@@ -0,0 +1,61 @@
+/**
+ * Shows how to draw overlays by visualizing which item processors are working
+ */
+
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: custom drawing",
+ version: "1",
+ id: "base",
+ description: "Displays an indicator on every item processing building when its working",
+};
+
+class ItemProcessorStatusGameSystem extends shapez.GameSystem {
+ drawChunk(parameters, chunk) {
+ const contents = chunk.containedEntitiesByLayer.regular;
+ for (let i = 0; i < contents.length; ++i) {
+ const entity = contents[i];
+ const processorComp = entity.components.ItemProcessor;
+ if (!processorComp) {
+ 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 = processorComp.ongoingCharges.length === 0 ? "#aaa" : "#53cf47";
+ context.strokeStyle = "#000";
+ context.lineWidth = 1;
+
+ context.beginCircle(center.x + 5, center.y + 5, 4);
+ context.fill();
+ context.stroke();
+ }
+ }
+ }
+}
+
+class Mod extends shapez.Mod {
+ init() {
+ // Register our game system
+ this.modInterface.registerGameSystem({
+ id: "item_processor_status",
+ systemClass: ItemProcessorStatusGameSystem,
+
+ // Specify at which point the update method will be called,
+ // in this case directly before the belt system. You can use
+ // before: "end" to make it the last system
+ before: "belt",
+
+ // Specify where our drawChunk method should be called, check out
+ // map_chunk_view
+ drawHooks: ["staticAfter"],
+ });
+ }
+}
diff --git a/mod_examples/custom_sub_shapes.js b/mod_examples/custom_sub_shapes.js
index 0ed47aec..e56a528e 100644
--- a/mod_examples/custom_sub_shapes.js
+++ b/mod_examples/custom_sub_shapes.js
@@ -1,53 +1,46 @@
/**
* This shows how to add custom sub shapes
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Custom Sub Shapes",
- version: "1",
- id: "custom-sub-shapes",
- description: "Shows how to add custom sub shapes",
- },
- modLoader
- );
- }
- init() {
- // Add a new type of sub shape ("Line", short code "L")
- this.modInterface.registerSubShapeType({
- id: "line",
- shortCode: "L",
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Custom Sub Shapes",
+ version: "1",
+ id: "custom-sub-shapes",
+ description: "Shows how to add custom sub shapes",
+};
- // Make it spawn on the map
- weightComputation: distanceToOriginInChunks =>
- Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
+class Mod extends shapez.Mod {
+ init() {
+ // Add a new type of sub shape ("Line", short code "L")
+ this.modInterface.registerSubShapeType({
+ id: "line",
+ shortCode: "L",
- // This defines how to draw it
- 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();
- },
- });
+ // Make it spawn on the map
+ weightComputation: distanceToOriginInChunks =>
+ Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
- // Modify the goal of the first level to add our goal
- this.signals.modifyLevelDefinitions.add(definitions => {
- definitions[0].shape = "LuLuLuLu";
- });
- }
- };
-});
+ // This defines how to draw it
+ 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 to add our goal
+ this.signals.modifyLevelDefinitions.add(definitions => {
+ definitions[0].shape = "LuLuLuLu";
+ });
+ }
+}
diff --git a/mod_examples/custom_theme.js b/mod_examples/custom_theme.js
index 328630a5..46557721 100644
--- a/mod_examples/custom_theme.js
+++ b/mod_examples/custom_theme.js
@@ -1,32 +1,24 @@
/**
* This example shows how to add a new theme to the game
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Custom Game Theme",
- version: "1",
- id: "custom-theme",
- description: "Shows how to add a custom game theme",
- },
- modLoader
- );
- }
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Custom Game Theme",
+ version: "1",
+ id: "custom-theme",
+ description: "Shows how to add a custom game theme",
+};
- init() {
- this.modInterface.registerGameTheme({
- id: "my-theme",
- name: "My fancy theme",
- theme: RESOURCES["my-theme.json"],
- });
- }
- };
-});
+class Mod extends shapez.Mod {
+ init() {
+ this.modInterface.registerGameTheme({
+ id: "my-theme",
+ name: "My fancy theme",
+ theme: RESOURCES["my-theme.json"],
+ });
+ }
+}
const RESOURCES = {
"my-theme.json": {
diff --git a/mod_examples/modify_existing_building.js b/mod_examples/modify_existing_building.js
index cf8452ae..97ce461a 100644
--- a/mod_examples/modify_existing_building.js
+++ b/mod_examples/modify_existing_building.js
@@ -1,36 +1,29 @@
/**
* This shows how to modify an existing building
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Modify existing building",
- version: "1",
- id: "modify-existing-building",
- description: "Shows how to modify an existing building",
- },
- modLoader
- );
- }
- init() {
- // Make Rotator always unlocked
- this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getIsUnlocked", function () {
- return true;
- });
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Modify existing building",
+ version: "1",
+ id: "modify-existing-building",
+ description: "Shows how to modify an existing building",
+};
- // Add some custom stats to the info panel when selecting the building
- this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getAdditionalStatistics", function (
- root,
- variant
- ) {
- return [["Awesomeness", 5]];
- });
- }
- };
-});
+class Mod extends shapez.Mod {
+ init() {
+ // Make Rotator always unlocked
+ this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getIsUnlocked", function () {
+ return true;
+ });
+
+ // Add some custom stats to the info panel when selecting the building
+ this.modInterface.replaceMethod(shapez.MetaRotaterBuilding, "getAdditionalStatistics", function (
+ root,
+ variant
+ ) {
+ return [["Awesomeness", 5]];
+ });
+ }
+}
diff --git a/mod_examples/modify_theme.js b/mod_examples/modify_theme.js
index 6f7f53ff..8a931fd7 100644
--- a/mod_examples/modify_theme.js
+++ b/mod_examples/modify_theme.js
@@ -2,29 +2,21 @@
* This example shows how to modify the builtin themes. If you want to create your own theme,
* be sure to check out the "custom_theme" example
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Modify Builtin Themes",
- version: "1",
- id: "modify-theme",
- description: "Shows how to modify builtin themes",
- },
- modLoader
- );
- }
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Modify Builtin Themes",
+ version: "1",
+ id: "modify-theme",
+ description: "Shows how to modify builtin themes",
+};
- init() {
- shapez.THEMES.light.map.background = "#eee";
- shapez.THEMES.light.items.outline = "#000";
+class Mod extends shapez.Mod {
+ init() {
+ shapez.THEMES.light.map.background = "#eee";
+ shapez.THEMES.light.items.outline = "#000";
- shapez.THEMES.dark.map.background = "#245";
- shapez.THEMES.dark.items.outline = "#fff";
- }
- };
-});
+ shapez.THEMES.dark.map.background = "#245";
+ shapez.THEMES.dark.items.outline = "#fff";
+ }
+}
diff --git a/mod_examples/replace_builtin_sprites.js b/mod_examples/replace_builtin_sprites.js
index a6c5d53d..bed831de 100644
--- a/mod_examples/replace_builtin_sprites.js
+++ b/mod_examples/replace_builtin_sprites.js
@@ -2,34 +2,24 @@
* This example shows how to replace builtin sprites, in this case
* the color sprites
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Replace builtin sprites",
- version: "1",
- id: "replace-builtin-sprites",
- description: "Shows how to replace builtin sprites",
- },
- modLoader
- );
- }
- init() {
- // Replace a builtin sprite
- ["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
- this.modInterface.registerSprite(
- "sprites/colors/" + color + ".png",
- RESOURCES[color + ".png"]
- );
- });
- }
- };
-});
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Replace builtin sprites",
+ version: "1",
+ id: "replace-builtin-sprites",
+ description: "Shows how to replace builtin sprites",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Replace a builtin sprite
+ ["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
+ this.modInterface.registerSprite("sprites/colors/" + color + ".png", RESOURCES[color + ".png"]);
+ });
+ }
+}
////////////////////////////////////////////////////////////////////////
diff --git a/mod_examples/translations.js b/mod_examples/translations.js
index 7763ee16..b2471ba3 100644
--- a/mod_examples/translations.js
+++ b/mod_examples/translations.js
@@ -1,71 +1,64 @@
/**
* Shows to add new translations
*/
-registerMod(() => {
- return class ModImpl extends shapez.Mod {
- constructor(app, modLoader) {
- super(
- app,
- {
- website: "https://tobspr.io",
- author: "tobspr",
- name: "Mod Example: Translations",
- version: "1",
- id: "translations",
- description: "Shows how to add and modify translations",
- },
- modLoader
- );
- }
- init() {
- // Replace an existing translation in the english language
- this.modInterface.registerTranslations("en", {
- ingame: {
- interactiveTutorial: {
- title: "Hello",
- hints: {
- "1_1_extractor": "World!",
- },
+const METADATA = {
+ website: "https://tobspr.io",
+ author: "tobspr",
+ name: "Mod Example: Translations",
+ version: "1",
+ id: "translations",
+ description: "Shows how to add and modify translations",
+};
+
+class Mod extends shapez.Mod {
+ init() {
+ // Replace an existing translation in the english language
+ this.modInterface.registerTranslations("en", {
+ ingame: {
+ interactiveTutorial: {
+ title: "Hello",
+ hints: {
+ "1_1_extractor": "World!",
},
},
- });
+ },
+ });
- // Replace an existing translation in german
- this.modInterface.registerTranslations("de", {
- ingame: {
- interactiveTutorial: {
- title: "Hallo",
- hints: {
- "1_1_extractor": "Welt!",
- },
+ // Replace an existing translation in german
+ this.modInterface.registerTranslations("de", {
+ ingame: {
+ interactiveTutorial: {
+ title: "Hallo",
+ hints: {
+ "1_1_extractor": "Welt!",
},
},
- });
+ },
+ });
- // Add an entirely new translation which is localized in german and english
- this.modInterface.registerTranslations("en", {
- mods: {
- mymod: {
- test: "Test Translation",
- },
+ // Add an entirely new translation which is localized in german and english
+ this.modInterface.registerTranslations("en", {
+ mods: {
+ mymod: {
+ test: "Test Translation",
},
- });
- this.modInterface.registerTranslations("de", {
- mods: {
- mymod: {
- test: "Test Übersetzung",
- },
+ },
+ });
+ this.modInterface.registerTranslations("de", {
+ mods: {
+ mymod: {
+ test: "Test Übersetzung",
},
- });
+ },
+ });
- // Show a dialog in the main menu
- this.signals.stateEntered.add(state => {
- if (state instanceof shapez.MainMenuState) {
- // Will show differently based on the selected language
- this.dialogs.showInfo("My translation", shapez.T.mods.mymod.test);
- }
- });
- }
- };
-});
+ // Show a dialog in the main menu
+ this.signals.stateEntered.add(state => {
+ if (state instanceof shapez.MainMenuState) {
+ // Will show differently based on the selected language
+ this.dialogs.showInfo("My translation", shapez.T.mods.mymod.test);
+ }
+ });
+ }
+}
diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js
index 453c26f1..12d9c741 100644
--- a/src/js/game/shape_definition.js
+++ b/src/js/game/shape_definition.js
@@ -384,75 +384,79 @@ export class ShapeDefinition extends BasicSerializableObject {
context.strokeStyle = THEME.items.outline;
context.lineWidth = THEME.items.outlineWidth;
- switch (subShape) {
- case enumSubShape.rect: {
- context.beginPath();
- const dims = quadrantSize * layerScale;
- context.rect(-quadrantHalfSize, quadrantHalfSize - dims, dims, dims);
+ if (MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]) {
+ MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]({
+ context,
+ layerScale,
+ quadrantSize,
+ });
+ } else {
+ switch (subShape) {
+ case enumSubShape.rect: {
+ context.beginPath();
+ const dims = quadrantSize * layerScale;
+ context.rect(-quadrantHalfSize, quadrantHalfSize - dims, dims, dims);
+ context.fill();
+ context.stroke();
+ break;
+ }
+ case enumSubShape.star: {
+ context.beginPath();
+ const dims = quadrantSize * layerScale;
- break;
- }
- case enumSubShape.star: {
- context.beginPath();
- const dims = quadrantSize * layerScale;
+ let originX = -quadrantHalfSize;
+ let originY = quadrantHalfSize - dims;
- let originX = -quadrantHalfSize;
- let originY = quadrantHalfSize - dims;
+ const moveInwards = dims * 0.4;
+ context.moveTo(originX, originY + moveInwards);
+ context.lineTo(originX + dims, originY);
+ context.lineTo(originX + dims - moveInwards, originY + dims);
+ context.lineTo(originX, originY + dims);
+ context.closePath();
+ context.fill();
+ context.stroke();
+ break;
+ }
- const moveInwards = dims * 0.4;
- context.moveTo(originX, originY + moveInwards);
- context.lineTo(originX + dims, originY);
- context.lineTo(originX + dims - moveInwards, originY + dims);
- context.lineTo(originX, originY + dims);
- context.closePath();
- break;
- }
+ case enumSubShape.windmill: {
+ context.beginPath();
+ const dims = quadrantSize * layerScale;
- case enumSubShape.windmill: {
- context.beginPath();
- const dims = quadrantSize * layerScale;
+ let originX = -quadrantHalfSize;
+ let originY = quadrantHalfSize - dims;
+ const moveInwards = dims * 0.4;
+ context.moveTo(originX, originY + moveInwards);
+ context.lineTo(originX + dims, originY);
+ context.lineTo(originX + dims, originY + dims);
+ context.lineTo(originX, originY + dims);
+ context.closePath();
+ context.fill();
+ context.stroke();
+ break;
+ }
- let originX = -quadrantHalfSize;
- let originY = quadrantHalfSize - dims;
- const moveInwards = dims * 0.4;
- context.moveTo(originX, originY + moveInwards);
- context.lineTo(originX + dims, originY);
- context.lineTo(originX + dims, originY + dims);
- context.lineTo(originX, originY + dims);
- context.closePath();
- break;
- }
+ case enumSubShape.circle: {
+ context.beginPath();
+ context.moveTo(-quadrantHalfSize, quadrantHalfSize);
+ context.arc(
+ -quadrantHalfSize,
+ quadrantHalfSize,
+ quadrantSize * layerScale,
+ -Math.PI * 0.5,
+ 0
+ );
+ context.closePath();
+ context.fill();
+ context.stroke();
+ break;
+ }
- case enumSubShape.circle: {
- context.beginPath();
- context.moveTo(-quadrantHalfSize, quadrantHalfSize);
- context.arc(
- -quadrantHalfSize,
- quadrantHalfSize,
- quadrantSize * layerScale,
- -Math.PI * 0.5,
- 0
- );
- context.closePath();
- break;
- }
-
- default: {
- if (MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]) {
- MODS_ADDITIONAL_SUB_SHAPE_DRAWERS[subShape]({
- context,
- layerScale,
- quadrantSize,
- });
- } else {
+ default: {
throw new Error("Unkown sub shape: " + subShape);
}
}
}
- context.fill();
- context.stroke();
-
context.rotate(-rotation);
context.translate(-centerQuadrantX, -centerQuadrantY);
}
diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts
index 042b549d..f18d1d61 100644
--- a/src/js/globals.d.ts
+++ b/src/js/globals.d.ts
@@ -26,8 +26,6 @@ declare const shapez: any;
declare const ipcRenderer: any;
-declare const registerMod: any;
-
// Polyfills
declare interface String {
replaceAll(search: string, replacement: string): string;
@@ -98,7 +96,7 @@ declare interface Window {
cpmstarAPI: any;
// Mods
- registerMod: any;
+ $shapez_registerMod: any;
anyModLoaded: any;
shapez: any;
diff --git a/src/js/mods/mod.js b/src/js/mods/mod.js
index 0bfe3693..b130370d 100644
--- a/src/js/mods/mod.js
+++ b/src/js/mods/mod.js
@@ -7,28 +7,22 @@ import { MOD_SIGNALS } from "./mod_signals";
export class Mod {
/**
- *
* @param {Application} app
- * @param {object} metadata
- * @param {string} metadata.name
- * @param {string} metadata.version
- * @param {string} metadata.author
- * @param {string} metadata.website
- * @param {string} metadata.description
- * @param {string} metadata.id
- *
* @param {ModLoader} modLoader
+ * @param {import("./modloader").ModMetadata} meta
*/
- constructor(app, metadata, modLoader) {
+ constructor(app, modLoader, meta) {
this.app = app;
- this.metadata = metadata;
this.modLoader = modLoader;
+ this.metadata = meta;
this.signals = MOD_SIGNALS;
this.modInterface = modLoader.modInterface;
}
- init() {}
+ init() {
+ // to be overridden
+ }
get dialogs() {
return this.modInterface.dialogs;
diff --git a/src/js/mods/mod_interface.js b/src/js/mods/mod_interface.js
index 697d3e00..ccdde710 100644
--- a/src/js/mods/mod_interface.js
+++ b/src/js/mods/mod_interface.js
@@ -229,19 +229,21 @@ export class ModInterface {
T.buildings[id] = {};
metaClass.getAllVariantCombinations().forEach(combination => {
- const actualVariant = combination.variant || defaultBuildingVariant;
- registerBuildingVariant(id, metaClass, actualVariant, combination.rotationVariant || 0);
+ const variant = combination.variant || defaultBuildingVariant;
+ const rotationVariant = combination.rotationVariant || 0;
+
+ const buildingIdentifier = id + (variant === defaultBuildingVariant ? "" : "-" + variant);
+
+ const uniqueTypeId = buildingIdentifier + (rotationVariant === 0 ? "" : "-" + rotationVariant);
+ registerBuildingVariant(uniqueTypeId, metaClass, variant, rotationVariant);
gBuildingVariants[id].metaInstance = metaInstance;
- T.buildings[id][actualVariant] = {
+ T.buildings[id][variant] = {
name: combination.name || "Name",
description: combination.description || "Description",
};
- const buildingIdentifier =
- id + (actualVariant === defaultBuildingVariant ? "" : "-" + actualVariant);
-
if (combination.regularImageBase64) {
this.registerSprite(
"sprites/buildings/" + buildingIdentifier + ".png",
@@ -256,7 +258,7 @@ export class ModInterface {
);
}
if (combination.tutorialImageBase64) {
- this.setBuildingTutorialImage(id, actualVariant, combination.tutorialImageBase64);
+ this.setBuildingTutorialImage(id, variant, combination.tutorialImageBase64);
}
});
diff --git a/src/js/mods/modloader.js b/src/js/mods/modloader.js
index 301b5ec7..1ef26f7a 100644
--- a/src/js/mods/modloader.js
+++ b/src/js/mods/modloader.js
@@ -9,6 +9,17 @@ import { MOD_SIGNALS } from "./mod_signals";
const LOG = createLogger("mods");
+/**
+ * @typedef {{
+ * name: string;
+ * version: string;
+ * author: string;
+ * website: string;
+ * description: string;
+ * id: string;
+ * }} ModMetadata
+ */
+
export class ModLoader {
constructor() {
LOG.log("modloader created");
@@ -23,7 +34,7 @@ export class ModLoader {
this.modInterface = new ModInterface(this);
- /** @type {((Object) => (new (Application, ModLoader) => Mod))[]} */
+ /** @type {({ meta: ModMetadata, modClass: typeof Mod})[]} */
this.modLoadQueue = [];
this.initialized = false;
@@ -73,10 +84,6 @@ export class ModLoader {
this.exposeExports();
- window.registerMod = mod => {
- this.modLoadQueue.push(mod);
- };
-
if (G_IS_STANDALONE || G_IS_DEV) {
try {
let mods = [];
@@ -101,34 +108,57 @@ export class ModLoader {
mods.push(await response.text());
}
+ window.$shapez_registerMod = (modClass, meta) => {
+ if (this.modLoadQueue.some(entry => entry.meta.id === meta.id)) {
+ console.warn(
+ "Not registering mod",
+ meta,
+ "since a mod with the same id is already loaded"
+ );
+ return;
+ }
+ this.modLoadQueue.push({
+ modClass,
+ meta,
+ });
+ };
+
mods.forEach(modCode => {
+ modCode += `
+ if (typeof Mod !== 'undefined') {
+ if (typeof METADATA !== 'object') {
+ throw new Error("No METADATA variable found");
+ }
+ window.$shapez_registerMod(Mod, METADATA);
+ }
+ `;
try {
const func = new Function(modCode);
func();
} catch (ex) {
console.error(ex);
- alert("Failed to parse mod (launch with --dev for more info): " + ex);
+ alert("Failed to parse mod (launch with --dev for more info): \n\n" + ex);
}
});
+
+ delete window.$shapez_registerMod;
} catch (ex) {
- alert("Failed to load mods (launch with --dev for more info): " + ex);
+ alert("Failed to load mods (launch with --dev for more info): \n\n" + ex);
}
}
this.initialized = true;
- this.modLoadQueue.forEach(modClass => {
+ this.modLoadQueue.forEach(({ modClass, meta }) => {
try {
- const mod = new (modClass())(this.app, this);
+ const mod = new modClass(this.app, this, meta);
mod.init();
this.mods.push(mod);
} catch (ex) {
console.error(ex);
- alert("Failed to initialize mods (launch with --dev for more info): " + ex);
+ alert("Failed to initialize mods (launch with --dev for more info): \n\n" + ex);
}
});
this.modLoadQueue = [];
-
- delete window.registerMod;
}
}