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; } }