mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Wegame adjustments
This commit is contained in:
parent
34754964d3
commit
9c1bac5afe
@ -1,30 +1,39 @@
|
|||||||
/* eslint-disable quotes,no-undef */
|
/* eslint-disable quotes,no-undef */
|
||||||
|
|
||||||
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell } = require("electron");
|
const { app, BrowserWindow, Menu, MenuItem, ipcMain, shell, dialog, session } = require("electron");
|
||||||
|
|
||||||
app.commandLine.appendSwitch("in-process-gpu");
|
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const wegame = require("./wegame");
|
const wegame = require("./wegame");
|
||||||
const asyncLock = require("async-lock");
|
const asyncLock = require("async-lock");
|
||||||
|
const windowStateKeeper = require("electron-window-state");
|
||||||
|
|
||||||
const isDev = process.argv.indexOf("--dev") >= 0;
|
// Disable hardware key handling, i.e. being able to pause/resume the game music
|
||||||
const isLocal = process.argv.indexOf("--local") >= 0;
|
// with hardware keys
|
||||||
|
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling");
|
||||||
|
|
||||||
|
const isDev = app.commandLine.hasSwitch("dev");
|
||||||
|
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 ||
|
||||||
(process.platform == "darwin"
|
(process.platform == "darwin"
|
||||||
? process.env.HOME + "/Library/Preferences"
|
? process.env.HOME + "/Library/Preferences"
|
||||||
: process.env.HOME + "/.local/share");
|
: process.env.HOME + "/.local/share");
|
||||||
let storePath = path.join(roamingFolder, "shapez-wegame", "saves");
|
let storePath = path.join(roamingFolder, "shapez-china", "saves");
|
||||||
|
let modsPath = path.join(roamingFolder, "shapez-china", "mods");
|
||||||
|
|
||||||
if (!fs.existsSync(storePath)) {
|
if (!fs.existsSync(storePath)) {
|
||||||
// No try-catch by design
|
// No try-catch by design
|
||||||
fs.mkdirSync(storePath, { recursive: true });
|
fs.mkdirSync(storePath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(modsPath)) {
|
||||||
|
fs.mkdirSync(modsPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {BrowserWindow} */
|
/** @type {BrowserWindow} */
|
||||||
let win = null;
|
let win = null;
|
||||||
let menu = null;
|
let menu = null;
|
||||||
@ -35,30 +44,44 @@ function createWindow() {
|
|||||||
faviconExtension = ".ico";
|
faviconExtension = ".ico";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mainWindowState = windowStateKeeper({
|
||||||
|
defaultWidth: 1000,
|
||||||
|
defaultHeight: 800,
|
||||||
|
});
|
||||||
|
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
width: 1280,
|
x: mainWindowState.x,
|
||||||
height: 800,
|
y: mainWindowState.y,
|
||||||
|
width: mainWindowState.width,
|
||||||
|
height: mainWindowState.height,
|
||||||
show: false,
|
show: false,
|
||||||
backgroundColor: "#222428",
|
backgroundColor: "#222428",
|
||||||
useContentSize: true,
|
useContentSize: false,
|
||||||
minWidth: 800,
|
minWidth: 800,
|
||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
title: "图形工厂",
|
title: "图形工厂",
|
||||||
transparent: false,
|
transparent: false,
|
||||||
icon: path.join(__dirname, "favicon" + faviconExtension),
|
icon: path.join(__dirname, "favicon" + faviconExtension),
|
||||||
// fullscreen: true,
|
// fullscreen: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: !isDev,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
|
nodeIntegrationInWorker: false,
|
||||||
|
nodeIntegrationInSubFrames: false,
|
||||||
|
contextIsolation: true,
|
||||||
|
enableRemoteModule: false,
|
||||||
|
disableBlinkFeatures: "Auxclick",
|
||||||
|
|
||||||
webSecurity: true,
|
webSecurity: true,
|
||||||
sandbox: true,
|
sandbox: true,
|
||||||
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: path.join(__dirname, "preload.js"),
|
preload: path.join(__dirname, "preload.js"),
|
||||||
|
experimentalFeatures: false,
|
||||||
},
|
},
|
||||||
allowRunningInsecureContent: false,
|
allowRunningInsecureContent: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindowState.manage(win);
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
win.loadURL("http://localhost:3005");
|
win.loadURL("http://localhost:3005");
|
||||||
} else {
|
} else {
|
||||||
@ -70,12 +93,70 @@ function createWindow() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
win.webContents.session.clearCache(() => null);
|
win.webContents.session.clearCache();
|
||||||
win.webContents.session.clearStorageData();
|
win.webContents.session.clearStorageData();
|
||||||
|
|
||||||
|
////// SECURITY
|
||||||
|
|
||||||
|
// Disable permission requests
|
||||||
|
win.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||||
|
callback(false);
|
||||||
|
});
|
||||||
|
session.fromPartition("default").setPermissionRequestHandler((webContents, permission, callback) => {
|
||||||
|
callback(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("web-contents-created", (event, contents) => {
|
||||||
|
// Disable vewbiew
|
||||||
|
contents.on("will-attach-webview", (event, webPreferences, params) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
// Disable navigation
|
||||||
|
contents.on("will-navigate", (event, navigationUrl) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
win.webContents.on("will-redirect", (contentsEvent, navigationUrl) => {
|
||||||
|
// Log and prevent the app from redirecting to a new page
|
||||||
|
console.error(
|
||||||
|
`The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.`
|
||||||
|
);
|
||||||
|
contentsEvent.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter loading any module via remote;
|
||||||
|
// you shouldn't be using remote at all, though
|
||||||
|
// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module
|
||||||
|
app.on("remote-require", (event, webContents, moduleName) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// built-ins are modules such as "app"
|
||||||
|
app.on("remote-get-builtin", (event, webContents, moduleName) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("remote-get-global", (event, webContents, globalName) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("remote-get-current-window", (event, webContents) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("remote-get-current-web-contents", (event, webContents) => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
//// END SECURITY
|
||||||
|
|
||||||
win.webContents.on("new-window", (event, pth) => {
|
win.webContents.on("new-window", (event, pth) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (pth.startsWith("https://") || pth.startsWith("steam://")) {
|
||||||
shell.openExternal(pth);
|
shell.openExternal(pth);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
win.on("closed", () => {
|
win.on("closed", () => {
|
||||||
@ -86,6 +167,8 @@ function createWindow() {
|
|||||||
if (isDev) {
|
if (isDev) {
|
||||||
menu = new Menu();
|
menu = new Menu();
|
||||||
|
|
||||||
|
win.webContents.toggleDevTools();
|
||||||
|
|
||||||
const mainItem = new MenuItem({
|
const mainItem = new MenuItem({
|
||||||
label: "Toggle Dev Tools",
|
label: "Toggle Dev Tools",
|
||||||
click: () => win.webContents.toggleDevTools(),
|
click: () => win.webContents.toggleDevTools(),
|
||||||
@ -94,7 +177,7 @@ function createWindow() {
|
|||||||
menu.append(mainItem);
|
menu.append(mainItem);
|
||||||
|
|
||||||
const reloadItem = new MenuItem({
|
const reloadItem = new MenuItem({
|
||||||
label: "Restart",
|
label: "Reload",
|
||||||
click: () => win.reload(),
|
click: () => win.reload(),
|
||||||
accelerator: "F5",
|
accelerator: "F5",
|
||||||
});
|
});
|
||||||
@ -107,7 +190,15 @@ function createWindow() {
|
|||||||
});
|
});
|
||||||
menu.append(fullscreenItem);
|
menu.append(fullscreenItem);
|
||||||
|
|
||||||
Menu.setApplicationMenu(menu);
|
const mainMenu = new Menu();
|
||||||
|
mainMenu.append(
|
||||||
|
new MenuItem({
|
||||||
|
label: "shapez.io",
|
||||||
|
submenu: menu,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(mainMenu);
|
||||||
} else {
|
} else {
|
||||||
Menu.setApplicationMenu(null);
|
Menu.setApplicationMenu(null);
|
||||||
}
|
}
|
||||||
@ -121,7 +212,7 @@ function createWindow() {
|
|||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
} else {
|
} else {
|
||||||
app.on("second-instance", (event, commandLine, workingDirectory) => {
|
app.on("second-instance", () => {
|
||||||
// Someone tried to run a second instance, we should focus
|
// Someone tried to run a second instance, we should focus
|
||||||
if (win) {
|
if (win) {
|
||||||
if (win.isMinimized()) {
|
if (win.isMinimized()) {
|
||||||
@ -143,7 +234,7 @@ ipcMain.on("set-fullscreen", (event, flag) => {
|
|||||||
win.setFullScreen(flag);
|
win.setFullScreen(flag);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on("exit-app", (event, flag) => {
|
ipcMain.on("exit-app", () => {
|
||||||
win.close();
|
win.close();
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
@ -224,7 +315,7 @@ async function writeFileSafe(filename, contents) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle("fs-job", async (event, job) => {
|
ipcMain.handle("fs-job", async (event, job) => {
|
||||||
const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/i, "");
|
const filenameSafe = job.filename.replace(/[^a-z\.\-_0-9]/gi, "_");
|
||||||
const fname = path.join(storePath, filenameSafe);
|
const fname = path.join(storePath, filenameSafe);
|
||||||
switch (job.type) {
|
switch (job.type) {
|
||||||
case "read": {
|
case "read": {
|
||||||
@ -249,5 +340,45 @@ 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 () => {
|
||||||
|
return mods;
|
||||||
|
});
|
||||||
|
|
||||||
wegame.init(isDev);
|
wegame.init(isDev);
|
||||||
wegame.listen();
|
wegame.listen();
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"electron": "^13.1.6"
|
"electron": "^13.1.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-lock": "^1.2.8"
|
"async-lock": "^1.2.8",
|
||||||
|
"electron-window-state": "^5.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
electron_wegame/preload.js
Normal file
7
electron_wegame/preload.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("ipcRenderer", {
|
||||||
|
invoke: ipcRenderer.invoke.bind(ipcRenderer),
|
||||||
|
on: ipcRenderer.on.bind(ipcRenderer),
|
||||||
|
send: ipcRenderer.send.bind(ipcRenderer),
|
||||||
|
});
|
@ -146,6 +146,14 @@ duplexer3@^0.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
||||||
|
|
||||||
|
electron-window-state@^5.0.3:
|
||||||
|
version "5.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8"
|
||||||
|
integrity sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg==
|
||||||
|
dependencies:
|
||||||
|
jsonfile "^4.0.0"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
|
||||||
electron@^13.1.6:
|
electron@^13.1.6:
|
||||||
version "13.1.6"
|
version "13.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/electron/-/electron-13.1.6.tgz#6ecaf969255d62ce82cc0b5c948bf26e7dfb489b"
|
resolved "https://registry.yarnpkg.com/electron/-/electron-13.1.6.tgz#6ecaf969255d62ce82cc0b5c948bf26e7dfb489b"
|
||||||
@ -357,6 +365,18 @@ minimist@^1.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
||||||
|
minimist@^1.2.6:
|
||||||
|
version "1.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
|
|
||||||
|
mkdirp@^0.5.1:
|
||||||
|
version "0.5.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||||
|
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||||
|
dependencies:
|
||||||
|
minimist "^1.2.6"
|
||||||
|
|
||||||
mkdirp@^0.5.4:
|
mkdirp@^0.5.4:
|
||||||
version "0.5.5"
|
version "0.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||||
|
@ -281,13 +281,10 @@ function gulptasksStandalone($, gulp) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(taskPrefix + ".package.win64", cb => packageStandalone("win32", "x64", cb));
|
gulp.task(taskPrefix + ".package.win64", cb => packageStandalone("win32", "x64", cb));
|
||||||
gulp.task(taskPrefix + ".package.linux64", cb => packageStandalone("linux", "x64", cb));
|
// gulp.task(taskPrefix + ".package.linux64", cb => packageStandalone("linux", "x64", cb));
|
||||||
gulp.task(
|
gulp.task(
|
||||||
taskPrefix + ".build-from-windows",
|
taskPrefix + ".build-from-windows",
|
||||||
gulp.series(
|
gulp.series(taskPrefix + ".prepare", gulp.parallel(taskPrefix + ".package.win64"))
|
||||||
taskPrefix + ".prepare",
|
|
||||||
gulp.parallel(taskPrefix + ".package.win64", taskPrefix + ".package.linux64")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
gulp.task(
|
gulp.task(
|
||||||
taskPrefix + ".build-from-darwin",
|
taskPrefix + ".build-from-darwin",
|
||||||
|
@ -48,7 +48,7 @@ export class MainMenuState extends GameState {
|
|||||||
!G_GOG_VERSION;
|
!G_GOG_VERSION;
|
||||||
const showWegameFooter = G_WEGAME_VERSION;
|
const showWegameFooter = G_WEGAME_VERSION;
|
||||||
const hasMods = MODS.anyModsActive();
|
const hasMods = MODS.anyModsActive();
|
||||||
const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO;
|
const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO && !G_WEGAME_VERSION;
|
||||||
|
|
||||||
let showExternalLinks = true;
|
let showExternalLinks = true;
|
||||||
|
|
||||||
|
@ -141,9 +141,6 @@ export class PreloadState extends GameState {
|
|||||||
.then(() => this.app.analytics.initialize())
|
.then(() => this.app.analytics.initialize())
|
||||||
.then(() => this.app.gameAnalytics.initialize())
|
.then(() => this.app.gameAnalytics.initialize())
|
||||||
|
|
||||||
.then(() => this.setStatus("Connecting to api", 15))
|
|
||||||
.then(() => this.fetchDiscounts())
|
|
||||||
|
|
||||||
.then(() => this.setStatus("Initializing settings", 20))
|
.then(() => this.setStatus("Initializing settings", 20))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.app.settings.initialize();
|
return this.app.settings.initialize();
|
||||||
|
@ -30,14 +30,9 @@ export class SettingsState extends TextualGameState {
|
|||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
${
|
|
||||||
G_WEGAME_VERSION
|
|
||||||
? ""
|
|
||||||
: `
|
|
||||||
<button class="styledButton categoryButton manageMods">${T.mods.title}
|
<button class="styledButton categoryButton manageMods">${T.mods.title}
|
||||||
<span class="newBadge">${T.settings.newBadge}</span>
|
<span class="newBadge">${T.settings.newBadge}</span>
|
||||||
</button>`
|
</button>
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<div class="other ${G_CHINA_VERSION || G_WEGAME_VERSION ? "noabout" : ""}">
|
<div class="other ${G_CHINA_VERSION || G_WEGAME_VERSION ? "noabout" : ""}">
|
||||||
|
@ -19,6 +19,7 @@ export class WegameSplashState extends GameState {
|
|||||||
onEnter() {
|
onEnter() {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => {
|
() => {
|
||||||
|
document.querySelector("body > .wrapper").remove();
|
||||||
this.app.stateMgr.moveToState("PreloadState");
|
this.app.stateMgr.moveToState("PreloadState");
|
||||||
},
|
},
|
||||||
G_IS_DEV ? 1 : 6000
|
G_IS_DEV ? 1 : 6000
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
steamPage:
|
steamPage:
|
||||||
shortText: “唯一能限制您的,只有您的想象力!” 《图形工厂》
|
shortText: “唯一能限制您的,只有您的想象力!” 《图形工厂》 是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。
|
||||||
是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。
|
|
||||||
discordLinkShort: 官方讨论区
|
discordLinkShort: 官方讨论区
|
||||||
intro: |-
|
intro: |-
|
||||||
“奇形怪状,放飞想象!”
|
“奇形怪状,放飞想象!”
|
||||||
@ -154,15 +153,13 @@ dialogs:
|
|||||||
desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。
|
desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。
|
||||||
keybindingsIntroduction:
|
keybindingsIntroduction:
|
||||||
title: 实用快捷键
|
title: 实用快捷键
|
||||||
desc:
|
desc: "这个游戏有很多有用的快捷键设定。 以下是其中的一些介绍,记得在<strong>按键设置</strong>中查看其他按键设定!<br><br>
|
||||||
"这个游戏有很多有用的快捷键设定。 以下是其中的一些介绍,记得在<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>"
|
||||||
createMarker:
|
createMarker:
|
||||||
title: 创建地图标记
|
title: 创建地图标记
|
||||||
desc:
|
desc: 填写一个有意义的名称, 还可以同时包含一个形状的 <strong>短代码</strong> (您可以 <link>点击这里</link>
|
||||||
填写一个有意义的名称, 还可以同时包含一个形状的 <strong>短代码</strong> (您可以 <link>点击这里</link>
|
|
||||||
生成短代码)
|
生成短代码)
|
||||||
titleEdit: 编辑地图标记
|
titleEdit: 编辑地图标记
|
||||||
markerDemoLimit:
|
markerDemoLimit:
|
||||||
@ -193,27 +190,27 @@ dialogs:
|
|||||||
title: 设置项目
|
title: 设置项目
|
||||||
puzzleLoadFailed:
|
puzzleLoadFailed:
|
||||||
title: 谜题载入失败
|
title: 谜题载入失败
|
||||||
desc: "谜题未能载入!"
|
desc: 谜题未能载入!
|
||||||
submitPuzzle:
|
submitPuzzle:
|
||||||
title: 提交谜题
|
title: 提交谜题
|
||||||
descName: "为您的谜题命名!"
|
descName: 为您的谜题命名!
|
||||||
descIcon: "请输入唯一的短代码,它将作为您的谜题图标显示(您可以在<link>这里</link>生成,或者从以下随机推荐的图形中选择一个):"
|
descIcon: 请输入唯一的短代码,它将作为您的谜题图标显示(您可以在<link>这里</link>生成,或者从以下随机推荐的图形中选择一个):
|
||||||
placeholderName: 谜题标题
|
placeholderName: 谜题标题
|
||||||
puzzleResizeBadBuildings:
|
puzzleResizeBadBuildings:
|
||||||
title: 无法重新定义尺寸
|
title: 无法重新定义尺寸
|
||||||
desc: 由于某些设施将会超出区域范围,因此您无法将区域变得更小。
|
desc: 由于某些设施将会超出区域范围,因此您无法将区域变得更小。
|
||||||
puzzleLoadError:
|
puzzleLoadError:
|
||||||
title: 谜题出错!
|
title: 谜题出错!
|
||||||
desc: "谜题未能载入!"
|
desc: 谜题未能载入!
|
||||||
offlineMode:
|
offlineMode:
|
||||||
title: 离线模式
|
title: 离线模式
|
||||||
desc: 无法访问服务器,所以游戏以离线模式进行。请确认您的互联网访问正常。
|
desc: 无法访问服务器,所以游戏以离线模式进行。请确认您的互联网访问正常。
|
||||||
puzzleDownloadError:
|
puzzleDownloadError:
|
||||||
title: 下载出错!
|
title: 下载出错!
|
||||||
desc: "无法下载谜题!"
|
desc: 无法下载谜题!
|
||||||
puzzleSubmitError:
|
puzzleSubmitError:
|
||||||
title: 提交出错!
|
title: 提交出错!
|
||||||
desc: "无法提交谜题!"
|
desc: 无法提交谜题!
|
||||||
puzzleSubmitOk:
|
puzzleSubmitOk:
|
||||||
title: 谜题成功发布!
|
title: 谜题成功发布!
|
||||||
desc: 恭喜!您的谜题已经成功发布,其他玩家已经可以玩到。您可以在“我的谜题”中找到自己已发布的谜题。
|
desc: 恭喜!您的谜题已经成功发布,其他玩家已经可以玩到。您可以在“我的谜题”中找到自己已发布的谜题。
|
||||||
@ -237,7 +234,7 @@ dialogs:
|
|||||||
desc: 此谜已被标记!
|
desc: 此谜已被标记!
|
||||||
puzzleReportError:
|
puzzleReportError:
|
||||||
title: 上报失败
|
title: 上报失败
|
||||||
desc: "无法处理您的上报!"
|
desc: 无法处理您的上报!
|
||||||
puzzleLoadShortKey:
|
puzzleLoadShortKey:
|
||||||
title: 输入短代码
|
title: 输入短代码
|
||||||
desc: 输入谜题的短代码并载入。
|
desc: 输入谜题的短代码并载入。
|
||||||
@ -358,22 +355,18 @@ ingame:
|
|||||||
interactiveTutorial:
|
interactiveTutorial:
|
||||||
title: 新手教程
|
title: 新手教程
|
||||||
hints:
|
hints:
|
||||||
1_1_extractor:
|
1_1_extractor: 亲爱的玩家,欢迎来到<strong>《图形工厂》<strong>!在这里你可以通过创造各种图形设施与传送带模拟流水线生产,尽情发挥创造力,创办属于自己的工厂!<br><br>
|
||||||
亲爱的玩家,欢迎来到<strong>《图形工厂》<strong>!在这里你可以通过创造各种图形设施与传送带模拟流水线生产,尽情发挥创造力,创办属于自己的工厂!<br><br>
|
|
||||||
在<strong>圆形<strong>上放置一个<strong>开采器</strong>来获取圆形!<br><br>提示:<strong>按下鼠标左键</strong>选中<strong>开采器</strong>
|
在<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:
|
1_3_expand: 您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
||||||
您可以放置更多的<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:
|
2_2_place_trash: 使用切割机后产生的废弃图形会导致<strong>堵塞</strong>。<br><br>注意使用<strong>垃圾桶</strong>清除当前
|
||||||
使用切割机后产生的废弃图形会导致<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:
|
3_1_rectangles: 现在让我们开采一些矩形!找到<strong>矩形地带</strong>并<strong>放置4个开采器</strong>并将它们用<strong>传送带</strong>连接到中心基地。<br><br>
|
||||||
现在让我们开采一些矩形!找到<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>
|
||||||
@ -670,8 +663,7 @@ storyRewards:
|
|||||||
desc: 恭喜!您解锁了<strong>旋转机</strong>。它会顺时针将输入的<strong>图形旋转90度</strong>。
|
desc: 恭喜!您解锁了<strong>旋转机</strong>。它会顺时针将输入的<strong>图形旋转90度</strong>。
|
||||||
reward_painter:
|
reward_painter:
|
||||||
title: 上色
|
title: 上色
|
||||||
desc:
|
desc: 恭喜!您解锁了<strong>上色器</strong>。开采一些颜色 (就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
||||||
恭喜!您解锁了<strong>上色器</strong>。开采一些颜色 (就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
|
||||||
<br>注意:如果您不幸患有色盲,可以在设置中启用<strong>色盲模式</strong>
|
<br>注意:如果您不幸患有色盲,可以在设置中启用<strong>色盲模式</strong>
|
||||||
reward_mixer:
|
reward_mixer:
|
||||||
title: 混合颜色
|
title: 混合颜色
|
||||||
@ -688,13 +680,11 @@ storyRewards:
|
|||||||
desc: 恭喜!您解锁了<strong>隧道</strong>。它可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
desc: 恭喜!您解锁了<strong>隧道</strong>。它可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
||||||
reward_rotater_ccw:
|
reward_rotater_ccw:
|
||||||
title: 逆时针旋转
|
title: 逆时针旋转
|
||||||
desc:
|
desc: 恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
||||||
恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
|
||||||
<br>选择<strong>旋转机</strong>然后按"T"键来选取这个变体。
|
<br>选择<strong>旋转机</strong>然后按"T"键来选取这个变体。
|
||||||
reward_miner_chainable:
|
reward_miner_chainable:
|
||||||
title: 链式开采器
|
title: 链式开采器
|
||||||
desc:
|
desc: 您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
||||||
您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
|
||||||
注意:新的开采器已替换了工具栏里旧的开采器!
|
注意:新的开采器已替换了工具栏里旧的开采器!
|
||||||
reward_underground_belt_tier_2:
|
reward_underground_belt_tier_2:
|
||||||
title: 二级隧道
|
title: 二级隧道
|
||||||
@ -711,14 +701,12 @@ storyRewards:
|
|||||||
<br>它<strong>优先从左边</strong>输出,这样您就可以用它做一个<strong>溢流门</strong>了!
|
<br>它<strong>优先从左边</strong>输出,这样您就可以用它做一个<strong>溢流门</strong>了!
|
||||||
reward_freeplay:
|
reward_freeplay:
|
||||||
title: 自由模式
|
title: 自由模式
|
||||||
desc:
|
desc: 成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
||||||
成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
|
||||||
从现在起,中心基地最为需要的是<strong>产量</strong>,我强烈建议您去制造一台能够自动交付所需图形的机器!<br><br>
|
从现在起,中心基地最为需要的是<strong>产量</strong>,我强烈建议您去制造一台能够自动交付所需图形的机器!<br><br>
|
||||||
基地会在<strong>电线层</strong>输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。
|
基地会在<strong>电线层</strong>输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。
|
||||||
reward_blueprints:
|
reward_blueprints:
|
||||||
title: 蓝图
|
title: 蓝图
|
||||||
desc:
|
desc: 您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
||||||
您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
|
||||||
<br><br>粘贴并<strong>不是免费的</strong>,您需要制造<strong>蓝图图形</strong>来负担。蓝图图形是您刚刚交付的图形。
|
<br><br>粘贴并<strong>不是免费的</strong>,您需要制造<strong>蓝图图形</strong>来负担。蓝图图形是您刚刚交付的图形。
|
||||||
no_reward:
|
no_reward:
|
||||||
title: 下一关
|
title: 下一关
|
||||||
@ -746,8 +734,7 @@ storyRewards:
|
|||||||
<br>注意:您注意到<strong>传送读取器</strong>和<strong>存储器</strong>输出的他们最后读取的物品了吗?试着在显示屏上展示一下!"
|
<br>注意:您注意到<strong>传送读取器</strong>和<strong>存储器</strong>输出的他们最后读取的物品了吗?试着在显示屏上展示一下!"
|
||||||
reward_constant_signal:
|
reward_constant_signal:
|
||||||
title: 恒定信号
|
title: 恒定信号
|
||||||
desc:
|
desc: 恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
||||||
恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
|
||||||
<br>比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。
|
<br>比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。
|
||||||
reward_logic_gates:
|
reward_logic_gates:
|
||||||
title: 逻辑门
|
title: 逻辑门
|
||||||
@ -766,8 +753,7 @@ storyRewards:
|
|||||||
<strong>提示</strong>:可在设置中打开电线层教程!"
|
<strong>提示</strong>:可在设置中打开电线层教程!"
|
||||||
reward_filter:
|
reward_filter:
|
||||||
title: 物品过滤器
|
title: 物品过滤器
|
||||||
desc:
|
desc: 恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
||||||
恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
|
||||||
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
||||||
reward_demo_end:
|
reward_demo_end:
|
||||||
title: 试玩结束
|
title: 试玩结束
|
||||||
@ -995,13 +981,10 @@ keybindings:
|
|||||||
showShapeTooltip: 显示图形输出提示。
|
showShapeTooltip: 显示图形输出提示。
|
||||||
about:
|
about:
|
||||||
title: 关于游戏
|
title: 关于游戏
|
||||||
body: >-
|
body: |-
|
||||||
本游戏由托比亚斯开发,并且已经开源。<br><br>
|
本游戏由托比亚斯开发,并且已经开源。<br><br>
|
||||||
|
|
||||||
这个游戏的开发获得了热情玩家的巨大支持。非常感谢!<br><br>
|
这个游戏的开发获得了热情玩家的巨大支持。非常感谢!<br><br>
|
||||||
|
|
||||||
本游戏的音乐由佩普森制作——他是个很棒的伙伴。<br><br>
|
本游戏的音乐由佩普森制作——他是个很棒的伙伴。<br><br>
|
||||||
|
|
||||||
最后,我想感谢我最好的朋友尼可拉斯——如果没有他的《异星工厂》带给我的体验和启发,《图形工厂》将不会存在。
|
最后,我想感谢我最好的朋友尼可拉斯——如果没有他的《异星工厂》带给我的体验和启发,《图形工厂》将不会存在。
|
||||||
changelog:
|
changelog:
|
||||||
title: 版本日志
|
title: 版本日志
|
||||||
|
Loading…
Reference in New Issue
Block a user