1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-13 10:11:50 +00:00

Merge remote-tracking branch 'upstream/modloader' into modloader

This commit is contained in:
saile515 2022-01-24 19:30:14 +01:00
commit aa679b2dda
34 changed files with 176 additions and 27 deletions

View File

@ -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_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

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "add-building-basic",
description: "Shows how to add a new basic building",
minimumGameVersion: ">=1.5.0",
};
class MetaDemoModBuilding extends shapez.ModMetaBuilding {

View File

@ -7,6 +7,7 @@ const METADATA = {
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",
minimumGameVersion: ">=1.5.0",
};
// Declare a new type of item processor

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "base",
description: "The most basic mod",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "patch-methods",
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 {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "class-extensions",
description: "Shows how to extend builtin classes",
minimumGameVersion: ">=1.5.0",
};
const BeltExtension = ({ $super, $old }) => ({

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "custom-css",
description: "Shows how to add custom css",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "base",
description: "Displays an indicator on every item processing building when its working",
minimumGameVersion: ">=1.5.0",
};
class ItemProcessorStatusGameSystem extends shapez.GameSystem {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "base",
description: "Shows how to add a new keybinding",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "custom-sub-shapes",
description: "Shows how to add custom sub shapes",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "custom-theme",
description: "Shows how to add a custom game theme",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "mod-settings",
description: "Shows how to add settings to your mod",
minimumGameVersion: ">=1.5.0",
settings: {
timesLaunched: 0,

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "modify-existing-building",
description: "Shows how to modify an existing building",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "modify-theme",
description: "Shows how to modify builtin themes",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "modify-ui",
description: "Shows how to modify a builtin game state, in this case the main menu",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "new-item-type",
description: "Shows how to add a new item type (fluid)",
minimumGameVersion: ">=1.5.0",
};
// Define which fluid types there are

View File

@ -7,6 +7,8 @@ const METADATA = {
id: "notification-blocks",
description:
"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",
};
////////////////////////////////////////////////////////////////////////

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "pasting",
description: "Shows how to properly receive paste events ingame",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "replace-builtin-sprites",
description: "Shows how to replace builtin sprites",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

21
mod_examples/sandbox.js Normal file
View 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;
});
}
}

View File

