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 isLocal = app.commandLine.hasSwitch("local");
|
||||
const safeMode = app.commandLine.hasSwitch("safe-mode");
|
||||
const externalMod = app.commandLine.getSwitchValue("load-mod");
|
||||
|
||||
const roamingFolder =
|
||||
process.env.APPDATA ||
|
||||
@ -60,7 +58,7 @@ function createWindow() {
|
||||
useContentSize: false,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
title: "shapez.io Standalone",
|
||||
title: "shapez.io Demo",
|
||||
transparent: false,
|
||||
icon: path.join(__dirname, "favicon" + faviconExtension),
|
||||
// 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 () => {
|
||||
return mods;
|
||||
return [];
|
||||
});
|
||||
|
||||
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,
|
||||
executableName: "shapezio",
|
||||
icon: path.join(electronBaseDir, "favicon"),
|
||||
name: "shapez.io-standalone" + suffix,
|
||||
name: "shapez.io-demo" + suffix,
|
||||
out: tempDestDir,
|
||||
overwrite: true,
|
||||
appBundleId: "io.shapez.standalone",
|
||||
appBundleId: "io.shapez.demo",
|
||||
appCategoryType: "public.app-category.games",
|
||||
}).then(
|
||||
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) {
|
||||
if (has119Savegames && !this.currentData.savegameV1119Imported) {
|
||||
this.currentData.savegameV1119Imported = true;
|
||||
console.warn("Current user now has access to all levels due to 1119 savegame");
|
||||
return this.writeAsync();
|
||||
}
|
||||
return Promise.resolve();
|
||||
@ -74,25 +73,6 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@ -101,7 +81,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsStandaloneMarketingActive() {
|
||||
return this.isLimitedVersion();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +89,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getIsExportingScreenshotsPossible() {
|
||||
return !this.isLimitedVersion();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +97,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {number}
|
||||
*/
|
||||
getMaximumWaypoints() {
|
||||
return this.isLimitedVersion() ? 2 : 1e20;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,7 +105,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getHasUnlimitedSavegames() {
|
||||
return !this.isLimitedVersion();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +113,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getHasExtendedSettings() {
|
||||
return !this.isLimitedVersion();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,7 +121,7 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getHasExtendedUpgrades() {
|
||||
return !this.isLimitedVersion() || this.currentData.savegameV1119Imported;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,6 +129,6 @@ export class RestrictionManager extends ReadWriteProxy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
getHasExtendedLevelsAndFreeplay() {
|
||||
return !this.isLimitedVersion() || this.currentData.savegameV1119Imported;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,11 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
import { globalConfig } from "../core/config";
|
||||
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 { 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");
|
||||
|
||||
/**
|
||||
@ -104,165 +97,9 @@ export class ModLoader {
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
exposeExports() {}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -18,23 +18,7 @@ const analyticsLocalFile = "shapez_token_123.bin";
|
||||
|
||||
export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
||||
get environment() {
|
||||
if (G_IS_DEV) {
|
||||
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";
|
||||
}
|
||||
return "steam-demo";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,14 +31,10 @@ export const SOUNDS = {
|
||||
export const MUSIC = {
|
||||
// The theme always depends on the standalone only, even if running the full
|
||||
// version in the browser
|
||||
theme: G_IS_STANDALONE ? "theme-full" : "theme-short",
|
||||
theme: "theme-short",
|
||||
menu: "menu",
|
||||
};
|
||||
|
||||
if (G_IS_STANDALONE || G_IS_DEV) {
|
||||
MUSIC.puzzle = "puzzle-full";
|
||||
}
|
||||
|
||||
export class SoundInstanceInterface {
|
||||
constructor(key, url) {
|
||||
this.key = key;
|
||||
|
@ -186,10 +186,7 @@ function initializeSettings() {
|
||||
if (app.platformWrapper.getSupportsFullscreen()) {
|
||||
app.platformWrapper.setFullscreen(value);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Application} app
|
||||
*/ app => app.restrictionMgr.getHasExtendedSettings()
|
||||
}
|
||||
),
|
||||
|
||||
new BoolSetting(
|
||||
|
@ -38,16 +38,15 @@ export class MainMenuState extends GameState {
|
||||
getInnerHTML() {
|
||||
const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION;
|
||||
const showExitAppButton = G_IS_STANDALONE;
|
||||
const showUpdateLabel = !G_WEGAME_VERSION;
|
||||
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 hasMods = MODS.anyModsActive();
|
||||
const hasMods = false;
|
||||
|
||||
let showExternalLinks = true;
|
||||
|
||||
if (G_IS_STANDALONE) {
|
||||
if (G_WEGAME_VERSION || G_CHINA_VERSION) {
|
||||
if (G_WEGAME_VERSION) {
|
||||
showExternalLinks = false;
|
||||
}
|
||||
} else {
|
||||
@ -58,19 +57,12 @@ export class MainMenuState extends GameState {
|
||||
}
|
||||
|
||||
let showDiscordLink = showExternalLinks;
|
||||
if (G_CHINA_VERSION) {
|
||||
showDiscordLink = true;
|
||||
}
|
||||
|
||||
const showCrosspromo = !G_IS_STANDALONE && showExternalLinks;
|
||||
const showCrosspromo = false;
|
||||
const showDemoAdvertisement =
|
||||
showExternalLinks && this.app.restrictionMgr.getIsStandaloneMarketingActive();
|
||||
|
||||
const ownsPuzzleDLC =
|
||||
G_IS_DEV ||
|
||||
(G_IS_STANDALONE &&
|
||||
/** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle);
|
||||
|
||||
const ownsPuzzleDLC = false;
|
||||
const bannerHtml = `
|
||||
<h3>${T.demoBanners.title}</h3>
|
||||
<p>${T.demoBanners.intro}</p>
|
||||
@ -96,7 +88,6 @@ export class MainMenuState extends GameState {
|
||||
|
||||
<div class="logo">
|
||||
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
|
||||
${showUpdateLabel ? `<span class="updateLabel">MODS UPDATE!</span>` : ""}
|
||||
</div>
|
||||
|
||||
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { THIRDPARTY_URLS } from "../core/config";
|
||||
import { TextualGameState } from "../core/textual_game_state";
|
||||
import { MODS } from "../mods/modloader";
|
||||
import { T } from "../translations";
|
||||
|
||||
export class ModsState extends TextualGameState {
|
||||
@ -14,35 +13,29 @@ export class ModsState extends TextualGameState {
|
||||
|
||||
internalGetFullHtml() {
|
||||
let headerHtml = `
|
||||
<div class="headerBar">
|
||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||
<div class="headerBar">
|
||||
<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>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="actions">
|
||||
${
|
||||
G_IS_STANDALONE
|
||||
? `<button class="styledButton browseMods">${T.mods.browseMods}</button>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>`;
|
||||
</div>`;
|
||||
|
||||
return `
|
||||
${headerHtml}
|
||||
<div class="container">
|
||||
${this.getInnerHTML()}
|
||||
</div>
|
||||
`;
|
||||
${headerHtml}
|
||||
<div class="container">
|
||||
${this.getInnerHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getMainContentHTML() {
|
||||
if (!G_IS_STANDALONE && !G_IS_DEV) {
|
||||
return `
|
||||
return `
|
||||
<div class="noModSupport">
|
||||
|
||||
<p>${T.mods.noModSupport}</p>
|
||||
@ -52,49 +45,6 @@ export class ModsState extends TextualGameState {
|
||||
|
||||
</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() {
|
||||
|
@ -74,7 +74,7 @@ mainMenu:
|
||||
puzzleDlcWishlist: 添加愿望单!
|
||||
puzzleDlcViewNow: 查看DLC
|
||||
mods:
|
||||
title: 激活游戏模组(Mods)
|
||||
title: 激活游戏模组
|
||||
warningPuzzleDLC: 无法在任何游戏模组(Mods)下进行“谜题挑战者”DLC,请关闭所有游戏模组(Mods)。
|
||||
dialogs:
|
||||
buttons:
|
||||
@ -139,7 +139,8 @@ dialogs:
|
||||
desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。
|
||||
keybindingsIntroduction:
|
||||
title: 实用快捷键
|
||||
desc: 这个游戏有很多有用的快捷键设定。以下是其中的一些介绍,记得在<strong>按键设置</strong>中查看其他按键设定!<br><br>
|
||||
desc:
|
||||
这个游戏有很多有用的快捷键设定。以下是其中的一些介绍,记得在<strong>按键设置</strong>中查看其他按键设定!<br><br>
|
||||
<code class='keybinding'>CTRL键</code> + 拖动:选择区域以复制或删除。<br> <code
|
||||
class='keybinding'>SHIFT键</code>: 按住以放置多个同一种设施。<br> <code
|
||||
class='keybinding'>ALT键</code>:反向放置传送带。<br>
|
||||
@ -318,15 +319,18 @@ ingame:
|
||||
hints:
|
||||
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_3_expand: 您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
||||
1_3_expand:
|
||||
您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
||||
提示:按住 <strong>SHIFT</strong>
|
||||
键可放置多个<strong>开采器</strong>,注意用<strong>R</strong>
|
||||
键可旋转<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>可以快速选择各项设施!
|
||||
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>
|
||||
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>
|
||||
@ -620,7 +624,8 @@ storyRewards:
|
||||
desc: 恭喜!您解锁了<strong>旋转机</strong>。它会顺时针将输入的<strong>图形旋转90度</strong>。
|
||||
reward_painter:
|
||||
title: 上色
|
||||
desc: 恭喜!您解锁了<strong>上色器</strong>。开采一些颜色(就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
||||
desc:
|
||||
恭喜!您解锁了<strong>上色器</strong>。开采一些颜色(就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
||||
<br>注意:如果您不幸患有色盲,可以在设置中启用<strong>色盲模式</strong>
|
||||
reward_mixer:
|
||||
title: 混合颜色
|
||||
@ -637,11 +642,13 @@ storyRewards:
|
||||
desc: 恭喜!您解锁了<strong>隧道</strong>。它可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
||||
reward_rotater_ccw:
|
||||
title: 逆时针旋转
|
||||
desc: 恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
||||
desc:
|
||||
恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
||||
<br>选择<strong>旋转机</strong>然后按"T"键来选取这个变体。
|
||||
reward_miner_chainable:
|
||||
title: 链式开采器
|
||||
desc: 您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
||||
desc:
|
||||
您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
||||
注意:新的开采器已替换了工具栏里旧的开采器!
|
||||
reward_underground_belt_tier_2:
|
||||
title: 二级隧道
|
||||
@ -658,12 +665,14 @@ storyRewards:
|
||||
<br>它<strong>优先从左边</strong>输出,这样您就可以用它做一个<strong>溢流门</strong>了!
|
||||
reward_freeplay:
|
||||
title: 自由模式
|
||||
desc: 成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
||||
desc:
|
||||
成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
||||
从现在起,中心基地最为需要的是<strong>产量</strong>,我强烈建议您去制造一台能够自动交付所需图形的机器!<br><br>
|
||||
基地会在<strong>电线层</strong>输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。
|
||||
reward_blueprints:
|
||||
title: 蓝图
|
||||
desc: 您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
||||
desc:
|
||||
您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
||||
<br><br>粘贴并<strong>不是免费的</strong>,您需要制造<strong>蓝图图形</strong>来负担。蓝图图形是您刚刚交付的图形。
|
||||
no_reward:
|
||||
title: 下一关
|
||||
@ -691,7 +700,8 @@ storyRewards:
|
||||
<br>注意:您注意到<strong>传送读取器</strong>和<strong>存储器</strong>输出的他们最后读取的物品了吗?试着在显示屏上展示一下!
|
||||
reward_constant_signal:
|
||||
title: 恒定信号
|
||||
desc: 恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
||||
desc:
|
||||
恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
||||
<br>比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。
|
||||
reward_logic_gates:
|
||||
title: 逻辑门
|
||||
@ -710,7 +720,8 @@ storyRewards:
|
||||
<strong>提示</strong>:可在设置中打开电线层教程!
|
||||
reward_filter:
|
||||
title: 物品过滤器
|
||||
desc: 恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
||||
desc:
|
||||
恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
||||
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
||||
reward_demo_end:
|
||||
title: 试玩结束
|
||||
@ -1093,7 +1104,7 @@ backendErrors:
|
||||
too-many-likes-already: 您的谜题已经得到了许多玩家的赞赏。如果您仍然希望删除它,请联系support@shapez.io!
|
||||
no-permission: 您没有执行此操作的权限。
|
||||
mods:
|
||||
title: 游戏模组(Mods)
|
||||
title: 游戏模组
|
||||
author: 作者
|
||||
version: 版本
|
||||
openFolder: 打开游戏模组(Mods)文件夹
|
||||
@ -1103,6 +1114,7 @@ mods:
|
||||
noModSupport: 您需要在Steam平台获得完整版才可以安装游戏模组(Mods)。
|
||||
togglingComingSoon:
|
||||
title: 即将开放
|
||||
description: 当前只能通过将游戏模组(Mods)文件复制到mods文件夹或从mods文件夹移除来启用或禁用游戏模组(Mods)。
|
||||
description:
|
||||
当前只能通过将游戏模组(Mods)文件复制到mods文件夹或从mods文件夹移除来启用或禁用游戏模组(Mods)。
|
||||
但是,可以切换游戏模组(Mods)已经计划在之后的更新中实现!
|
||||
modWebsite: Website
|
||||
|
Loading…
Reference in New Issue
Block a user