mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-13 18:21:51 +00:00
Allow loading mods from standalone
This commit is contained in:
parent
258dbbd98f
commit
575abd255d
1
electron/.gitignore
vendored
Normal file
1
electron/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
mods/*.js
|
||||
@ -9,6 +9,7 @@ const asyncLock = require("async-lock");
|
||||
|
||||
const isDev = process.argv.indexOf("--dev") >= 0;
|
||||
const isLocal = process.argv.indexOf("--local") >= 0;
|
||||
const safeMode = process.argv.indexOf("--safe-mode") >= 0;
|
||||
|
||||
const roamingFolder =
|
||||
process.env.APPDATA ||
|
||||
@ -16,6 +17,7 @@ const roamingFolder =
|
||||
? process.env.HOME + "/Library/Preferences"
|
||||
: process.env.HOME + "/.local/share");
|
||||
let storePath = path.join(roamingFolder, "shapez.io", "saves");
|
||||
let modsPath = path.join(app.getAppPath(), "mods");
|
||||
|
||||
if (!fs.existsSync(storePath)) {
|
||||
// No try-catch by design
|
||||
@ -79,6 +81,8 @@ function createWindow() {
|
||||
if (isDev) {
|
||||
menu = new Menu();
|
||||
|
||||
win.toggleDevTools();
|
||||
|
||||
const mainItem = new MenuItem({
|
||||
label: "Toggle Dev Tools",
|
||||
click: () => win.toggleDevTools(),
|
||||
@ -279,5 +283,28 @@ ipcMain.on("fs-job", async (event, arg) => {
|
||||
event.reply("fs-response", { id: arg.id, result });
|
||||
});
|
||||
|
||||
ipcMain.on("open-mods-folder", async () => {
|
||||
shell.openPath(modsPath);
|
||||
});
|
||||
|
||||
ipcMain.handle("get-mods", async (event, arg) => {
|
||||
if (safeMode) {
|
||||
console.warn("Not loading mods due to safe mode");
|
||||
return [];
|
||||
}
|
||||
if (!fs.existsSync(modsPath)) {
|
||||
console.warn("Mods folder not found:", modsPath);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
console.log("Loading mods from", modsPath);
|
||||
let entries = fs.readdirSync(modsPath);
|
||||
entries = entries.filter(entry => entry.endsWith(".js"));
|
||||
return entries.map(filename => fs.readFileSync(path.join(modsPath, filename), { encoding: "utf8" }));
|
||||
} catch (ex) {
|
||||
throw new Error(ex);
|
||||
}
|
||||
});
|
||||
|
||||
steam.init(isDev);
|
||||
steam.listen();
|
||||
|
||||
6
electron/mods/README.txt
Normal file
6
electron/mods/README.txt
Normal file
@ -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 ---
|
||||
@ -185,7 +185,7 @@
|
||||
.updateLabel {
|
||||
position: absolute;
|
||||
transform: translateX(50%) rotate(-5deg);
|
||||
color: #ff590b;
|
||||
color: #300bff;
|
||||
@include Heading;
|
||||
font-weight: bold;
|
||||
@include S(right, 40px);
|
||||
@ -290,6 +290,73 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modsList {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
grid-row: 1 / 2;
|
||||
grid-column: 2 / 3;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
align-items: flex-start;
|
||||
@include S(padding, 15px);
|
||||
@include S(padding-bottom, 10px);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
|
||||
h3 {
|
||||
@include S(margin-bottom, 10px);
|
||||
@include Heading;
|
||||
}
|
||||
|
||||
.dlcHint {
|
||||
@include SuperSmallText;
|
||||
@include S(margin-top, 10px);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mod {
|
||||
background: #eee;
|
||||
width: 100%;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include S(padding, 5px);
|
||||
box-sizing: border-box;
|
||||
@include S(margin-bottom, 5px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
@include S(grid-gap, 5px);
|
||||
|
||||
.author,
|
||||
.version {
|
||||
@include SuperSmallText;
|
||||
}
|
||||
.name {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.modsList {
|
||||
box-sizing: border-box;
|
||||
@include S(height, 100px);
|
||||
@include S(padding, 5px);
|
||||
border: D(1px) solid #eee;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: all;
|
||||
|
||||
:last-child {
|
||||
margin-bottom: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -130,8 +130,6 @@ export class Application {
|
||||
// Store the mouse position, or null if not available
|
||||
/** @type {Vector|null} */
|
||||
this.mousePosition = null;
|
||||
|
||||
MODS.initMods();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -331,8 +329,11 @@ export class Application {
|
||||
/**
|
||||
* Boots the application
|
||||
*/
|
||||
boot() {
|
||||
async boot() {
|
||||
console.log("Booting ...");
|
||||
|
||||
await MODS.initMods();
|
||||
|
||||
this.registerStates();
|
||||
this.registerEventListeners();
|
||||
|
||||
|
||||
@ -116,5 +116,8 @@ export default {
|
||||
// Disables slow asserts, useful for debugging performance
|
||||
// disableSlowAsserts: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
// Loads the dev_mod.js for developing new mods
|
||||
// loadDevMod: true,
|
||||
// -----------------------------------------------------------------------------------
|
||||
/* dev:end */
|
||||
};
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
/* typehints:start */
|
||||
import { Entity } from "../game/entity";
|
||||
/* typehints:end */
|
||||
|
||||
export default function (shapez) {
|
||||
class MetaDemoModBuilding extends shapez.MetaBuilding {
|
||||
constructor() {
|
||||
@ -12,10 +8,6 @@ export default function (shapez) {
|
||||
return "red";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the entity at the given location
|
||||
* @param {Entity} entity
|
||||
*/
|
||||
setupEntityComponents(entity) {}
|
||||
}
|
||||
|
||||
@ -36,11 +28,11 @@ export default function (shapez) {
|
||||
|
||||
init() {
|
||||
// Add some custom css
|
||||
this.modInterface.registerCss(`
|
||||
* {
|
||||
font-family: "Comic Sans", "Comic Sans MS", Tahoma !important;
|
||||
}
|
||||
`);
|
||||
// this.modInterface.registerCss(`
|
||||
// button {
|
||||
// font-family: "Comic Sans", "Comic Sans MS", Tahoma !important;
|
||||
// }
|
||||
// `);
|
||||
|
||||
// Replace a builtin sprite
|
||||
this.modInterface.registerSprite("sprites/colors/red.png", RESOURCES["red.png"]);
|
||||
@ -1,4 +1,6 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { getIPCRenderer } from "../core/utils";
|
||||
import { Mod } from "./mod";
|
||||
import { ModInterface } from "./mod_interface";
|
||||
import { MOD_SIGNALS } from "./mod_signals";
|
||||
@ -20,17 +22,39 @@ export class ModLoader {
|
||||
this.initialized = false;
|
||||
|
||||
this.signals = MOD_SIGNALS;
|
||||
|
||||
this.registerMod(/** @type {any} */ (require("./demo_mod").default));
|
||||
}
|
||||
|
||||
linkApp(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
initMods() {
|
||||
anyModsActive() {
|
||||
return this.mods.length > 0;
|
||||
}
|
||||
|
||||
async initMods() {
|
||||
LOG.log("hook:init");
|
||||
|
||||
if (G_IS_STANDALONE) {
|
||||
try {
|
||||
const mods = await getIPCRenderer().invoke("get-mods");
|
||||
|
||||
mods.forEach(modCode => {
|
||||
const registerMod = mod => {
|
||||
this.modLoadQueue.push(mod);
|
||||
};
|
||||
// ugh
|
||||
eval(modCode);
|
||||
});
|
||||
} catch (ex) {
|
||||
alert("Failed to load mods: " + ex);
|
||||
}
|
||||
} else if (G_IS_DEV) {
|
||||
if (globalConfig.debug.loadDevMod) {
|
||||
this.modLoadQueue.push(/** @type {any} */ (require("./dev_mod").default));
|
||||
}
|
||||
}
|
||||
|
||||
let exports = {};
|
||||
|
||||
if (G_IS_DEV || G_IS_STANDALONE) {
|
||||
@ -67,17 +91,6 @@ export class ModLoader {
|
||||
this.modLoadQueue = [];
|
||||
this.signals.postInit.dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {(Object) => (new (Application, ModLoader) => Mod)} mod
|
||||
*/
|
||||
registerMod(mod) {
|
||||
if (this.initialized) {
|
||||
throw new Error("Mods are already initialized, can not add mod afterwards.");
|
||||
}
|
||||
this.modLoadQueue.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
export const MODS = new ModLoader();
|
||||
|
||||
@ -8,6 +8,7 @@ import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||
import {
|
||||
formatSecondsToTimeAgo,
|
||||
generateFileDownload,
|
||||
getIPCRenderer,
|
||||
isSupportedBrowser,
|
||||
makeButton,
|
||||
makeButtonElement,
|
||||
@ -17,6 +18,7 @@ import {
|
||||
waitNextFrame,
|
||||
} from "../core/utils";
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { MODS } from "../mods/modloader";
|
||||
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
|
||||
import { PlatformWrapperImplElectron } from "../platform/electron/wrapper";
|
||||
import { getApplicationSettingById } from "../profile/application_settings";
|
||||
@ -41,6 +43,7 @@ export class MainMenuState extends GameState {
|
||||
const showBrowserWarning = !G_IS_STANDALONE && !isSupportedBrowser();
|
||||
const showPuzzleDLC = !G_WEGAME_VERSION && (G_IS_STANDALONE || G_IS_DEV);
|
||||
const showWegameFooter = G_WEGAME_VERSION;
|
||||
const hasMods = MODS.anyModsActive();
|
||||
|
||||
let showExternalLinks = true;
|
||||
|
||||
@ -94,7 +97,7 @@ export class MainMenuState extends GameState {
|
||||
|
||||
<div class="logo">
|
||||
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
|
||||
${showUpdateLabel ? `<span class="updateLabel">v${G_BUILD_VERSION}!</span>` : ""}
|
||||
${showUpdateLabel ? `<span class="updateLabel">MODS UPDATE!</span>` : ""}
|
||||
</div>
|
||||
|
||||
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
|
||||
@ -112,7 +115,7 @@ export class MainMenuState extends GameState {
|
||||
</div>
|
||||
|
||||
${
|
||||
showPuzzleDLC && ownsPuzzleDLC
|
||||
showPuzzleDLC && ownsPuzzleDLC && !hasMods
|
||||
? `
|
||||
<div class="puzzleContainer">
|
||||
<img class="dlcLogo" src="${cachebust(
|
||||
@ -126,7 +129,7 @@ export class MainMenuState extends GameState {
|
||||
}
|
||||
|
||||
${
|
||||
showPuzzleDLC && !ownsPuzzleDLC
|
||||
showPuzzleDLC && !ownsPuzzleDLC && !hasMods
|
||||
? `
|
||||
<div class="puzzleContainer notOwned">
|
||||
<span class="badge">
|
||||
@ -147,6 +150,49 @@ export class MainMenuState extends GameState {
|
||||
</div>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
hasMods
|
||||
? `
|
||||
|
||||
<div class="modsList">
|
||||
<h3>${T.mainMenu.mods.title}
|
||||
|
||||
</h3>
|
||||
|
||||
<div class="modsList">
|
||||
${MODS.mods
|
||||
.map(mod => {
|
||||
return `
|
||||
<div class="mod">
|
||||
<span class="name">${mod.metadata.name}</span>
|
||||
<span class="version">${T.mainMenu.mods.version.replace(
|
||||
"<version>",
|
||||
mod.metadata.version
|
||||
)}</span>
|
||||
<span class="author">${T.mainMenu.mods.author.replace(
|
||||
"<author>",
|
||||
mod.metadata.authorName
|
||||
)}</span>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("")}
|
||||
</div>
|
||||
|
||||
<div class="dlcHint">
|
||||
${T.mainMenu.modsWarningPuzzleDLC}
|
||||
<button class="styledButton modsOpenFolder">${
|
||||
T.mainMenu.mods.openFolder
|
||||
}</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
${
|
||||
@ -335,6 +381,7 @@ export class MainMenuState extends GameState {
|
||||
".puzzleDlcPlayButton": this.onPuzzleModeButtonClicked,
|
||||
".puzzleDlcGetButton": this.onPuzzleWishlistButtonClicked,
|
||||
".wegameDisclaimer > .rating": this.onWegameRatingClicked,
|
||||
".modsOpenFolder": this.openModsFolder,
|
||||
};
|
||||
|
||||
for (const key in clickHandling) {
|
||||
@ -716,6 +763,14 @@ export class MainMenuState extends GameState {
|
||||
});
|
||||
}
|
||||
|
||||
openModsFolder() {
|
||||
if (!G_IS_STANDALONE) {
|
||||
this.dialogs.showWarning(T.global.error, T.mainMenu.mods.folderOnlyStandalone);
|
||||
return;
|
||||
}
|
||||
getIPCRenderer().send("open-mods-folder");
|
||||
}
|
||||
|
||||
onLeave() {
|
||||
this.dialogs.cleanup();
|
||||
}
|
||||
|
||||
@ -126,6 +126,16 @@ mainMenu:
|
||||
puzzleDlcWishlist: Wishlist now!
|
||||
puzzleDlcViewNow: View Dlc
|
||||
|
||||
modsWarningPuzzleDLC: >-
|
||||
Playing the Puzzle DLC is not possible with mods. Please disable all mods to play the DLC.
|
||||
|
||||
mods:
|
||||
title: Active Mods
|
||||
author: by <author>
|
||||
version: v<version>
|
||||
openFolder: Open Folder
|
||||
folderOnlyStandalone: Opening the mod folder is only possible when running the standalone.
|
||||
|
||||
puzzleMenu:
|
||||
play: Play
|
||||
edit: Edit
|
||||
|
||||
Loading…
Reference in New Issue
Block a user