@ -6,6 +6,7 @@ const METADATA = {
version: "1",
id: "translations",
description: "Shows how to add and modify translations",
minimumGameVersion: ">=1.5.0",
};
class Mod extends shapez.Mod {

View File

@ -7,6 +7,8 @@ const METADATA = {
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",
};
/**

View File

@ -56,6 +56,7 @@
"promise-polyfill": "^8.1.0",
"query-string": "^6.8.1",
"rusha": "^0.8.13",
"semver": "^7.3.5",
"serialize-error": "^3.0.0",
"strictdom": "^1.0.1",
"string-replace-webpack-plugin": "^0.1.3",

View File

@ -199,10 +199,6 @@
transform: translateX(50%) rotate(-7deg) scale(1.1);
}
}
@include DarkThemeOverride {
color: $colorBlueBright;
}
}
}
@ -326,6 +322,7 @@
/* @load-async */
background-image: uiResource("icons/edit_key.png") !important;
}
@include DarkThemeInvert;
}
}
@ -354,13 +351,14 @@
box-sizing: border-box;
@include PlainText;
@include S(margin-bottom, 5px);
display: grid;
grid-template-columns: 1fr auto auto;
@include S(grid-gap, 5px);
display: flex;
flex-direction: column;
.author,
.version {
@include SuperSmallText;
align-self: end;
opacity: 0.4;
}
.name {
overflow: hidden;
@ -457,19 +455,19 @@
.newGameButton {
@include IncreasedClickArea(0px);
@include S(margin-left, 15px);
@include S(margin-left, 10px);
}
.modsButton {
@include IncreasedClickArea(0px);
@include S(margin-left, 15px);
@include S(margin-left, 10px);
@include S(width, 20px);
// @include S(width, 20px);
& {
/* @load-async */
background-image: uiResource("res/ui/icons/mods_white.png") !important;
}
// & {
// /* @load-async */
// background-image: uiResource("res/ui/icons/mods_white.png") !important;
// }
background-position: center center;
background-size: D(15px);
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 {
> a,
.sidelinks > a {

View File

@ -62,6 +62,11 @@
@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: "";
@ -94,6 +99,10 @@
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;

View File

@ -28,6 +28,8 @@ export const THIRDPARTY_URLS = {
25: "https://www.youtube.com/watch?v=7OCV1g40Iew&",
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";

View File

@ -94,11 +94,12 @@ export class HUDEntityDebugger extends BaseHUDPart {
<div>`;
for (const property in val) {
const isRoot = val[property] == this.root;
const isRecursive = recursion.includes(val[property]);
let hiddenValue = isRoot ? "<root>" : null;
if (isRecursive) {
let hiddenValue = null;
if (val[property] == this.root) {
hiddenValue = "<root>";
} else if (val[property] instanceof Node) {
hiddenValue = `<${val[property].constructor.name}>`;
} else if (recursion.includes(val[property])) {
// Avoid recursion by not "expanding" object more than once
hiddenValue = "<recursion>";
}

View File

@ -50,8 +50,13 @@ export class StorageSystem extends GameSystemWithFilter {
let targetAlpha = storageComp.storedCount > 0 ? 1 : 0;
storageComp.overlayOpacity = lerp(storageComp.overlayOpacity, targetAlpha, 0.05);
pinsComp.slots[0].value = storageComp.storedItem;
pinsComp.slots[1].value = storageComp.getIsFull() ? BOOL_TRUE_SINGLETON : BOOL_FALSE_SINGLETON;
// a wired pins component is not guaranteed, but if its there, set the value
if (pinsComp) {
pinsComp.slots[0].value = storageComp.storedItem;
pinsComp.slots[1].value = storageComp.getIsFull()
? BOOL_TRUE_SINGLETON
: BOOL_FALSE_SINGLETON;
}
}
}

View File

@ -31,6 +31,12 @@ import { BaseHUDPart } from "../game/hud/base_hud_part";
* @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 P
@ -400,7 +406,7 @@ export class ModInterface {
* @template {extendsPrams<P[M]>} O the method that will override the old one
* @param {C} classHandle
* @param {M} methodName
* @param {beforePrams<O, P[M]>} override
* @param {bindThis<beforePrams<O, P[M]>, InstanceType<C>>} override
*/
replaceMethod(classHandle, methodName, override) {
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
* @param {C} classHandle
* @param {M} methodName
* @param {O} executeBefore
* @param {bindThis<O, InstanceType<C>>} executeBefore
*/
runBeforeMethod(classHandle, methodName, executeBefore) {
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
* @param {C} classHandle
* @param {M} methodName
* @param {O} executeAfter
* @param {bindThis<O, InstanceType<C>>} executeAfter
*/
runAfterMethod(classHandle, methodName, executeAfter) {
const oldHandle = classHandle.prototype[methodName];

View File

@ -10,6 +10,9 @@ import { Mod } from "./mod";
import { ModInterface } from "./mod_interface";
import { MOD_SIGNALS } from "./mod_signals";
import semverValidRange from "semver/ranges/valid";
import semverSatisifies from "semver/functions/satisfies";
const LOG = createLogger("mods");
/**
@ -20,6 +23,7 @@ const LOG = createLogger("mods");
* website: string;
* description: string;
* id: string;
* minimumGameVersion?: string;
* settings: []
* }} ModMetadata
*/
@ -158,6 +162,27 @@ export class ModLoader {
const { modClass, meta } = this.modLoadQueue[i];
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;
if (meta.settings) {

View File

@ -161,7 +161,10 @@ export class MainMenuState extends GameState {
${MODS.mods
.map(mod => {
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("")}
@ -413,7 +416,10 @@ export class MainMenuState extends GameState {
}
// Mods
this.trackClicks(makeButton(outerDiv, ["modsButton", "styledButton"], "&nbsp;"), this.onModsClicked);
this.trackClicks(
makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title),
this.onModsClicked
);
buttonContainer.appendChild(outerDiv);
}

View File

@ -18,6 +18,11 @@ export class ModsState extends TextualGameState {
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
<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
? `<button class="styledButton openModsFolder">${T.mods.openFolder}</button>`
@ -53,8 +58,9 @@ export class ModsState extends TextualGameState {
return `
<div class="modsStats noMods">
${T.mods.modsInfo}
<button class="styledButton browseMods">${T.mods.browseMods}</button>
</div>
`;
@ -100,6 +106,10 @@ export class ModsState extends TextualGameState {
if (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");
Array.from(checkboxes).forEach(checkbox => {
@ -119,6 +129,11 @@ export class ModsState extends TextualGameState {
ipcRenderer.invoke("open-mods-folder");
}
openBrowseMods() {
this.app.analytics.trackUiClick("mods_sbrowse_link");
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.modBrowser);
}
onSteamLinkClicked() {
this.app.analytics.trackUiClick("mods_steam_link");
this.app.platformWrapper.openExternalLink(

View File

@ -1098,6 +1098,7 @@ mods:
version: Version
openFolder: Open Mods Folder
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
browseMods: Browse Mods
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.

View File

@ -5245,6 +5245,13 @@ lru-cache@^5.1.1:
dependencies:
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:
version "1.4.4"
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"
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:
version "0.17.1"
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"
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:
version "0.1.5"
resolved "https://registry.npmjs.org/yaml-js/-/yaml-js-0.1.5.tgz"