Mod Support - 1.5.0 Update (#1361)
* initial modloader draft * modloader features * Refactor mods to use signals * Add support for modifying and registering new transltions * Minor adjustments * Support for string building ids for mods * Initial support for adding new buildings * Refactor how mods are loaded to resolve circular dependencies and prepare for future mod loading * Lazy Load mods to make sure all dependencies are loaded * Expose all exported members automatically to mods * Fix duplicate exports * Allow loading mods from standalone * update changelog * Fix mods folder incorrect path * Fix modloading in standalone * Fix sprites not getting replaced, update demo mod * Load dev mod via raw loader * Improve mod developing so mods are directly ready to be deployed, load mods from local file server * Proper mods ui * Allow mods to register game systems and draw stuff * Change mods path * Fix sprites not loading * Minor adjustments, closes #1333 * Add support for loading atlases via mods * Add support for loading mods from external sources in DEV * Add confirmation when loading mods * Fix circular dependency * Minor Keybindings refactor, add support for keybindings to mods, add support for dialogs to mods * Add some mod signals * refactor game loading states * Make shapez exports global * Start to make mods safer * Refactor file system electron event handling * Properly isolate electron renderer process * Update to latest electron * Show errors when loading mods * Update confirm dialgo * Minor restructure, start to add mod examples * Allow adding custom themesw * Add more examples and allow defining custom item processor operations * Add interface to register new buildings * Fixed typescript type errors (#1335) * Refactor building registry, make it easier for mods to add new buildings * Allow overriding existing methods * Add more examples and more features * More mod examples * Make mod loading simpler * Add example how to add custom drawings * Remove unused code * Minor modloader adjustments * Support for rotation variants in mods (was broken previously) * Allow mods to replace builtin sub shapes * Add helper methods to extend classes * Fix menu bar on mac os * Remember window state * Add support for paste signals * Add example how to add custom components and systems * Support for mod settings * Add example for adding a new item type * Update class extensions * Minor adjustments * Fix typo * Add notification blocks mod example * Add small tutorial * Update readme * Add better instructions * Update JSDoc for Replacing Methods (#1336) * upgraded types for overriding methods * updated comments Co-authored-by: Edward Badel <you@example.com> * Direction lock now indicates when there is a building inbetween * Fix mod examples * Fix linter error * Game state register (#1341) * Added a gamestate register helper Added a gamestate register helper * Update mod_interface.js * export build options * Fix runBeforeMethod and runAfterMethod * Minor game system code cleanup * Belt path drawing optimization * Fix belt path optimization * Belt drawing improvements, again * Do not render belts in statics disabled view * Allow external URL to load more than one mod (#1337) * Allow external URL to load more than one mod Instead of loading the text returned from the remote server, load a JSON object with a `mods` field, containing strings of all the mods. This lets us work on more than one mod at a time or without separate repos. This will break tooling such as `create-shapezio-mod` though. * Update modloader.js * Prettier fixes * Added link to create-shapezio-mod npm page (#1339) Added link to create-shapezio-mod npm page: https://www.npmjs.com/package/create-shapezio-mod * allow command line switch to load more than one mod (#1342) * Fixed class handle type (#1345) * Fixed class handle type * Fixed import game state * Minor adjustments * Refactor item acceptor to allow only single direction slots * Allow specifying minimumGameVersion * Add sandbox example * Replaced concatenated strings with template literals (#1347) * Mod improvements * Make wired pins component optional on the storage * Fix mod examples * Bind `this` for method overriding JSDoc (#1352) * fix entity debugger reaching HTML elements (#1353) * Store mods in savegame and show warning when it differs * Closes #1357 * Fix All Shapez Exports Being Const (#1358) * Allowed setting of variables inside webpack modules * remove console log * Fix stringification of things inside of eval Co-authored-by: Edward Badel <you@example.com> * Fix building placer intersection warning * Add example for storing data in the savegame * Fix double painter bug (#1349) * Add example on how to extend builtin buildings * update readme * Disable steam achievements when playing with mods * Update translations Co-authored-by: Thomas (DJ1TJOO) <44841260+DJ1TJOO@users.noreply.github.com> Co-authored-by: Bagel03 <70449196+Bagel03@users.noreply.github.com> Co-authored-by: Edward Badel <you@example.com> Co-authored-by: Emerald Block <69981203+EmeraldBlock@users.noreply.github.com> Co-authored-by: saile515 <63782477+saile515@users.noreply.github.com> Co-authored-by: Sense101 <67970865+Sense101@users.noreply.github.com>pull/1363/head
parent
a7a2aad2b6
commit
c41aaa1fc5
@ -0,0 +1 @@
|
||||
mods/*.js
|
@ -0,0 +1,6 @@
|
||||
Here you can place mods. Every mod should be a single file ending with ".js".
|
||||
|
||||
--- WARNING ---
|
||||
Mods can potentially access to your filesystem.
|
||||
Please only install mods from trusted sources and developers.
|
||||
--- WARNING ---
|
@ -0,0 +1,7 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("ipcRenderer", {
|
||||
invoke: ipcRenderer.invoke.bind(ipcRenderer),
|
||||
on: ipcRenderer.on.bind(ipcRenderer),
|
||||
send: ipcRenderer.send.bind(ipcRenderer),
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
module.exports = function (source, map) {
|
||||
return source + `\nexport let $s=(n,v)=>eval(n+"=v")`;
|
||||
};
|
@ -0,0 +1,59 @@
|
||||
# shapez.io Modding
|
||||
|
||||
## General Instructions
|
||||
|
||||
Currently there are two options to develop mods for shapez.io:
|
||||
|
||||
1. Writing single file mods, which doesn't require any additional tools and can be loaded directly in the game
|
||||
2. Using the [create-shapezio-mod](https://www.npmjs.com/package/create-shapezio-mod) package. This package is still in development but allows you to pack multiple files and images into a single mod file, so you don't have to base64 encode your images etc.
|
||||
|
||||
Since the `create-shapezio-mod` package is still in development, the current recommended way is to write single file mods, which I'll explain now.
|
||||
|
||||
## Mod Developer Discord
|
||||
|
||||
A great place to get help with mod development is the official [shapez.io modloader discord](https://discord.gg/xq5v8uyMue).
|
||||
|
||||
## Setting up your development environment
|
||||
|
||||
The simplest way of developing mods is by just creating a `mymod.js` file and putting it in the `mods/` folder of the standalone (You can find the `mods/` folder by clicking "Open Mods Folder" in the shapez.io Standalone, be sure to select the 1.5.0-modloader branch on Steam).
|
||||
|
||||
You can then add `--dev` to the launch options on Steam. This adds an application menu where you can click "Restart" to reload your mod, and will also show the developer console where you can see any potential errors.
|
||||
|
||||
## Getting started
|
||||
|
||||
To get into shapez.io modding, I highly recommend checking out all of the examples in this folder. Here's a list of examples and what features of the modloader they show:
|
||||
|
||||
| Example | Description | Demonstrates |
|
||||
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| [base.js](base.js) | The most basic mod | Base structure of a mod |
|
||||
| [class_extensions.js](class_extensions.js) | Shows how to extend multiple methods of one class at once, useful for overriding a lot of methods | Overriding and extending builtin methods |
|
||||
| [custom_css.js](custom_css.js) | Modifies the Main Menu State look | Modifying the UI styles with CSS |
|
||||
| [replace_builtin_sprites.js](replace_builtin_sprites.js) | Replaces all color sprites with icons | Replacing builtin sprites |
|
||||
| [translations.js](translations.js) | Shows how to replace and add new translations in multiple languages | Adding and replacing translations |
|
||||
| [add_building_basic.js](add_building_basic.js) | Shows how to add a new building | Registering a new building |
|
||||
| [add_building_flipper.js](add_building_flipper.js) | Adds a "flipper" building which mirrors shapes from top to bottom | Registering a new building, Adding a custom shape and item processing operation (flip) |
|
||||
| [custom_drawing.js](custom_drawing.js) | Displays a a small indicator on every item processing building whether it is currently working | Adding a new GameSystem and drawing overlays |
|
||||
| [custom_keybinding.js](custom_keybinding.js) | Adds a new customizable ingame keybinding (Shift+F) | Adding a new keybinding |
|
||||
| [custom_sub_shapes.js](custom_sub_shapes.js) | Adds a new type of sub-shape (Line) | Adding a new sub shape and drawing it, making it spawn on the map, modifying the builtin levels |
|
||||
| [modify_theme.js](modify_theme.js) | Modifies the default game themes | Modifying the builtin themes |
|
||||
| [custom_theme.js](custom_theme.js) | Adds a new UI and map theme | Adding a new game theme |
|
||||
| [mod_settings.js](mod_settings.js) | Shows a dialog counting how often the mod has been launched | Reading and storing mod settings |
|
||||
| [storing_data_in_savegame.js](storing_data_in_savegame.js) | Shows how to store custom (structured) data in the savegame | Storing custom data in savegame |
|
||||
| [modify_existing_building.js](modify_existing_building.js) | Makes the rotator building always unlocked and adds a new statistic to the building panel | Modifying a builtin building, replacing builtin methods |
|
||||
| [modify_ui.js](modify_ui.js) | Shows how to add custom IU elements to builtin game states (the Main Menu in this case) | Extending builtin UI states, Adding CSS |
|
||||
| [pasting.js](pasting.js) | Shows a dialog when pasting text in the game | Listening to paste events |
|
||||
| [sandbox.js](sandbox.js) | Makes blueprints free and always unlocked | Overriding builtin methods |
|
||||
|
||||
### Advanced Examples
|
||||
|
||||
| Example | Description | Demonstrates |
|
||||
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [notification_blocks.js](notification_blocks.js) | Adds a notification block building, which shows a user defined notification when receiving a truthy signal | Adding a new Component, Adding a new GameSystem, Working with wire networks, Adding a new building, Adding a new HUD part, Using Input Dialogs, Adding Translations |
|
||||
| [usage_statistics.js](usage_statistics.js) | Displays a percentage on every building showing its utilization | Adding a new component, Adding a new GameSystem, Drawing within a GameSystem, Modifying builtin buildings, Adding custom game logic |
|
||||
| [new_item_type.js](new_item_type.js) | Adds a new type of items to the map (fluids) | Adding a new item type, modifying map generation |
|
||||
| [buildings_have_cost.js](buildings_have_cost.js) | Adds a new currency, and belts cost 1 of that currency | Extending and replacing builtin methods, Adding CSS and custom sprites |
|
||||
| [mirrored_cutter.js](mirrored_cutter.js) | Adds a mirorred variant of the cutter | Adding a new variant to existing buildings |
|
||||
|
||||
### Creating new sprites
|
||||
|
||||
If you want to add new buildings and create sprites for them, you can download the original Photoshop PSD files here: https://static.shapez.io/building-psds.zip
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,20 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Base",
|
||||
version: "1",
|
||||
id: "base",
|
||||
description: "The most basic mod",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
// Start the modding here
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,32 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Class Extensions",
|
||||
version: "1",
|
||||
id: "class-extensions",
|
||||
description: "Shows how to extend builtin classes",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
};
|
||||
|
||||
const BeltExtension = ({ $super, $old }) => ({
|
||||
getShowWiresLayerPreview() {
|
||||
// Access the old method
|
||||
return !$old.getShowWiresLayerPreview();
|
||||
},
|
||||
|
||||
getIsReplaceable() {
|
||||
// Instead of super, use $super
|
||||
return $super.getIsReplaceable.call(this);
|
||||
},
|
||||
|
||||
getIsRemoveable() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
this.modInterface.extendClass(shapez.MetaBeltBuilding, BeltExtension);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,63 @@
|
||||
// @ts-nocheck
|
||||
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",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
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"],
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Custom Keybindings",
|
||||
version: "1",
|
||||
id: "base",
|
||||
description: "Shows how to add a new keybinding",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
// Register keybinding
|
||||
this.modInterface.registerIngameKeybinding({
|
||||
id: "demo_mod_binding",
|
||||
keyCode: shapez.keyToKeyCode("F"),
|
||||
translation: "Do something (always with SHIFT)",
|
||||
modifiers: {
|
||||
shift: true,
|
||||
},
|
||||
handler: root => {
|
||||
this.dialogs.showInfo("Mod Message", "It worked!");
|
||||
return shapez.STOP_PROPAGATION;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// @ts-nocheck
|
||||
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",
|
||||
minimumGameVersion: ">=1.5.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",
|
||||
|
||||
// Make it spawn on the map
|
||||
weightComputation: distanceToOriginInChunks =>
|
||||
Math.round(20 + Math.max(Math.min(distanceToOriginInChunks, 30), 0)),
|
||||
|
||||
// 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();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
},
|
||||
});
|
||||
|
||||
// Modify the goal of the first level to add our goal
|
||||
this.signals.modifyLevelDefinitions.add(definitions => {
|
||||
definitions[0].shape = "LuLuLuLu";
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// @ts-nocheck
|
||||
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",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
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": {
|
||||
map: {
|
||||
background: "#abc",
|
||||
grid: "#ccc",
|
||||
gridLineWidth: 1,
|
||||
|
||||
selectionOverlay: "rgba(74, 163, 223, 0.7)",
|
||||
selectionOutline: "rgba(74, 163, 223, 0.5)",
|
||||
selectionBackground: "rgba(74, 163, 223, 0.2)",
|
||||
|
||||
chunkBorders: "rgba(0, 30, 50, 0.03)",
|
||||
|
||||
directionLock: {
|
||||
regular: {
|
||||
color: "rgb(74, 237, 134)",
|
||||
background: "rgba(74, 237, 134, 0.2)",
|
||||
},
|
||||
wires: {
|
||||
color: "rgb(74, 237, 134)",
|
||||
background: "rgba(74, 237, 134, 0.2)",
|
||||
},
|
||||
error: {
|
||||
color: "rgb(255, 137, 137)",
|
||||
background: "rgba(255, 137, 137, 0.2)",
|
||||
},
|
||||
},
|
||||
|
||||
colorBlindPickerTile: "rgba(50, 50, 50, 0.4)",
|
||||
|
||||
resources: {
|
||||
shape: "#eaebec",
|
||||
red: "#ffbfc1",
|
||||
green: "#cbffc4",
|
||||
blue: "#bfdaff",
|
||||
},
|
||||
|
||||
chunkOverview: {
|
||||
empty: "#a6afbb",
|
||||
filled: "#c5ccd6",
|
||||
beltColor: "#777",
|
||||
},
|
||||
|
||||
wires: {
|
||||
overlayColor: "rgba(97, 161, 152, 0.75)",
|
||||
previewColor: "rgb(97, 161, 152, 0.4)",
|
||||
highlightColor: "rgba(72, 137, 255, 1)",
|
||||
},
|
||||
|
||||
connectedMiners: {
|
||||
overlay: "rgba(40, 50, 60, 0.5)",
|
||||
textColor: "#fff",
|
||||
textColorCapped: "#ef5072",
|
||||
background: "rgba(40, 50, 60, 0.8)",
|
||||
},
|
||||
|
||||
zone: {
|
||||
borderSolid: "rgba(23, 192, 255, 1)",
|
||||
outerColor: "rgba(240, 240, 255, 0.5)",
|
||||
},
|
||||
},
|
||||
|
||||
items: {
|
||||
outline: "#55575a",
|
||||
outlineWidth: 0.75,
|
||||
circleBackground: "rgba(40, 50, 65, 0.1)",
|
||||
},
|
||||
|
||||
shapeTooltip: {
|
||||
background: "#dee1ea",
|
||||
outline: "#54565e",
|
||||
},
|
||||
},
|
||||
};
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,32 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Mod Settings",
|
||||
version: "1",
|
||||
id: "mod-settings",
|
||||
description: "Shows how to add settings to your mod",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
settings: {
|
||||
timesLaunched: 0,
|
||||
},
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
// Increment the setting every time we launch the mod
|
||||
this.settings.timesLaunched++;
|
||||
this.saveSettings();
|
||||
|
||||
// Show a dialog in the main menu with the settings
|
||||
this.signals.stateEntered.add(state => {
|
||||
if (state instanceof shapez.MainMenuState) {
|
||||
this.dialogs.showInfo(
|
||||
"Welcome back",
|
||||
`You have launched this mod ${this.settings.timesLaunched} times`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// @ts-nocheck
|
||||
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",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
};
|
||||
|
||||
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]];
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// @ts-nocheck
|
||||
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",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Modify UI",
|
||||
version: "1",
|
||||
id: "modify-ui",
|
||||
description: "Shows how to modify a builtin game state, in this case the main menu",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
// 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(`
|
||||
#demo_mod_hello_world_element {
|
||||
position: absolute;
|
||||
top: calc(10px * var(--ui-scale));
|
||||
left: calc(10px * var(--ui-scale));
|
||||
color: red;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,23 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Pasting",
|
||||
version: "1",
|
||||
id: "pasting",
|
||||
description: "Shows how to properly receive paste events ingame",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
this.signals.gameInitialized.add(root => {
|
||||
root.gameState.inputReciever.paste.add(event => {
|
||||
event.preventDefault();
|
||||
|
||||
const data = event.clipboardData.getData("text");
|
||||
this.dialogs.showInfo("Pasted", `You pasted: '${data}'`);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,21 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Sandbox",
|
||||
version: "1",
|
||||
id: "sandbox",
|
||||
description: "Blueprints are always unlocked and cost no money, also all buildings are unlocked",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
this.modInterface.replaceMethod(shapez.Blueprint, "getCost", function () {
|
||||
return 0;
|
||||
});
|
||||
this.modInterface.replaceMethod(shapez.HubGoals, "isRewardUnlocked", function () {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Storing Data in Savegame",
|
||||
version: "1",
|
||||
id: "storing-savegame-data",
|
||||
description: "Shows how to add custom data to a savegame",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
};
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Option 1: For simple data
|
||||
this.signals.gameSerialized.add((root, data) => {
|
||||
data.modExtraData["storing-savegame-data"] = Math.random();
|
||||
});
|
||||
|
||||
this.signals.gameDeserialized.add((root, data) => {
|
||||
alert("The value stored in the savegame was: " + data.modExtraData["storing-savegame-data"]);
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Option 2: If you need a structured way of storing data
|
||||
|
||||
class SomeSerializableObject extends shapez.BasicSerializableObject {
|
||||
static getId() {
|
||||
return "SomeSerializableObject";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
return {
|
||||
someInt: shapez.types.int,
|
||||
someString: shapez.types.string,
|
||||
someVector: shapez.types.vector,
|
||||
|
||||
// this value is allowed to be null
|
||||
nullableInt: shapez.types.nullable(shapez.types.int),
|
||||
|
||||
// There is a lot more .. be sure to checkout src/js/savegame/serialization.js
|
||||
// You can have maps, classes, arrays etc..
|
||||
// And if you need something specific you can always ask in the modding discord.
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.someInt = 42;
|
||||
this.someString = "Hello World";
|
||||
this.someVector = new shapez.Vector(1, 2);
|
||||
|
||||
this.nullableInt = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Store our object in the global game root
|
||||
this.signals.gameInitialized.add(root => {
|
||||
root.myObject = new SomeSerializableObject();
|
||||
});
|
||||
|
||||
// Save it within the savegame
|
||||
this.signals.gameSerialized.add((root, data) => {
|
||||
data.modExtraData["storing-savegame-data-2"] = root.myObject.serialize();
|
||||
});
|
||||
|
||||
// Restore it when the savegame is loaded
|
||||
this.signals.gameDeserialized.add((root, data) => {
|
||||
const errorText = root.myObject.deserialize(data.modExtraData["storing-savegame-data-2"]);
|
||||
if (errorText) {
|
||||
alert("Mod failed to deserialize from savegame: " + errorText);
|
||||
}
|
||||
alert("The other value stored in the savegame (option 2) was " + root.myObject.someInt);
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Translations",
|
||||
version: "1",
|
||||
id: "translations",
|
||||
description: "Shows how to add and modify translations",
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
|
||||
// You can specify this parameter if savegames will still work
|
||||
// after your mod has been uninstalled
|
||||
doesNotAffectSavegame: true,
|
||||
};
|
||||
|
||||
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!",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 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",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
// @ts-nocheck
|
||||
const METADATA = {
|
||||
website: "https://tobspr.io",
|
||||
author: "tobspr",
|
||||
name: "Mod Example: Usage Statistics",
|
||||
version: "1",
|
||||
id: "usage-statistics",
|
||||
description:
|
||||
"Shows how to add a new component to the game, how to save additional data and how to add custom logic and drawings",
|
||||
|
||||
minimumGameVersion: ">=1.5.0",
|
||||
};
|
||||
|
||||
/**
|
||||
* Quick info on how this mod works:
|
||||
*
|
||||
* It tracks how many ticks a building was idle within X seconds to compute
|
||||
* the usage percentage.
|
||||
*
|
||||
* Every tick the logic checks if the building is idle, if so, it increases aggregatedIdleTime.
|
||||
* Once X seconds are over, the aggregatedIdleTime is copied to computedUsage which
|
||||
* is displayed on screen via the UsageStatisticsSystem
|
||||
*/
|
||||
|
||||
const MEASURE_INTERVAL_SECONDS = 5;
|
||||
|
||||
class UsageStatisticsComponent extends shapez.Component {
|
||||
static getId() {
|
||||
return "UsageStatistics";
|
||||
}
|
||||
|
||||
static getSchema() {
|
||||
// Here you define which properties should be saved to the savegame
|
||||
// and get automatically restored
|
||||
return {
|
||||
lastTimestamp: shapez.types.float,
|
||||
computedUsage: shapez.types.float,
|
||||
aggregatedIdleTime: shapez.types.float,
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.lastTimestamp = 0;
|
||||
this.computedUsage = 0;
|
||||
this.aggregatedIdleTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class UsageStatisticsSystem extends shapez.GameSystemWithFilter {
|
||||
constructor(root) {
|
||||
// By specifying the list of components, `this.allEntities` will only
|
||||
// contain entities which have *all* of the specified components
|
||||
super(root, [UsageStatisticsComponent, shapez.ItemProcessorComponent]);
|
||||
}
|
||||
|
||||
update() {
|
||||
const now = this.root.time.now();
|
||||
for (let i = 0; i < this.allEntities.length; ++i) {
|
||||
const entity = this.allEntities[i];
|
||||
|
||||
const processorComp = entity.components.ItemProcessor;
|
||||
const usageComp = entity.components.UsageStatistics;
|
||||
|
||||
if (now - usageComp.lastTimestamp > MEASURE_INTERVAL_SECONDS) {
|
||||
usageComp.computedUsage = shapez.clamp(
|
||||
1 - usageComp.aggregatedIdleTime / MEASURE_INTERVAL_SECONDS
|
||||
);
|
||||
usageComp.aggregatedIdleTime = 0;
|
||||
usageComp.lastTimestamp = now;
|
||||
}
|
||||
|
||||
if (processorComp.ongoingCharges.length === 0) {
|
||||
usageComp.aggregatedIdleTime += this.root.dynamicTickrate.deltaSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawChunk(parameters, chunk) {
|
||||
const contents = chunk.containedEntitiesByLayer.regular;
|
||||
for (let i = 0; i < contents.length; ++i) {
|
||||
const entity = contents[i];
|
||||
const usageComp = entity.components.UsageStatistics;
|
||||
if (!usageComp) {
|
||||
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)) {
|
||||
// Background badge
|
||||
context.fillStyle = "rgba(250, 250, 250, 0.8)";
|
||||
context.beginRoundedRect(center.x - 10, center.y + 3, 20, 8, 2);
|
||||
|
||||
context.fill();
|
||||
|
||||
// Text
|
||||
const usage = usageComp.computedUsage * 100.0;
|
||||
if (usage > 99.99) {
|
||||
context.fillStyle = "green";
|
||||
} else if (usage > 70) {
|
||||
context.fillStyle = "orange";
|
||||
} else {
|
||||
context.fillStyle = "red";
|
||||
}
|
||||
|
||||
context.textAlign = "center";
|
||||
context.font = "7px GameFont";
|
||||
context.fillText(Math.round(usage) + "%", center.x, center.y + 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Mod extends shapez.Mod {
|
||||
init() {
|
||||
// Register the component
|
||||
this.modInterface.registerComponent(UsageStatisticsComponent);
|
||||
|
||||
// Add our new component to all item processor buildings so we can see how many items it processed.
|
||||
// You can also inspect the entity with F8
|
||||
const buildings = [
|
||||
shapez.MetaBalancerBuilding,
|
||||
shapez.MetaCutterBuilding,
|
||||
shapez.MetaRotaterBuilding,
|
||||
shapez.MetaStackerBuilding,
|
||||
shapez.MetaMixerBuilding,
|
||||
shapez.MetaPainterBuilding,
|
||||
];
|
||||
|
||||
buildings.forEach(metaClass => {
|
||||
this.modInterface.runAfterMethod(metaClass, "setupEntityComponents", function (entity) {
|
||||
entity.addComponent(new UsageStatisticsComponent());
|
||||
});
|
||||
});
|
||||
|
||||
// Register our game system so we can update and draw stuff
|
||||
this.modInterface.registerGameSystem({
|
||||
id: "demo_mod",
|
||||
systemClass: UsageStatisticsSystem,
|
||||
before: "belt",
|
||||
drawHooks: ["staticAfter"],
|
||||
});
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.1 KiB |
@ -0,0 +1,141 @@
|
||||
#state_ModsState {
|
||||
.mainContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
> .headerBar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
|
||||
> h1 {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.openModsFolder {
|
||||
background-color: $modsColor;
|
||||
}
|
||||
}
|
||||
|
||||
.noModSupport {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.steamLink {
|
||||
@include S(height, 50px);
|
||||
@include S(width, 220px);
|
||||
background: #171a23 center center / contain no-repeat;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
text-indent: -999em;
|
||||
cursor: pointer;
|
||||
@include S(margin-top, 30px);
|
||||
pointer-events: all;
|
||||
transition: all 0.12s ease-in;
|
||||
transition-property: opacity, transform;
|
||||
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modsStats {
|
||||
@include PlainText;
|
||||
color: $accentColorDark;
|
||||
|
||||
&.noMods {
|
||||
@include S(width, 400px);
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@include Text;
|
||||
@include S(margin-top, 100px);
|
||||
color: lighten($accentColorDark, 15);
|
||||
|
||||
button {
|
||||
@include S(margin-top, 10px);
|
||||
@include S(padding, 10px, 20px);
|
||||
}
|
||||
|
||||
&::before {
|
||||
@include S(margin-bottom, 15px);
|
||||
content: "";
|
||||
@include S(width, 50px);
|
||||
@include S(height, 50px);
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
opacity: 0.2;
|
||||
}
|
||||
&::before {
|
||||
/* @load-async */
|
||||
background-image: uiResource("res/ui/icons/mods.png") !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modsList {
|
||||
@include S(margin-top, 10px);
|
||||
overflow-y: scroll;
|
||||
pointer-events: all;
|
||||
@include S(padding-right, 5px);
|
||||
flex-grow: 1;
|
||||
|
||||
.mod {
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
background: $accentColorBright;
|
||||
@include S(margin-bottom, 4px);
|
||||
@include S(padding, 7px, 10px);
|
||||
@include S(grid-gap, 15px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr D(100px) D(80px) D(50px);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: darken($darkModeControlsBackground, 5);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.mainInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
@include SuperSmallText;
|
||||
@include S(margin-top, 5px);
|
||||
color: $accentColorDark;
|
||||
}
|
||||
.website {
|
||||
text-transform: uppercase;
|
||||
align-self: start;
|
||||
@include SuperSmallText;
|
||||
@include S(margin-top, 5px);
|
||||
}
|
||||
}
|
||||
|
||||
.version,
|
||||
.author {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
strong {
|
||||
text-transform: uppercase;
|
||||
color: $accentColorDark;
|
||||
@include SuperSmallText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue