mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Steam demo
This commit is contained in:
parent
4e5e5c8ef7
commit
7ade7946dc
@ -14,8 +14,6 @@ app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling");
|
|||||||
|
|
||||||
const isDev = app.commandLine.hasSwitch("dev");
|
const isDev = app.commandLine.hasSwitch("dev");
|
||||||
const isLocal = app.commandLine.hasSwitch("local");
|
const isLocal = app.commandLine.hasSwitch("local");
|
||||||
const safeMode = app.commandLine.hasSwitch("safe-mode");
|
|
||||||
const externalMod = app.commandLine.getSwitchValue("load-mod");
|
|
||||||
|
|
||||||
const roamingFolder =
|
const roamingFolder =
|
||||||
process.env.APPDATA ||
|
process.env.APPDATA ||
|
||||||
@ -60,7 +58,7 @@ function createWindow() {
|
|||||||
useContentSize: false,
|
useContentSize: false,
|
||||||
minWidth: 800,
|
minWidth: 800,
|
||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
title: "shapez.io Standalone",
|
title: "shapez.io Demo",
|
||||||
transparent: false,
|
transparent: false,
|
||||||
icon: path.join(__dirname, "favicon" + faviconExtension),
|
icon: path.join(__dirname, "favicon" + faviconExtension),
|
||||||
// fullscreen: true,
|
// fullscreen: true,
|
||||||
@ -341,49 +339,8 @@ ipcMain.handle("fs-job", async (event, job) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("open-mods-folder", async () => {
|
|
||||||
shell.openPath(modsPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Loading mods ...");
|
|
||||||
|
|
||||||
function loadMods() {
|
|
||||||
if (safeMode) {
|
|
||||||
console.log("Safe Mode enabled for mods, skipping mod search");
|
|
||||||
}
|
|
||||||
console.log("Loading mods from", modsPath);
|
|
||||||
let modFiles = safeMode
|
|
||||||
? []
|
|
||||||
: fs
|
|
||||||
.readdirSync(modsPath)
|
|
||||||
.filter(filename => filename.endsWith(".js"))
|
|
||||||
.map(filename => path.join(modsPath, filename));
|
|
||||||
|
|
||||||
if (externalMod) {
|
|
||||||
console.log("Adding external mod source:", externalMod);
|
|
||||||
const externalModPaths = externalMod.split(",");
|
|
||||||
modFiles = modFiles.concat(externalModPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
return modFiles.map(filename => fs.readFileSync(filename, "utf8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mods = [];
|
|
||||||
try {
|
|
||||||
mods = loadMods();
|
|
||||||
console.log("Loaded", mods.length, "mods");
|
|
||||||
} catch (ex) {
|
|
||||||
console.error("Failed to load mods");
|
|
||||||
dialog.showErrorBox("Failed to load mods:", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.handle("get-mods", async () => {
|
ipcMain.handle("get-mods", async () => {
|
||||||
return mods;
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
steam.init(isDev);
|
steam.init(isDev);
|
||||||
|
|
||||||
// Only allow achievements and puzzle DLC if no mods are loaded
|
|
||||||
if (mods.length === 0) {
|
|
||||||
steam.listen();
|
|
||||||
}
|
|
||||||
|
@ -151,10 +151,10 @@ function gulptasksStandalone($, gulp) {
|
|||||||
asar: asar,
|
asar: asar,
|
||||||
executableName: "shapezio",
|
executableName: "shapezio",
|
||||||
icon: path.join(electronBaseDir, "favicon"),
|
icon: path.join(electronBaseDir, "favicon"),
|
||||||
name: "shapez.io-standalone" + suffix,
|
name: "shapez.io-demo" + suffix,
|
||||||
out: tempDestDir,
|
out: tempDestDir,
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
appBundleId: "io.shapez.standalone",
|
appBundleId: "io.shapez.demo",
|
||||||
appCategoryType: "public.app-category.games",
|
appCategoryType: "public.app-category.games",
|
||||||
}).then(
|
}).then(
|
||||||
appPaths => {
|
appPaths => {
|
||||||
|
BIN
res/logo.png
BIN
res/logo.png
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
@ -63,7 +63,6 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
onHasLegacySavegamesChanged(has119Savegames = false) {
|
onHasLegacySavegamesChanged(has119Savegames = false) {
|
||||||
if (has119Savegames && !this.currentData.savegameV1119Imported) {
|
if (has119Savegames && !this.currentData.savegameV1119Imported) {
|
||||||
this.currentData.savegameV1119Imported = true;
|
this.currentData.savegameV1119Imported = true;
|
||||||
console.warn("Current user now has access to all levels due to 1119 savegame");
|
|
||||||
return this.writeAsync();
|
return this.writeAsync();
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@ -74,25 +73,6 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isLimitedVersion() {
|
isLimitedVersion() {
|
||||||
if (IS_MAC) {
|
|
||||||
// On mac, the full version is always active
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (G_IS_STANDALONE) {
|
|
||||||
// Standalone is never limited
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queryParamOptions.embedProvider === "gamedistribution") {
|
|
||||||
// also full version on gamedistribution
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (G_IS_DEV) {
|
|
||||||
return typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +81,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getIsStandaloneMarketingActive() {
|
getIsStandaloneMarketingActive() {
|
||||||
return this.isLimitedVersion();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,7 +89,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getIsExportingScreenshotsPossible() {
|
getIsExportingScreenshotsPossible() {
|
||||||
return !this.isLimitedVersion();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,7 +97,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
getMaximumWaypoints() {
|
getMaximumWaypoints() {
|
||||||
return this.isLimitedVersion() ? 2 : 1e20;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +105,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getHasUnlimitedSavegames() {
|
getHasUnlimitedSavegames() {
|
||||||
return !this.isLimitedVersion();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,7 +113,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getHasExtendedSettings() {
|
getHasExtendedSettings() {
|
||||||
return !this.isLimitedVersion();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,7 +121,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getHasExtendedUpgrades() {
|
getHasExtendedUpgrades() {
|
||||||
return !this.isLimitedVersion() || this.currentData.savegameV1119Imported;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,6 +129,6 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
getHasExtendedLevelsAndFreeplay() {
|
getHasExtendedLevelsAndFreeplay() {
|
||||||
return !this.isLimitedVersion() || this.currentData.savegameV1119Imported;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
import { Application } from "../application";
|
import { Application } from "../application";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
import { globalConfig } from "../core/config";
|
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import { StorageImplBrowserIndexedDB } from "../platform/browser/storage_indexed_db";
|
|
||||||
import { StorageImplElectron } from "../platform/electron/storage";
|
|
||||||
import { FILE_NOT_FOUND } from "../platform/storage";
|
|
||||||
import { Mod } from "./mod";
|
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");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,165 +97,9 @@ export class ModLoader {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
exposeExports() {
|
exposeExports() {}
|
||||||
if (G_IS_DEV || G_IS_STANDALONE) {
|
|
||||||
let exports = {};
|
|
||||||
const modules = require.context("../", true, /\.js$/);
|
|
||||||
Array.from(modules.keys()).forEach(key => {
|
|
||||||
// @ts-ignore
|
|
||||||
const module = modules(key);
|
|
||||||
for (const member in module) {
|
|
||||||
if (member === "default" || member === "__$S__") {
|
|
||||||
// Setter
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (exports[member]) {
|
|
||||||
throw new Error("Duplicate export of " + member);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(exports, member, {
|
|
||||||
get() {
|
|
||||||
return module[member];
|
|
||||||
},
|
|
||||||
set(v) {
|
|
||||||
module.__$S__(member, v);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.shapez = exports;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async initMods() {
|
async initMods() {
|
||||||
if (!G_IS_STANDALONE && !G_IS_DEV) {
|
|
||||||
this.initialized = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a storage for reading mod settings
|
|
||||||
const storage = G_IS_STANDALONE
|
|
||||||
? new StorageImplElectron(this.app)
|
|
||||||
: new StorageImplBrowserIndexedDB(this.app);
|
|
||||||
await storage.initialize();
|
|
||||||
|
|
||||||
LOG.log("hook:init", this.app, this.app.storage);
|
|
||||||
this.exposeExports();
|
|
||||||
|
|
||||||
let mods = [];
|
|
||||||
if (G_IS_STANDALONE) {
|
|
||||||
mods = await ipcRenderer.invoke("get-mods");
|
|
||||||
}
|
|
||||||
if (G_IS_DEV && globalConfig.debug.externalModUrl) {
|
|
||||||
const modURLs = Array.isArray(globalConfig.debug.externalModUrl)
|
|
||||||
? globalConfig.debug.externalModUrl
|
|
||||||
: [globalConfig.debug.externalModUrl];
|
|
||||||
|
|
||||||
for (let i = 0; i < modURLs.length; i++) {
|
|
||||||
const response = await fetch(modURLs[i], {
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error(
|
|
||||||
"Failed to load " + modURLs[i] + ": " + response.status + " " + response.statusText
|
|
||||||
);
|
|
||||||
}
|
|
||||||
mods.push(await response.text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.$shapez_registerMod = (modClass, meta) => {
|
|
||||||
if (this.initialized) {
|
|
||||||
throw new Error("Can't register mod after modloader is initialized");
|
|
||||||
}
|
|
||||||
if (this.modLoadQueue.some(entry => entry.meta.id === meta.id)) {
|
|
||||||
console.warn("Not registering mod", meta, "since a mod with the same id is already loaded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.modLoadQueue.push({
|
|
||||||
modClass,
|
|
||||||
meta,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
mods.forEach(modCode => {
|
|
||||||
modCode += `
|
|
||||||
if (typeof Mod !== 'undefined') {
|
|
||||||
if (typeof METADATA !== 'object') {
|
|
||||||
throw new Error("No METADATA variable found");
|
|
||||||
}
|
|
||||||
window.$shapez_registerMod(Mod, METADATA);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
try {
|
|
||||||
const func = new Function(modCode);
|
|
||||||
func();
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(ex);
|
|
||||||
alert("Failed to parse mod (launch with --dev for more info): \n\n" + ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
delete window.$shapez_registerMod;
|
|
||||||
|
|
||||||
for (let i = 0; i < this.modLoadQueue.length; i++) {
|
|
||||||
const { modClass, meta } = this.modLoadQueue[i];
|
|
||||||
const modDataFile = "modsettings_" + meta.id + "__" + meta.version + ".json";
|
|
||||||
|
|
||||||
if (meta.minimumGameVersion) {
|
|
||||||
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) {
|
|
||||||
try {
|
|
||||||
const storedSettings = await storage.readFileAsync(modDataFile);
|
|
||||||
settings = JSON.parse(storedSettings);
|
|
||||||
} catch (ex) {
|
|
||||||
if (ex === FILE_NOT_FOUND) {
|
|
||||||
// Write default data
|
|
||||||
await storage.writeFileAsync(modDataFile, JSON.stringify(meta.settings));
|
|
||||||
} else {
|
|
||||||
alert("Failed to load settings for " + meta.id + ", will use defaults:\n\n" + ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const mod = new modClass({
|
|
||||||
app: this.app,
|
|
||||||
modLoader: this,
|
|
||||||
meta,
|
|
||||||
settings,
|
|
||||||
saveSettings: () => storage.writeFileAsync(modDataFile, JSON.stringify(mod.settings)),
|
|
||||||
});
|
|
||||||
mod.init();
|
|
||||||
this.mods.push(mod);
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(ex);
|
|
||||||
alert("Failed to initialize mods (launch with --dev for more info): \n\n" + ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.modLoadQueue = [];
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,7 @@ const analyticsLocalFile = "shapez_token_123.bin";
|
|||||||
|
|
||||||
export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
||||||
get environment() {
|
get environment() {
|
||||||
if (G_IS_DEV) {
|
return "steam-demo";
|
||||||
return "dev";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (G_IS_STANDALONE) {
|
|
||||||
return "steam";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (G_IS_RELEASE) {
|
|
||||||
return "prod";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.location.host.indexOf("alpha") >= 0) {
|
|
||||||
return "alpha";
|
|
||||||
} else {
|
|
||||||
return "beta";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,14 +31,10 @@ export const SOUNDS = {
|
|||||||
export const MUSIC = {
|
export const MUSIC = {
|
||||||
// The theme always depends on the standalone only, even if running the full
|
// The theme always depends on the standalone only, even if running the full
|
||||||
// version in the browser
|
// version in the browser
|
||||||
theme: G_IS_STANDALONE ? "theme-full" : "theme-short",
|
theme: "theme-short",
|
||||||
menu: "menu",
|
menu: "menu",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (G_IS_STANDALONE || G_IS_DEV) {
|
|
||||||
MUSIC.puzzle = "puzzle-full";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SoundInstanceInterface {
|
export class SoundInstanceInterface {
|
||||||
constructor(key, url) {
|
constructor(key, url) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
@ -186,10 +186,7 @@ function initializeSettings() {
|
|||||||
if (app.platformWrapper.getSupportsFullscreen()) {
|
if (app.platformWrapper.getSupportsFullscreen()) {
|
||||||
app.platformWrapper.setFullscreen(value);
|
app.platformWrapper.setFullscreen(value);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
/**
|
|
||||||
* @param {Application} app
|
|
||||||
*/ app => app.restrictionMgr.getHasExtendedSettings()
|
|
||||||
),
|
),
|
||||||
|
|
||||||
new BoolSetting(
|
new BoolSetting(
|
||||||
|
@ -38,16 +38,15 @@ export class MainMenuState extends GameState {
|
|||||||
getInnerHTML() {
|
getInnerHTML() {
|
||||||
const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION;
|
const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION;
|
||||||
const showExitAppButton = G_IS_STANDALONE;
|
const showExitAppButton = G_IS_STANDALONE;
|
||||||
const showUpdateLabel = !G_WEGAME_VERSION;
|
|
||||||
const showBrowserWarning = !G_IS_STANDALONE && !isSupportedBrowser();
|
const showBrowserWarning = !G_IS_STANDALONE && !isSupportedBrowser();
|
||||||
const showPuzzleDLC = !G_WEGAME_VERSION && (G_IS_STANDALONE || G_IS_DEV);
|
const showPuzzleDLC = false;
|
||||||
const showWegameFooter = G_WEGAME_VERSION;
|
const showWegameFooter = G_WEGAME_VERSION;
|
||||||
const hasMods = MODS.anyModsActive();
|
const hasMods = false;
|
||||||
|
|
||||||
let showExternalLinks = true;
|
let showExternalLinks = true;
|
||||||
|
|
||||||
if (G_IS_STANDALONE) {
|
if (G_IS_STANDALONE) {
|
||||||
if (G_WEGAME_VERSION || G_CHINA_VERSION) {
|
if (G_WEGAME_VERSION) {
|
||||||
showExternalLinks = false;
|
showExternalLinks = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -58,19 +57,12 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let showDiscordLink = showExternalLinks;
|
let showDiscordLink = showExternalLinks;
|
||||||
if (G_CHINA_VERSION) {
|
|
||||||
showDiscordLink = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showCrosspromo = !G_IS_STANDALONE && showExternalLinks;
|
const showCrosspromo = false;
|
||||||
const showDemoAdvertisement =
|
const showDemoAdvertisement =
|
||||||
showExternalLinks && this.app.restrictionMgr.getIsStandaloneMarketingActive();
|
showExternalLinks && this.app.restrictionMgr.getIsStandaloneMarketingActive();
|
||||||
|
|
||||||
const ownsPuzzleDLC =
|
const ownsPuzzleDLC = false;
|
||||||
G_IS_DEV ||
|
|
||||||
(G_IS_STANDALONE &&
|
|
||||||
/** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle);
|
|
||||||
|
|
||||||
const bannerHtml = `
|
const bannerHtml = `
|
||||||
<h3>${T.demoBanners.title}</h3>
|
<h3>${T.demoBanners.title}</h3>
|
||||||
<p>${T.demoBanners.intro}</p>
|
<p>${T.demoBanners.intro}</p>
|
||||||
@ -96,7 +88,6 @@ export class MainMenuState extends GameState {
|
|||||||
|
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
|
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
|
||||||
${showUpdateLabel ? `<span class="updateLabel">MODS UPDATE!</span>` : ""}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
|
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { THIRDPARTY_URLS } from "../core/config";
|
import { THIRDPARTY_URLS } from "../core/config";
|
||||||
import { TextualGameState } from "../core/textual_game_state";
|
import { TextualGameState } from "../core/textual_game_state";
|
||||||
import { MODS } from "../mods/modloader";
|
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
|
|
||||||
export class ModsState extends TextualGameState {
|
export class ModsState extends TextualGameState {
|
||||||
@ -14,35 +13,29 @@ export class ModsState extends TextualGameState {
|
|||||||
|
|
||||||
internalGetFullHtml() {
|
internalGetFullHtml() {
|
||||||
let headerHtml = `
|
let headerHtml = `
|
||||||
<div class="headerBar">
|
<div class="headerBar">
|
||||||
<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
|
G_IS_STANDALONE
|
||||||
? `<button class="styledButton browseMods">${T.mods.browseMods}</button>`
|
? `<button class="styledButton browseMods">${T.mods.browseMods}</button>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
${
|
</div>
|
||||||
G_IS_STANDALONE || G_IS_DEV
|
|
||||||
? `<button class="styledButton openModsFolder">${T.mods.openFolder}</button>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
${headerHtml}
|
${headerHtml}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
${this.getInnerHTML()}
|
${this.getInnerHTML()}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMainContentHTML() {
|
getMainContentHTML() {
|
||||||
if (!G_IS_STANDALONE && !G_IS_DEV) {
|
return `
|
||||||
return `
|
|
||||||
<div class="noModSupport">
|
<div class="noModSupport">
|
||||||
|
|
||||||
<p>${T.mods.noModSupport}</p>
|
<p>${T.mods.noModSupport}</p>
|
||||||
@ -52,49 +45,6 @@ export class ModsState extends TextualGameState {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
|
||||||
|
|
||||||
if (MODS.mods.length === 0) {
|
|
||||||
return `
|
|
||||||
|
|
||||||
<div class="modsStats noMods">
|
|
||||||
${T.mods.modsInfo}
|
|
||||||
|
|
||||||
<button class="styledButton browseMods">${T.mods.browseMods}</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let modsHtml = ``;
|
|
||||||
|
|
||||||
MODS.mods.forEach(mod => {
|
|
||||||
modsHtml += `
|
|
||||||
<div class="mod">
|
|
||||||
<div class="mainInfo">
|
|
||||||
<span class="name">${mod.metadata.name}</span>
|
|
||||||
<span class="description">${mod.metadata.description}</span>
|
|
||||||
<a class="website" href="${mod.metadata.website}" target="_blank">${T.mods.modWebsite}</a>
|
|
||||||
</div>
|
|
||||||
<span class="version"><strong>${T.mods.version}</strong>${mod.metadata.version}</span>
|
|
||||||
<span class="author"><strong>${T.mods.author}</strong>${mod.metadata.author}</span>
|
|
||||||
<div class="value checkbox checked">
|
|
||||||
<span class="knob"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
return `
|
|
||||||
|
|
||||||
<div class="modsStats">
|
|
||||||
${T.mods.modsInfo}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modsList">
|
|
||||||
${modsHtml}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnter() {
|
onEnter() {
|
||||||
|
@ -74,7 +74,7 @@ mainMenu:
|
|||||||
puzzleDlcWishlist: 添加愿望单!
|
puzzleDlcWishlist: 添加愿望单!
|
||||||
puzzleDlcViewNow: 查看DLC
|
puzzleDlcViewNow: 查看DLC
|
||||||
mods:
|
mods:
|
||||||
title: 激活游戏模组(Mods)
|
title: 激活游戏模组
|
||||||
warningPuzzleDLC: 无法在任何游戏模组(Mods)下进行“谜题挑战者”DLC,请关闭所有游戏模组(Mods)。
|
warningPuzzleDLC: 无法在任何游戏模组(Mods)下进行“谜题挑战者”DLC,请关闭所有游戏模组(Mods)。
|
||||||
dialogs:
|
dialogs:
|
||||||
buttons:
|
buttons:
|
||||||
@ -139,7 +139,8 @@ dialogs:
|
|||||||
desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。
|
desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。
|
||||||
keybindingsIntroduction:
|
keybindingsIntroduction:
|
||||||
title: 实用快捷键
|
title: 实用快捷键
|
||||||
desc: 这个游戏有很多有用的快捷键设定。以下是其中的一些介绍,记得在<strong>按键设置</strong>中查看其他按键设定!<br><br>
|
desc:
|
||||||
|
这个游戏有很多有用的快捷键设定。以下是其中的一些介绍,记得在<strong>按键设置</strong>中查看其他按键设定!<br><br>
|
||||||
<code class='keybinding'>CTRL键</code> + 拖动:选择区域以复制或删除。<br> <code
|
<code class='keybinding'>CTRL键</code> + 拖动:选择区域以复制或删除。<br> <code
|
||||||
class='keybinding'>SHIFT键</code>: 按住以放置多个同一种设施。<br> <code
|
class='keybinding'>SHIFT键</code>: 按住以放置多个同一种设施。<br> <code
|
||||||
class='keybinding'>ALT键</code>:反向放置传送带。<br>
|
class='keybinding'>ALT键</code>:反向放置传送带。<br>
|
||||||
@ -318,15 +319,18 @@ ingame:
|
|||||||
hints:
|
hints:
|
||||||
1_1_extractor: 在<strong>圆形</strong>上放置一个<strong>开采器</strong>来获取圆形!<br><br>提示:<strong>按下鼠标左键</strong>选中<strong>开采器</strong>
|
1_1_extractor: 在<strong>圆形</strong>上放置一个<strong>开采器</strong>来获取圆形!<br><br>提示:<strong>按下鼠标左键</strong>选中<strong>开采器</strong>
|
||||||
1_2_conveyor: 用<strong>传送带</strong>将您的开采器连接到中心基地上!<br><br>提示:选中<strong>传送带</strong>后<strong>按下鼠标左键可拖动</strong>布置传送带!
|
1_2_conveyor: 用<strong>传送带</strong>将您的开采器连接到中心基地上!<br><br>提示:选中<strong>传送带</strong>后<strong>按下鼠标左键可拖动</strong>布置传送带!
|
||||||
1_3_expand: 您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
1_3_expand:
|
||||||
|
您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
||||||
提示:按住 <strong>SHIFT</strong>
|
提示:按住 <strong>SHIFT</strong>
|
||||||
键可放置多个<strong>开采器</strong>,注意用<strong>R</strong>
|
键可放置多个<strong>开采器</strong>,注意用<strong>R</strong>
|
||||||
键可旋转<strong>开采器</strong>的出口方向,确保开采的图形可以顺利传送。
|
键可旋转<strong>开采器</strong>的出口方向,确保开采的图形可以顺利传送。
|
||||||
2_1_place_cutter: 现在放置一个<strong>切割器</strong>,这个设施可把<strong>圆形</strong>切成两半!<br><br>注意:无论如何放置,切割机总是<strong>从上到下</strong>切割。
|
2_1_place_cutter: 现在放置一个<strong>切割器</strong>,这个设施可把<strong>圆形</strong>切成两半!<br><br>注意:无论如何放置,切割机总是<strong>从上到下</strong>切割。
|
||||||
2_2_place_trash: 使用切割机后产生的废弃图形会导致<strong>堵塞</strong>。<br><br>注意使用<strong>垃圾桶</strong>清除当前
|
2_2_place_trash:
|
||||||
|
使用切割机后产生的废弃图形会导致<strong>堵塞</strong>。<br><br>注意使用<strong>垃圾桶</strong>清除当前
|
||||||
(!) 不需要的废物。
|
(!) 不需要的废物。
|
||||||
2_3_more_cutters: 干的好!现在放置<strong>2个以上的切割机</strong>来加快当前缓慢的过程!<br><br>提示:用<strong>快捷键0-9</strong>可以快速选择各项设施!
|
2_3_more_cutters: 干的好!现在放置<strong>2个以上的切割机</strong>来加快当前缓慢的过程!<br><br>提示:用<strong>快捷键0-9</strong>可以快速选择各项设施!
|
||||||
3_1_rectangles: 现在让我们开采一些矩形!找到<strong>矩形地带</strong>并<strong>放置4个开采器</strong>并将它们用<strong>传送带</strong>连接到中心基地。<br><br>
|
3_1_rectangles:
|
||||||
|
现在让我们开采一些矩形!找到<strong>矩形地带</strong>并<strong>放置4个开采器</strong>并将它们用<strong>传送带</strong>连接到中心基地。<br><br>
|
||||||
提示:选中<strong>传送带</strong>后按住<strong>SHIFT键</strong>可快速准确地规划<strong>传送带路线!</strong>
|
提示:选中<strong>传送带</strong>后按住<strong>SHIFT键</strong>可快速准确地规划<strong>传送带路线!</strong>
|
||||||
21_1_place_quad_painter: 放置<strong>四口上色器</strong>并且获取一些<strong>圆形</strong>,<strong>白色</strong>和<strong>红色</strong>!
|
21_1_place_quad_painter: 放置<strong>四口上色器</strong>并且获取一些<strong>圆形</strong>,<strong>白色</strong>和<strong>红色</strong>!
|
||||||
21_2_switch_to_wires: 按 <strong>E</strong> 键选择<strong>电线层</strong>!<br><br>
|
21_2_switch_to_wires: 按 <strong>E</strong> 键选择<strong>电线层</strong>!<br><br>
|
||||||
@ -620,7 +624,8 @@ storyRewards:
|
|||||||
desc: 恭喜!您解锁了<strong>旋转机</strong>。它会顺时针将输入的<strong>图形旋转90度</strong>。
|
desc: 恭喜!您解锁了<strong>旋转机</strong>。它会顺时针将输入的<strong>图形旋转90度</strong>。
|
||||||
reward_painter:
|
reward_painter:
|
||||||
title: 上色
|
title: 上色
|
||||||
desc: 恭喜!您解锁了<strong>上色器</strong>。开采一些颜色(就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
desc:
|
||||||
|
恭喜!您解锁了<strong>上色器</strong>。开采一些颜色(就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
||||||
<br>注意:如果您不幸患有色盲,可以在设置中启用<strong>色盲模式</strong>
|
<br>注意:如果您不幸患有色盲,可以在设置中启用<strong>色盲模式</strong>
|
||||||
reward_mixer:
|
reward_mixer:
|
||||||
title: 混合颜色
|
title: 混合颜色
|
||||||
@ -637,11 +642,13 @@ storyRewards:
|
|||||||
desc: 恭喜!您解锁了<strong>隧道</strong>。它可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
desc: 恭喜!您解锁了<strong>隧道</strong>。它可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
||||||
reward_rotater_ccw:
|
reward_rotater_ccw:
|
||||||
title: 逆时针旋转
|
title: 逆时针旋转
|
||||||
desc: 恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
desc:
|
||||||
|
恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
||||||
<br>选择<strong>旋转机</strong>然后按"T"键来选取这个变体。
|
<br>选择<strong>旋转机</strong>然后按"T"键来选取这个变体。
|
||||||
reward_miner_chainable:
|
reward_miner_chainable:
|
||||||
title: 链式开采器
|
title: 链式开采器
|
||||||
desc: 您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
desc:
|
||||||
|
您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
||||||
注意:新的开采器已替换了工具栏里旧的开采器!
|
注意:新的开采器已替换了工具栏里旧的开采器!
|
||||||
reward_underground_belt_tier_2:
|
reward_underground_belt_tier_2:
|
||||||
title: 二级隧道
|
title: 二级隧道
|
||||||
@ -658,12 +665,14 @@ storyRewards:
|
|||||||
<br>它<strong>优先从左边</strong>输出,这样您就可以用它做一个<strong>溢流门</strong>了!
|
<br>它<strong>优先从左边</strong>输出,这样您就可以用它做一个<strong>溢流门</strong>了!
|
||||||
reward_freeplay:
|
reward_freeplay:
|
||||||
title: 自由模式
|
title: 自由模式
|
||||||
desc: 成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
desc:
|
||||||
|
成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
||||||
从现在起,中心基地最为需要的是<strong>产量</strong>,我强烈建议您去制造一台能够自动交付所需图形的机器!<br><br>
|
从现在起,中心基地最为需要的是<strong>产量</strong>,我强烈建议您去制造一台能够自动交付所需图形的机器!<br><br>
|
||||||
基地会在<strong>电线层</strong>输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。
|
基地会在<strong>电线层</strong>输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。
|
||||||
reward_blueprints:
|
reward_blueprints:
|
||||||
title: 蓝图
|
title: 蓝图
|
||||||
desc: 您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
desc:
|
||||||
|
您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
||||||
<br><br>粘贴并<strong>不是免费的</strong>,您需要制造<strong>蓝图图形</strong>来负担。蓝图图形是您刚刚交付的图形。
|
<br><br>粘贴并<strong>不是免费的</strong>,您需要制造<strong>蓝图图形</strong>来负担。蓝图图形是您刚刚交付的图形。
|
||||||
no_reward:
|
no_reward:
|
||||||
title: 下一关
|
title: 下一关
|
||||||
@ -691,7 +700,8 @@ storyRewards:
|
|||||||
<br>注意:您注意到<strong>传送读取器</strong>和<strong>存储器</strong>输出的他们最后读取的物品了吗?试着在显示屏上展示一下!
|
<br>注意:您注意到<strong>传送读取器</strong>和<strong>存储器</strong>输出的他们最后读取的物品了吗?试着在显示屏上展示一下!
|
||||||
reward_constant_signal:
|
reward_constant_signal:
|
||||||
title: 恒定信号
|
title: 恒定信号
|
||||||
desc: 恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
desc:
|
||||||
|
恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
||||||
<br>比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。
|
<br>比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。
|
||||||
reward_logic_gates:
|
reward_logic_gates:
|
||||||
title: 逻辑门
|
title: 逻辑门
|
||||||
@ -710,7 +720,8 @@ storyRewards:
|
|||||||
<strong>提示</strong>:可在设置中打开电线层教程!
|
<strong>提示</strong>:可在设置中打开电线层教程!
|
||||||
reward_filter:
|
reward_filter:
|
||||||
title: 物品过滤器
|
title: 物品过滤器
|
||||||
desc: 恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
desc:
|
||||||
|
恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
||||||
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
||||||
reward_demo_end:
|
reward_demo_end:
|
||||||
title: 试玩结束
|
title: 试玩结束
|
||||||
@ -1093,7 +1104,7 @@ backendErrors:
|
|||||||
too-many-likes-already: 您的谜题已经得到了许多玩家的赞赏。如果您仍然希望删除它,请联系support@shapez.io!
|
too-many-likes-already: 您的谜题已经得到了许多玩家的赞赏。如果您仍然希望删除它,请联系support@shapez.io!
|
||||||
no-permission: 您没有执行此操作的权限。
|
no-permission: 您没有执行此操作的权限。
|
||||||
mods:
|
mods:
|
||||||
title: 游戏模组(Mods)
|
title: 游戏模组
|
||||||
author: 作者
|
author: 作者
|
||||||
version: 版本
|
version: 版本
|
||||||
openFolder: 打开游戏模组(Mods)文件夹
|
openFolder: 打开游戏模组(Mods)文件夹
|
||||||
@ -1103,6 +1114,7 @@ mods:
|
|||||||
noModSupport: 您需要在Steam平台获得完整版才可以安装游戏模组(Mods)。
|
noModSupport: 您需要在Steam平台获得完整版才可以安装游戏模组(Mods)。
|
||||||
togglingComingSoon:
|
togglingComingSoon:
|
||||||
title: 即将开放
|
title: 即将开放
|
||||||
description: 当前只能通过将游戏模组(Mods)文件复制到mods文件夹或从mods文件夹移除来启用或禁用游戏模组(Mods)。
|
description:
|
||||||
|
当前只能通过将游戏模组(Mods)文件复制到mods文件夹或从mods文件夹移除来启用或禁用游戏模组(Mods)。
|
||||||
但是,可以切换游戏模组(Mods)已经计划在之后的更新中实现!
|
但是,可以切换游戏模组(Mods)已经计划在之后的更新中实现!
|
||||||
modWebsite: Website
|
modWebsite: Website
|
||||||
|
Loading…
Reference in New Issue
Block a user