mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-13 10:11:50 +00:00
Merge branch 'modloader' into processing-refactor
This commit is contained in:
commit
fb0a66f5ca
@ -361,7 +361,8 @@ ipcMain.handle("get-mods", async () => {
|
|||||||
|
|
||||||
if (externalMod) {
|
if (externalMod) {
|
||||||
console.log("Adding external mod source:", externalMod);
|
console.log("Adding external mod source:", externalMod);
|
||||||
modFiles.push(externalMod);
|
const externalModPaths = externalMod.split(",");
|
||||||
|
modFiles = modFiles.concat(externalModPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modFiles.length > 0 && !isDev) {
|
if (modFiles.length > 0 && !isDev) {
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
Currently there are two options to develop mods for shapez.io:
|
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
|
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` 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.
|
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.
|
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.
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ To get into shapez.io modding, I highly recommend checking out all of the exampl
|
|||||||
| [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_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 |
|
| [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 |
|
| [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
|
### Advanced Examples
|
||||||
|
|
||||||
@ -50,3 +51,7 @@ To get into shapez.io modding, I highly recommend checking out all of the exampl
|
|||||||
| [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 |
|
| [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 |
|
| [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 |
|
| [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 |
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "add-building-basic",
|
id: "add-building-basic",
|
||||||
description: "Shows how to add a new basic building",
|
description: "Shows how to add a new basic building",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class MetaDemoModBuilding extends shapez.ModMetaBuilding {
|
class MetaDemoModBuilding extends shapez.ModMetaBuilding {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const METADATA = {
|
|||||||
id: "add-building-extended",
|
id: "add-building-extended",
|
||||||
description:
|
description:
|
||||||
"Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
|
"Shows how to add a new building with logic, in this case it flips/mirrors shapez from top to down",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Declare a new type of item processor
|
// Declare a new type of item processor
|
||||||
@ -80,7 +81,7 @@ class MetaModFlipperBuilding extends shapez.ModMetaBuilding {
|
|||||||
slots: [
|
slots: [
|
||||||
{
|
{
|
||||||
pos: new shapez.Vector(0, 0),
|
pos: new shapez.Vector(0, 0),
|
||||||
directions: [shapez.enumDirection.bottom],
|
direction: shapez.enumDirection.bottom,
|
||||||
filter: "shape",
|
filter: "shape",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "base",
|
id: "base",
|
||||||
description: "The most basic mod",
|
description: "The most basic mod",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "patch-methods",
|
id: "patch-methods",
|
||||||
description: "Shows how to patch existing methods to change the game by making the belts cost shapes",
|
description: "Shows how to patch existing methods to change the game by making the belts cost shapes",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "class-extensions",
|
id: "class-extensions",
|
||||||
description: "Shows how to extend builtin classes",
|
description: "Shows how to extend builtin classes",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const BeltExtension = ({ $super, $old }) => ({
|
const BeltExtension = ({ $super, $old }) => ({
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "custom-css",
|
id: "custom-css",
|
||||||
description: "Shows how to add custom css",
|
description: "Shows how to add custom css",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "base",
|
id: "base",
|
||||||
description: "Displays an indicator on every item processing building when its working",
|
description: "Displays an indicator on every item processing building when its working",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class ItemProcessorStatusGameSystem extends shapez.GameSystem {
|
class ItemProcessorStatusGameSystem extends shapez.GameSystem {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "base",
|
id: "base",
|
||||||
description: "Shows how to add a new keybinding",
|
description: "Shows how to add a new keybinding",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "custom-sub-shapes",
|
id: "custom-sub-shapes",
|
||||||
description: "Shows how to add custom sub shapes",
|
description: "Shows how to add custom sub shapes",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "custom-theme",
|
id: "custom-theme",
|
||||||
description: "Shows how to add a custom game theme",
|
description: "Shows how to add a custom game theme",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "mod-settings",
|
id: "mod-settings",
|
||||||
description: "Shows how to add settings to your mod",
|
description: "Shows how to add settings to your mod",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
timesLaunched: 0,
|
timesLaunched: 0,
|
||||||
@ -23,7 +24,7 @@ class Mod extends shapez.Mod {
|
|||||||
if (state instanceof shapez.MainMenuState) {
|
if (state instanceof shapez.MainMenuState) {
|
||||||
this.dialogs.showInfo(
|
this.dialogs.showInfo(
|
||||||
"Welcome back",
|
"Welcome back",
|
||||||
"You have launched this mod " + this.settings.timesLaunched + " times"
|
`You have launched this mod ${this.settings.timesLaunched} times`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "modify-existing-building",
|
id: "modify-existing-building",
|
||||||
description: "Shows how to modify an existing building",
|
description: "Shows how to modify an existing building",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "modify-theme",
|
id: "modify-theme",
|
||||||
description: "Shows how to modify builtin themes",
|
description: "Shows how to modify builtin themes",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "modify-ui",
|
id: "modify-ui",
|
||||||
description: "Shows how to modify a builtin game state, in this case the main menu",
|
description: "Shows how to modify a builtin game state, in this case the main menu",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "new-item-type",
|
id: "new-item-type",
|
||||||
description: "Shows how to add a new item type (fluid)",
|
description: "Shows how to add a new item type (fluid)",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define which fluid types there are
|
// Define which fluid types there are
|
||||||
@ -75,7 +76,7 @@ class FluidItem extends shapez.BaseItem {
|
|||||||
*/
|
*/
|
||||||
drawFullSizeOnCanvas(context, size) {
|
drawFullSizeOnCanvas(context, size) {
|
||||||
if (!this.cachedSprite) {
|
if (!this.cachedSprite) {
|
||||||
this.cachedSprite = shapez.Loader.getSprite("sprites/fluids/" + this.fluidType + ".png");
|
this.cachedSprite = shapez.Loader.getSprite(`sprites/fluids/${this.fluidType}.png`);
|
||||||
}
|
}
|
||||||
this.cachedSprite.drawCentered(context, size / 2, size / 2, size);
|
this.cachedSprite.drawCentered(context, size / 2, size / 2, size);
|
||||||
}
|
}
|
||||||
@ -89,7 +90,7 @@ class FluidItem extends shapez.BaseItem {
|
|||||||
drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
drawItemCenteredClipped(x, y, parameters, diameter = globalConfig.defaultItemDiameter) {
|
||||||
const realDiameter = diameter * 0.6;
|
const realDiameter = diameter * 0.6;
|
||||||
if (!this.cachedSprite) {
|
if (!this.cachedSprite) {
|
||||||
this.cachedSprite = shapez.Loader.getSprite("sprites/fluids/" + this.fluidType + ".png");
|
this.cachedSprite = shapez.Loader.getSprite(`sprites/fluids/${this.fluidType}.png`);
|
||||||
}
|
}
|
||||||
this.cachedSprite.drawCachedCentered(parameters, x, y, realDiameter);
|
this.cachedSprite.drawCachedCentered(parameters, x, y, realDiameter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ const METADATA = {
|
|||||||
id: "notification-blocks",
|
id: "notification-blocks",
|
||||||
description:
|
description:
|
||||||
"Adds a new building to the wires layer, 'Notification Blocks' which show a custom notification when they get a truthy signal.",
|
"Adds a new building to the wires layer, 'Notification Blocks' which show a custom notification when they get a truthy signal.",
|
||||||
|
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "pasting",
|
id: "pasting",
|
||||||
description: "Shows how to properly receive paste events ingame",
|
description: "Shows how to properly receive paste events ingame",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
@ -15,7 +16,7 @@ class Mod extends shapez.Mod {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const data = event.clipboardData.getData("text");
|
const data = event.clipboardData.getData("text");
|
||||||
this.dialogs.showInfo("Pasted", "You pasted: '" + data + "'");
|
this.dialogs.showInfo("Pasted", `You pasted: '${data}'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,13 +6,14 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "replace-builtin-sprites",
|
id: "replace-builtin-sprites",
|
||||||
description: "Shows how to replace builtin sprites",
|
description: "Shows how to replace builtin sprites",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
init() {
|
init() {
|
||||||
// Replace a builtin sprite
|
// Replace a builtin sprite
|
||||||
["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
|
["red", "green", "blue", "yellow", "purple", "cyan", "white"].forEach(color => {
|
||||||
this.modInterface.registerSprite("sprites/colors/" + color + ".png", RESOURCES[color + ".png"]);
|
this.modInterface.registerSprite(`sprites/colors/${color}.png`, RESOURCES[color + ".png"]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
mod_examples/sandbox.js
Normal file
21
mod_examples/sandbox.js
Normal file
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ const METADATA = {
|
|||||||
version: "1",
|
version: "1",
|
||||||
id: "translations",
|
id: "translations",
|
||||||
description: "Shows how to add and modify translations",
|
description: "Shows how to add and modify translations",
|
||||||
|
minimumGameVersion: ">=1.5.0",
|
||||||
};
|
};
|
||||||
|
|
||||||
class Mod extends shapez.Mod {
|
class Mod extends shapez.Mod {
|
||||||
|
|||||||
@ -7,6 +7,8 @@ const METADATA = {
|
|||||||
id: "usage-statistics",
|
id: "usage-statistics",
|
||||||
description:
|
description:
|
||||||
"Shows how to add a new component to the game, how to save additional data and how to add custom logic and drawings",
|
"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",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
"promise-polyfill": "^8.1.0",
|
"promise-polyfill": "^8.1.0",
|
||||||
"query-string": "^6.8.1",
|
"query-string": "^6.8.1",
|
||||||
"rusha": "^0.8.13",
|
"rusha": "^0.8.13",
|
||||||
|
"semver": "^7.3.5",
|
||||||
"serialize-error": "^3.0.0",
|
"serialize-error": "^3.0.0",
|
||||||
"strictdom": "^1.0.1",
|
"strictdom": "^1.0.1",
|
||||||
"string-replace-webpack-plugin": "^0.1.3",
|
"string-replace-webpack-plugin": "^0.1.3",
|
||||||
|
|||||||
@ -199,10 +199,6 @@
|
|||||||
transform: translateX(50%) rotate(-7deg) scale(1.1);
|
transform: translateX(50%) rotate(-7deg) scale(1.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include DarkThemeOverride {
|
|
||||||
color: $colorBlueBright;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,6 +322,7 @@
|
|||||||
/* @load-async */
|
/* @load-async */
|
||||||
background-image: uiResource("icons/edit_key.png") !important;
|
background-image: uiResource("icons/edit_key.png") !important;
|
||||||
}
|
}
|
||||||
|
@include DarkThemeInvert;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,13 +351,14 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@include PlainText;
|
@include PlainText;
|
||||||
@include S(margin-bottom, 5px);
|
@include S(margin-bottom, 5px);
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr auto auto;
|
flex-direction: column;
|
||||||
@include S(grid-gap, 5px);
|
|
||||||
|
|
||||||
.author,
|
.author,
|
||||||
.version {
|
.version {
|
||||||
@include SuperSmallText;
|
@include SuperSmallText;
|
||||||
|
align-self: end;
|
||||||
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -457,19 +455,19 @@
|
|||||||
|
|
||||||
.newGameButton {
|
.newGameButton {
|
||||||
@include IncreasedClickArea(0px);
|
@include IncreasedClickArea(0px);
|
||||||
@include S(margin-left, 15px);
|
@include S(margin-left, 10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modsButton {
|
.modsButton {
|
||||||
@include IncreasedClickArea(0px);
|
@include IncreasedClickArea(0px);
|
||||||
@include S(margin-left, 15px);
|
@include S(margin-left, 10px);
|
||||||
|
|
||||||
@include S(width, 20px);
|
// @include S(width, 20px);
|
||||||
|
|
||||||
& {
|
// & {
|
||||||
/* @load-async */
|
// /* @load-async */
|
||||||
background-image: uiResource("res/ui/icons/mods_white.png") !important;
|
// background-image: uiResource("res/ui/icons/mods_white.png") !important;
|
||||||
}
|
// }
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-size: D(15px);
|
background-size: D(15px);
|
||||||
background-color: $modsColor !important;
|
background-color: $modsColor !important;
|
||||||
@ -837,6 +835,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modsOverview {
|
||||||
|
background: $darkModeControlsBackground;
|
||||||
|
|
||||||
|
.modsList {
|
||||||
|
border-color: darken($darkModeControlsBackground, 5);
|
||||||
|
|
||||||
|
.mod {
|
||||||
|
background: darken($darkModeControlsBackground, 5);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dlcHint {
|
||||||
|
color: $accentColorBright;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
> a,
|
> a,
|
||||||
.sidelinks > a {
|
.sidelinks > a {
|
||||||
|
|||||||
@ -62,6 +62,11 @@
|
|||||||
@include S(margin-top, 100px);
|
@include S(margin-top, 100px);
|
||||||
color: lighten($accentColorDark, 15);
|
color: lighten($accentColorDark, 15);
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include S(margin-top, 10px);
|
||||||
|
@include S(padding, 10px, 20px);
|
||||||
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@include S(margin-bottom, 15px);
|
@include S(margin-bottom, 15px);
|
||||||
content: "";
|
content: "";
|
||||||
@ -94,6 +99,10 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr D(100px) D(80px) D(50px);
|
grid-template-columns: 1fr D(100px) D(80px) D(50px);
|
||||||
|
|
||||||
|
@include DarkThemeOverride {
|
||||||
|
background: darken($darkModeControlsBackground, 5);
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export const THIRDPARTY_URLS = {
|
|||||||
25: "https://www.youtube.com/watch?v=7OCV1g40Iew&",
|
25: "https://www.youtube.com/watch?v=7OCV1g40Iew&",
|
||||||
26: "https://www.youtube.com/watch?v=gfm6dS1dCoY",
|
26: "https://www.youtube.com/watch?v=gfm6dS1dCoY",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
modBrowser: "https://shapez.mod.io/?preview=f55f6304ca4873d9a25f3b575571b948",
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const A_B_TESTING_LINK_TYPE = Math.random() > 0.95 ? "steam_1_pr" : "steam_2_npr";
|
// export const A_B_TESTING_LINK_TYPE = Math.random() > 0.95 ? "steam_1_pr" : "steam_2_npr";
|
||||||
@ -58,7 +60,7 @@ export const globalConfig = {
|
|||||||
// Map
|
// Map
|
||||||
mapChunkSize: 16,
|
mapChunkSize: 16,
|
||||||
chunkAggregateSize: 4,
|
chunkAggregateSize: 4,
|
||||||
mapChunkOverviewMinZoom: 0,
|
mapChunkOverviewMinZoom: 0.9,
|
||||||
mapChunkWorldSize: null, // COMPUTED
|
mapChunkWorldSize: null, // COMPUTED
|
||||||
|
|
||||||
maxBeltShapeBundleSize: 20,
|
maxBeltShapeBundleSize: 20,
|
||||||
|
|||||||
@ -70,100 +70,22 @@ export class MetaHubBuilding extends MetaBuilding {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("../components/item_acceptor").ItemAcceptorSlotConfig>}
|
||||||
|
*/
|
||||||
|
const slots = [];
|
||||||
|
for (let i = 0; i < 4; ++i) {
|
||||||
|
slots.push(
|
||||||
|
{ pos: new Vector(i, 0), direction: enumDirection.top, filter: "shape" },
|
||||||
|
{ pos: new Vector(i, 3), direction: enumDirection.bottom, filter: "shape" },
|
||||||
|
{ pos: new Vector(0, i), direction: enumDirection.left, filter: "shape" },
|
||||||
|
{ pos: new Vector(3, i), direction: enumDirection.right, filter: "shape" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
entity.addComponent(
|
entity.addComponent(
|
||||||
new ItemAcceptorComponent({
|
new ItemAcceptorComponent({
|
||||||
slots: [
|
slots,
|
||||||
{
|
|
||||||
pos: new Vector(0, 0),
|
|
||||||
direction: enumDirection.top,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 0),
|
|
||||||
direction: enumDirection.left,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(1, 0),
|
|
||||||
direction: enumDirection.top,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(2, 0),
|
|
||||||
direction: enumDirection.top,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 0),
|
|
||||||
direction: enumDirection.top,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 0),
|
|
||||||
direction: enumDirection.right,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 3),
|
|
||||||
direction: enumDirection.left,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 3),
|
|
||||||
direction: enumDirection.bottom,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(1, 3),
|
|
||||||
direction: enumDirection.bottom,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(2, 3),
|
|
||||||
direction: enumDirection.bottom,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 3),
|
|
||||||
direction: enumDirection.bottom,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 3),
|
|
||||||
direction: enumDirection.right,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 1),
|
|
||||||
direction: enumDirection.left,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 2),
|
|
||||||
direction: enumDirection.left,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 3),
|
|
||||||
direction: enumDirection.left,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 1),
|
|
||||||
direction: enumDirection.right,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 2),
|
|
||||||
direction: enumDirection.right,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(3, 3),
|
|
||||||
direction: enumDirection.right,
|
|
||||||
filter: "shape",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,10 +55,22 @@ export class MetaTrashBuilding extends MetaBuilding {
|
|||||||
entity.addComponent(
|
entity.addComponent(
|
||||||
new ItemAcceptorComponent({
|
new ItemAcceptorComponent({
|
||||||
slots: [
|
slots: [
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.top },
|
{
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.left },
|
pos: new Vector(0, 0),
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.right },
|
direction: enumDirection.top,
|
||||||
{ pos: new Vector(0, 0), direction: enumDirection.bottom },
|
},
|
||||||
|
{
|
||||||
|
pos: new Vector(0, 0),
|
||||||
|
direction: enumDirection.right,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pos: new Vector(0, 0),
|
||||||
|
direction: enumDirection.bottom,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pos: new Vector(0, 0),
|
||||||
|
direction: enumDirection.left,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,11 +13,11 @@ import { GameRoot } from "../root";
|
|||||||
*
|
*
|
||||||
* Contains information about a slot plus its location
|
* Contains information about a slot plus its location
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* slot: ItemAcceptorSlot,
|
* slot: ItemAcceptorSlot,
|
||||||
* index: number,
|
* index: number,
|
||||||
* acceptedDirection: enumDirection
|
* }} ItemAcceptorLocatedSlot */
|
||||||
* }} ItemAcceptorLocatedSlot
|
|
||||||
*
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* pos: Vector,
|
* pos: Vector,
|
||||||
* direction: enumDirection,
|
* direction: enumDirection,
|
||||||
@ -128,11 +128,11 @@ export class ItemAcceptorComponent extends Component {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the acceptor slot accepts items from our direction
|
||||||
if (desiredDirection === slot.direction) {
|
if (desiredDirection === slot.direction) {
|
||||||
return {
|
return {
|
||||||
slot,
|
slot,
|
||||||
index: slotIndex,
|
index: slotIndex,
|
||||||
acceptedDirection: desiredDirection,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -435,7 +435,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const randomColor = () => rng.choice(colors);
|
const randomColor = () => rng.choice(colors);
|
||||||
const randomShape = () => rng.choice(Object.values(enumSubShape));
|
const randomShape = () => rng.choice(availableShapes);
|
||||||
|
|
||||||
let anyIsMissingTwo = false;
|
let anyIsMissingTwo = false;
|
||||||
|
|
||||||
|
|||||||
@ -530,7 +530,13 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
|
|
||||||
const offsetShift = 10;
|
const offsetShift = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("../../components/item_acceptor").ItemAcceptorSlot>}
|
||||||
|
*/
|
||||||
let acceptorSlots = [];
|
let acceptorSlots = [];
|
||||||
|
/**
|
||||||
|
* @type {Array<import("../../components/item_ejector").ItemEjectorSlot>}
|
||||||
|
*/
|
||||||
let ejectorSlots = [];
|
let ejectorSlots = [];
|
||||||
|
|
||||||
if (ejectorComp) {
|
if (ejectorComp) {
|
||||||
@ -548,8 +554,9 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
acceptorSlots.push(fakeAcceptorSlot);
|
acceptorSlots.push(fakeAcceptorSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let acceptorSlotIndex = 0; acceptorSlotIndex < acceptorSlots.length; ++acceptorSlotIndex) {
|
// Go over all slots
|
||||||
const slot = acceptorSlots[acceptorSlotIndex];
|
for (let i = 0; i < acceptorSlots.length; ++i) {
|
||||||
|
const slot = acceptorSlots[i];
|
||||||
|
|
||||||
const acceptorSlotWsTile = staticComp.localTileToWorld(slot.pos);
|
const acceptorSlotWsTile = staticComp.localTileToWorld(slot.pos);
|
||||||
const acceptorSlotWsPos = acceptorSlotWsTile.toWorldSpaceCenterOfTile();
|
const acceptorSlotWsPos = acceptorSlotWsTile.toWorldSpaceCenterOfTile();
|
||||||
@ -567,8 +574,8 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||||||
const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
|
const sourceEntities = this.root.map.getLayersContentsMultipleXY(sourceTile.x, sourceTile.y);
|
||||||
|
|
||||||
// Check for every entity:
|
// Check for every entity:
|
||||||
for (let i = 0; i < sourceEntities.length; ++i) {
|
for (let j = 0; j < sourceEntities.length; ++j) {
|
||||||
const sourceEntity = sourceEntities[i];
|
const sourceEntity = sourceEntities[j];
|
||||||
const sourceEjector = sourceEntity.components.ItemEjector;
|
const sourceEjector = sourceEntity.components.ItemEjector;
|
||||||
const sourceBeltComp = sourceEntity.components.Belt;
|
const sourceBeltComp = sourceEntity.components.Belt;
|
||||||
const sourceStaticComp = sourceEntity.components.StaticMapEntity;
|
const sourceStaticComp = sourceEntity.components.StaticMapEntity;
|
||||||
|
|||||||
@ -94,11 +94,12 @@ export class HUDEntityDebugger extends BaseHUDPart {
|
|||||||
<div>`;
|
<div>`;
|
||||||
|
|
||||||
for (const property in val) {
|
for (const property in val) {
|
||||||
const isRoot = val[property] == this.root;
|
let hiddenValue = null;
|
||||||
const isRecursive = recursion.includes(val[property]);
|
if (val[property] == this.root) {
|
||||||
|
hiddenValue = "<root>";
|
||||||
let hiddenValue = isRoot ? "<root>" : null;
|
} else if (val[property] instanceof Node) {
|
||||||
if (isRecursive) {
|
hiddenValue = `<${val[property].constructor.name}>`;
|
||||||
|
} else if (recursion.includes(val[property])) {
|
||||||
// Avoid recursion by not "expanding" object more than once
|
// Avoid recursion by not "expanding" object more than once
|
||||||
hiddenValue = "<recursion>";
|
hiddenValue = "<recursion>";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -395,7 +395,14 @@ export class GameLogic {
|
|||||||
|
|
||||||
const entity = this.root.map.getLayerContentXY(tile.x + dx, tile.y + dy, "regular");
|
const entity = this.root.map.getLayerContentXY(tile.x + dx, tile.y + dy, "regular");
|
||||||
if (entity) {
|
if (entity) {
|
||||||
|
/**
|
||||||
|
* @type {Array<import("./components/item_ejector").ItemEjectorSlot>}
|
||||||
|
*/
|
||||||
let ejectorSlots = [];
|
let ejectorSlots = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("./components/item_acceptor").ItemAcceptorSlot>}
|
||||||
|
*/
|
||||||
let acceptorSlots = [];
|
let acceptorSlots = [];
|
||||||
|
|
||||||
const staticComp = entity.components.StaticMapEntity;
|
const staticComp = entity.components.StaticMapEntity;
|
||||||
@ -438,7 +445,6 @@ export class GameLogic {
|
|||||||
const wsTile = staticComp.localTileToWorld(slot.pos);
|
const wsTile = staticComp.localTileToWorld(slot.pos);
|
||||||
const direction = slot.direction;
|
const direction = slot.direction;
|
||||||
const wsDirection = staticComp.localDirectionToWorld(direction);
|
const wsDirection = staticComp.localDirectionToWorld(direction);
|
||||||
|
|
||||||
const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]);
|
const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]);
|
||||||
if (sourceTile.equals(tile)) {
|
if (sourceTile.equals(tile)) {
|
||||||
acceptors.push({
|
acceptors.push({
|
||||||
|
|||||||
@ -65,8 +65,13 @@ export class StorageSystem extends GameSystemWithFilter {
|
|||||||
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
|
||||||
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
|
||||||
|
|
||||||
pinsComp.slots[0].value = storageComp.storedItem;
|
// a wired pins component is not guaranteed, but if its there, set the value
|
||||||
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
|
if (pinsComp) {
|
||||||
|
pinsComp.slots[0].value = storageComp.storedItem;
|
||||||
|
pinsComp.slots[1].value = storageComp.getIsFull()
|
||||||
|
? BOOL_TRUE_SINGLETON
|
||||||
|
: BOOL_FALSE_SINGLETON;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,12 @@ import { BaseHUDPart } from "../game/hud/base_hud_part";
|
|||||||
* @typedef {{new(...args: any[]): any, prototype: any}} constructable
|
* @typedef {{new(...args: any[]): any, prototype: any}} constructable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {(...args: any) => any} F The function
|
||||||
|
* @template {object} T The value of this
|
||||||
|
* @typedef {(this: T, ...args: Parameters<F>) => ReturnType<F>} bindThis
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template {(...args: any[]) => any} F
|
* @template {(...args: any[]) => any} F
|
||||||
* @template P
|
* @template P
|
||||||
@ -369,7 +375,7 @@ export class ModInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new state class, should be a GameState derived class
|
* Registers a new state class, should be a GameState derived class
|
||||||
* @param {typeof GameState} stateClass
|
* @param {typeof import("../core/game_state").GameState} stateClass
|
||||||
*/
|
*/
|
||||||
registerGameState(stateClass) {
|
registerGameState(stateClass) {
|
||||||
this.modLoader.app.stateMgr.register(stateClass);
|
this.modLoader.app.stateMgr.register(stateClass);
|
||||||
@ -400,7 +406,7 @@ export class ModInterface {
|
|||||||
* @template {extendsPrams<P[M]>} O the method that will override the old one
|
* @template {extendsPrams<P[M]>} O the method that will override the old one
|
||||||
* @param {C} classHandle
|
* @param {C} classHandle
|
||||||
* @param {M} methodName
|
* @param {M} methodName
|
||||||
* @param {beforePrams<O, P[M]>} override
|
* @param {bindThis<beforePrams<O, P[M]>, InstanceType<C>>} override
|
||||||
*/
|
*/
|
||||||
replaceMethod(classHandle, methodName, override) {
|
replaceMethod(classHandle, methodName, override) {
|
||||||
const oldMethod = classHandle.prototype[methodName];
|
const oldMethod = classHandle.prototype[methodName];
|
||||||
@ -418,7 +424,7 @@ export class ModInterface {
|
|||||||
* @template {extendsPrams<P[M]>} O the method that will run before the old one
|
* @template {extendsPrams<P[M]>} O the method that will run before the old one
|
||||||
* @param {C} classHandle
|
* @param {C} classHandle
|
||||||
* @param {M} methodName
|
* @param {M} methodName
|
||||||
* @param {O} executeBefore
|
* @param {bindThis<O, InstanceType<C>>} executeBefore
|
||||||
*/
|
*/
|
||||||
runBeforeMethod(classHandle, methodName, executeBefore) {
|
runBeforeMethod(classHandle, methodName, executeBefore) {
|
||||||
const oldHandle = classHandle.prototype[methodName];
|
const oldHandle = classHandle.prototype[methodName];
|
||||||
@ -437,7 +443,7 @@ export class ModInterface {
|
|||||||
* @template {extendsPrams<P[M]>} O the method that will run before the old one
|
* @template {extendsPrams<P[M]>} O the method that will run before the old one
|
||||||
* @param {C} classHandle
|
* @param {C} classHandle
|
||||||
* @param {M} methodName
|
* @param {M} methodName
|
||||||
* @param {O} executeAfter
|
* @param {bindThis<O, InstanceType<C>>} executeAfter
|
||||||
*/
|
*/
|
||||||
runAfterMethod(classHandle, methodName, executeAfter) {
|
runAfterMethod(classHandle, methodName, executeAfter) {
|
||||||
const oldHandle = classHandle.prototype[methodName];
|
const oldHandle = classHandle.prototype[methodName];
|
||||||
@ -470,7 +476,7 @@ export class ModInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {typeof Object} classHandle
|
* @param {Class} classHandle
|
||||||
* @param {({ $super, $old }) => any} extender
|
* @param {({ $super, $old }) => any} extender
|
||||||
*/
|
*/
|
||||||
extendClass(classHandle, extender) {
|
extendClass(classHandle, extender) {
|
||||||
|
|||||||
@ -10,6 +10,9 @@ import { Mod } from "./mod";
|
|||||||
import { ModInterface } from "./mod_interface";
|
import { ModInterface } from "./mod_interface";
|
||||||
import { MOD_SIGNALS } from "./mod_signals";
|
import { MOD_SIGNALS } from "./mod_signals";
|
||||||
|
|
||||||
|
import semverValidRange from "semver/ranges/valid";
|
||||||
|
import semverSatisifies from "semver/functions/satisfies";
|
||||||
|
|
||||||
const LOG = createLogger("mods");
|
const LOG = createLogger("mods");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,6 +23,7 @@ const LOG = createLogger("mods");
|
|||||||
* website: string;
|
* website: string;
|
||||||
* description: string;
|
* description: string;
|
||||||
* id: string;
|
* id: string;
|
||||||
|
* minimumGameVersion?: string;
|
||||||
* settings: []
|
* settings: []
|
||||||
* }} ModMetadata
|
* }} ModMetadata
|
||||||
*/
|
*/
|
||||||
@ -103,21 +107,17 @@ export class ModLoader {
|
|||||||
mods = await ipcRenderer.invoke("get-mods");
|
mods = await ipcRenderer.invoke("get-mods");
|
||||||
}
|
}
|
||||||
if (G_IS_DEV && globalConfig.debug.externalModUrl) {
|
if (G_IS_DEV && globalConfig.debug.externalModUrl) {
|
||||||
let modURLs = Array.isArray(globalConfig.debug.externalModUrl) ?
|
const modURLs = Array.isArray(globalConfig.debug.externalModUrl)
|
||||||
globalConfig.debug.externalModUrl : [globalConfig.debug.externalModUrl];
|
? globalConfig.debug.externalModUrl
|
||||||
|
: [globalConfig.debug.externalModUrl];
|
||||||
for(let i = 0; i < modURLs.length; i++) {
|
|
||||||
|
for (let i = 0; i < modURLs.length; i++) {
|
||||||
const response = await fetch(modURLs[i], {
|
const response = await fetch(modURLs[i], {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Failed to load " +
|
"Failed to load " + modURLs[i] + ": " + response.status + " " + response.statusText
|
||||||
modURLs[i] +
|
|
||||||
": " +
|
|
||||||
response.status +
|
|
||||||
" " +
|
|
||||||
response.statusText
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
mods.push(await response.text());
|
mods.push(await response.text());
|
||||||
@ -162,6 +162,27 @@ export class ModLoader {
|
|||||||
const { modClass, meta } = this.modLoadQueue[i];
|
const { modClass, meta } = this.modLoadQueue[i];
|
||||||
const modDataFile = "modsettings_" + meta.id + "__" + meta.version + ".json";
|
const modDataFile = "modsettings_" + meta.id + "__" + meta.version + ".json";
|
||||||
|
|
||||||
|
if (meta.minimumGameVersion) {
|
||||||
|
console.warn(meta.minimumGameVersion, G_BUILD_VERSION);
|
||||||
|
const minimumGameVersion = meta.minimumGameVersion;
|
||||||
|
if (!semverValidRange(minimumGameVersion)) {
|
||||||
|
alert("Mod " + meta.id + " has invalid minimumGameVersion: " + minimumGameVersion);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!semverSatisifies(G_BUILD_VERSION, minimumGameVersion)) {
|
||||||
|
alert(
|
||||||
|
"Mod '" +
|
||||||
|
meta.id +
|
||||||
|
"' is incompatible with this version of the game: \n\n" +
|
||||||
|
"Mod requires version " +
|
||||||
|
minimumGameVersion +
|
||||||
|
" but this game has version " +
|
||||||
|
G_BUILD_VERSION
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let settings = meta.settings;
|
let settings = meta.settings;
|
||||||
|
|
||||||
if (meta.settings) {
|
if (meta.settings) {
|
||||||
|
|||||||
@ -161,7 +161,10 @@ export class MainMenuState extends GameState {
|
|||||||
${MODS.mods
|
${MODS.mods
|
||||||
.map(mod => {
|
.map(mod => {
|
||||||
return `
|
return `
|
||||||
<div class="mod">${mod.metadata.name} @ v${mod.metadata.version}</div>
|
<div class="mod">
|
||||||
|
<div class="name">${mod.metadata.name}</div>
|
||||||
|
<div class="author">by ${mod.metadata.author}</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
})
|
||||||
.join("")}
|
.join("")}
|
||||||
@ -413,7 +416,10 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mods
|
// Mods
|
||||||
this.trackClicks(makeButton(outerDiv, ["modsButton", "styledButton"], " "), this.onModsClicked);
|
this.trackClicks(
|
||||||
|
makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title),
|
||||||
|
this.onModsClicked
|
||||||
|
);
|
||||||
|
|
||||||
buttonContainer.appendChild(outerDiv);
|
buttonContainer.appendChild(outerDiv);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,11 @@ export class ModsState extends TextualGameState {
|
|||||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
${
|
||||||
|
(G_IS_STANDALONE || G_IS_DEV) && MODS.mods.length > 0
|
||||||
|
? `<button class="styledButton browseMods">${T.mods.browseMods}</button>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
${
|
${
|
||||||
G_IS_STANDALONE || G_IS_DEV
|
G_IS_STANDALONE || G_IS_DEV
|
||||||
? `<button class="styledButton openModsFolder">${T.mods.openFolder}</button>`
|
? `<button class="styledButton openModsFolder">${T.mods.openFolder}</button>`
|
||||||
@ -53,8 +58,9 @@ export class ModsState extends TextualGameState {
|
|||||||
return `
|
return `
|
||||||
|
|
||||||
<div class="modsStats noMods">
|
<div class="modsStats noMods">
|
||||||
|
|
||||||
${T.mods.modsInfo}
|
${T.mods.modsInfo}
|
||||||
|
|
||||||
|
<button class="styledButton browseMods">${T.mods.browseMods}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
@ -100,6 +106,10 @@ export class ModsState extends TextualGameState {
|
|||||||
if (openModsFolder) {
|
if (openModsFolder) {
|
||||||
this.trackClicks(openModsFolder, this.openModsFolder);
|
this.trackClicks(openModsFolder, this.openModsFolder);
|
||||||
}
|
}
|
||||||
|
const browseMods = this.htmlElement.querySelector(".browseMods");
|
||||||
|
if (browseMods) {
|
||||||
|
this.trackClicks(browseMods, this.openBrowseMods);
|
||||||
|
}
|
||||||
|
|
||||||
const checkboxes = this.htmlElement.querySelectorAll(".checkbox");
|
const checkboxes = this.htmlElement.querySelectorAll(".checkbox");
|
||||||
Array.from(checkboxes).forEach(checkbox => {
|
Array.from(checkboxes).forEach(checkbox => {
|
||||||
@ -119,6 +129,11 @@ export class ModsState extends TextualGameState {
|
|||||||
ipcRenderer.invoke("open-mods-folder");
|
ipcRenderer.invoke("open-mods-folder");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openBrowseMods() {
|
||||||
|
this.app.analytics.trackUiClick("mods_sbrowse_link");
|
||||||
|
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.modBrowser);
|
||||||
|
}
|
||||||
|
|
||||||
onSteamLinkClicked() {
|
onSteamLinkClicked() {
|
||||||
this.app.analytics.trackUiClick("mods_steam_link");
|
this.app.analytics.trackUiClick("mods_steam_link");
|
||||||
this.app.platformWrapper.openExternalLink(
|
this.app.platformWrapper.openExternalLink(
|
||||||
|
|||||||
@ -1098,6 +1098,7 @@ mods:
|
|||||||
version: Version
|
version: Version
|
||||||
openFolder: Open Mods Folder
|
openFolder: Open Mods Folder
|
||||||
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
|
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
|
||||||
|
browseMods: Browse Mods
|
||||||
|
|
||||||
modsInfo: >-
|
modsInfo: >-
|
||||||
To install and manage mods, copy them to the mods folder within the game directory. You can also use the 'Open Mods Folder' button on the top right.
|
To install and manage mods, copy them to the mods folder within the game directory. You can also use the 'Open Mods Folder' button on the top right.
|
||||||
|
|||||||
19
yarn.lock
19
yarn.lock
@ -5245,6 +5245,13 @@ lru-cache@^5.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
|
lru-cache@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||||
|
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||||
|
dependencies:
|
||||||
|
yallist "^4.0.0"
|
||||||
|
|
||||||
lz-string@^1.4.4:
|
lz-string@^1.4.4:
|
||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz"
|
resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz"
|
||||||
@ -7496,6 +7503,13 @@ semver@^7.2.1, semver@^7.3.2:
|
|||||||
resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz"
|
resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz"
|
||||||
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||||
|
|
||||||
|
semver@^7.3.5:
|
||||||
|
version "7.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||||
|
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
send@0.17.1:
|
send@0.17.1:
|
||||||
version "0.17.1"
|
version "0.17.1"
|
||||||
resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz"
|
resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz"
|
||||||
@ -8800,6 +8814,11 @@ yallist@^3.0.2:
|
|||||||
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
|
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
|
||||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||||
|
|
||||||
|
yallist@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
yaml-js@^0.1.3:
|
yaml-js@^0.1.3:
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
resolved "https://registry.npmjs.org/yaml-js/-/yaml-js-0.1.5.tgz"
|
resolved "https://registry.npmjs.org/yaml-js/-/yaml-js-0.1.5.tgz"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user