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_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/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/modloader.js b/src/js/mods/modloader.js
index 301b5ec7..9f45e401 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,8 +84,11 @@ export class ModLoader {
this.exposeExports();
- window.registerMod = mod => {
- this.modLoadQueue.push(mod);
+ window.$shapez_registerMod = (modClass, meta) => {
+ this.modLoadQueue.push({
+ modClass,
+ meta,
+ });
};
if (G_IS_STANDALONE || G_IS_DEV) {
@@ -103,6 +117,7 @@ export class ModLoader {
mods.forEach(modCode => {
try {
+ modCode += "\n;window.$shapez_registerMod(Mod, METADATA);";
const func = new Function(modCode);
func();
} catch (ex) {
@@ -116,9 +131,9 @@ export class ModLoader {
}
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) {
@@ -128,7 +143,7 @@ export class ModLoader {
});
this.modLoadQueue = [];
- delete window.registerMod;
+ delete window.$shapez_registerMod;
}
}