1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00
This commit is contained in:
DJ1TJOO 2021-03-10 16:54:09 +01:00
commit 4617fe01ca
77 changed files with 12330 additions and 12576 deletions

5
.gitignore vendored
View File

@ -46,7 +46,12 @@ res_built
gulp/runnable-texturepacker.jar
tmp_standalone_files
tmp_standalone_files_china
# Local config
config.local.js
.DS_Store
# Editor artifacts
*.*.swp
*.*.swo

View File

@ -6,6 +6,7 @@ const url = require("url");
const childProcess = require("child_process");
const { ipcMain, shell } = require("electron");
const fs = require("fs");
const steam = require("./steam");
const isDev = process.argv.indexOf("--dev") >= 0;
const isLocal = process.argv.indexOf("--local") >= 0;
@ -290,3 +291,5 @@ const emitOpenedWithFile = path => {
const content = fs.readFileSync(path, "utf-8");
ipcMain.emit("opened-with-file", path, content);
};
steam.init(isDev);
steam.listen();

View File

@ -10,7 +10,9 @@
"start": "electron --disable-direct-composition --in-process-gpu ."
},
"devDependencies": {
"electron": "10.1.3"
"electron": "11.3.0"
},
"dependencies": {}
"optionalDependencies": {
"shapez.io-private-artifacts": "github:tobspr/shapez.io-private-artifacts#abi-v85"
}
}

73
electron/steam.js Normal file
View File

@ -0,0 +1,73 @@
const fs = require('fs');
const path = require('path');
const { ipcMain } = require("electron");
let greenworks = null;
let appId = null;
let initialized = false;
try {
greenworks = require("shapez.io-private-artifacts/steam/greenworks");
appId = parseInt(fs.readFileSync(path.join(__dirname, "steam_appid.txt"), "utf8"));
} catch (err) {
// greenworks is not installed
// throw err;
}
function init (isDev) {
if (!greenworks) {
return;
}
if (!isDev) {
if (greenworks.restartAppIfNecessary(appId)) {
console.log("Restarting ...");
process.exit(0);
}
}
if (!greenworks.init()) {
console.log("Failed to initialize greenworks");
process.exit(1);
}
initialized = true;
}
function listen () {
ipcMain.handle("steam:is-initialized", isInitialized);
if (!greenworks || !initialized) {
console.log("Ignoring Steam IPC events");
return;
}
ipcMain.handle("steam:get-achievement-names", getAchievementNames);
ipcMain.handle("steam:activate-achievement", activateAchievement);
}
function isInitialized(event) {
return Promise.resolve(initialized);
}
function getAchievementNames(event) {
return new Promise((resolve, reject) => {
try {
const achievements = greenworks.getAchievementNames()
resolve(achievements);
} catch (err) {
reject(err);
}
});
}
function activateAchievement(event, id) {
return new Promise((resolve, reject) => {
greenworks.activateAchievement(id, () => resolve(), err => reject(err))
});
}
module.exports = {
init,
listen
};

View File

@ -1 +1 @@
1134480
1318690

File diff suppressed because it is too large Load Diff

View File

@ -50,6 +50,9 @@ css.gulptasksCSS($, gulp, buildFolder, browserSync);
const sounds = require("./sounds");
sounds.gulptasksSounds($, gulp, buildFolder);
const localConfig = require("./local-config");
localConfig.gulptasksLocalConfig($, gulp);
const js = require("./js");
js.gulptasksJS($, gulp, buildFolder, browserSync);
@ -136,7 +139,7 @@ gulp.task("main.webserver", () => {
);
});
function serve({ standalone }) {
function serve({ standalone, chineseVersion = false }) {
browserSync.init({
server: buildFolder,
port: 3005,
@ -200,7 +203,11 @@ function serve({ standalone }) {
if (standalone) {
gulp.series("js.standalone-dev.watch")(() => true);
} else {
gulp.series("js.dev.watch")(() => true);
if (chineseVersion) {
gulp.series("china.js.dev.watch")(() => true);
} else {
gulp.series("js.dev.watch")(() => true);
}
}
}
@ -221,6 +228,7 @@ gulp.task(
gulp.series(
"utils.cleanup",
"utils.copyAdditionalBuildFiles",
"localConfig.findOrCreate",
"imgres.buildAtlas",
"imgres.atlasToJson",
"imgres.atlas",
@ -238,6 +246,7 @@ gulp.task(
"build.standalone.dev",
gulp.series(
"utils.cleanup",
"localConfig.findOrCreate",
"imgres.buildAtlas",
"imgres.atlasToJson",
"imgres.atlas",
@ -284,30 +293,28 @@ gulp.task(
);
// Builds everything (standalone-prod)
gulp.task(
"step.standalone-prod.code",
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", "js.standalone-prod")
);
gulp.task("step.standalone-prod.mainbuild", gulp.parallel("step.baseResources", "step.standalone-prod.code"));
gulp.task(
"step.standalone-prod.all",
gulp.series("step.standalone-prod.mainbuild", "css.prod-standalone", "html.standalone-prod")
);
gulp.task(
"build.standalone-prod",
gulp.series("utils.cleanup", "step.standalone-prod.all", "step.postbuild")
);
// OS X build and release upload
gulp.task(
"build.darwin64-prod",
gulp.series(
"build.standalone-prod",
"standalone.prepare",
"standalone.package.prod.darwin64",
"standalone.uploadRelease.darwin64"
)
);
for (const prefix of ["", "china."]) {
gulp.task(
prefix + "step.standalone-prod.code",
gulp.series("sounds.fullbuildHQ", "translations.fullBuild", prefix + "js.standalone-prod")
);
gulp.task(
prefix + "step.standalone-prod.mainbuild",
gulp.parallel("step.baseResources", prefix + "step.standalone-prod.code")
);
gulp.task(
prefix + "step.standalone-prod.all",
gulp.series(prefix + "step.standalone-prod.mainbuild", "css.prod-standalone", "html.standalone-prod")
);
gulp.task(
prefix + "build.standalone-prod",
gulp.series("utils.cleanup", prefix + "step.standalone-prod.all", "step.postbuild")
);
}
// Deploying!
gulp.task(
@ -320,7 +327,12 @@ gulp.task(
);
gulp.task("main.deploy.prod", gulp.series("utils.requireCleanWorkingTree", "build.prod", "ftp.upload.prod"));
gulp.task("main.deploy.all", gulp.series("main.deploy.staging", "main.deploy.prod"));
gulp.task("main.standalone", gulp.series("build.standalone-prod", "standalone.package.prod"));
gulp.task("regular.main.standalone", gulp.series("build.standalone-prod", "standalone.package.prod"));
gulp.task(
"china.main.standalone",
gulp.series("china.build.standalone-prod", "china.standalone.package.prod")
);
gulp.task("standalone.all", gulp.series("regular.main.standalone", "china.main.standalone"));
// Live-development
gulp.task(
@ -331,5 +343,9 @@ gulp.task(
"main.serveStandalone",
gulp.series("build.standalone.dev", () => serve({ standalone: true }))
);
gulp.task(
"china.main.serveDev",
gulp.series("build.dev", () => serve({ standalone: false, chineseVersion: true }))
);
gulp.task("default", gulp.series("main.serveDev"));

View File

@ -29,6 +29,36 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
.pipe(gulp.dest(buildFolder));
});
//// DEV CHINA
gulp.task("china.js.dev.watch", () => {
return gulp
.src("../src/js/main.js")
.pipe(
$.webpackStream(
requireUncached("./webpack.config.js")({
watch: true,
chineseVersion: true,
})
)
)
.pipe(gulp.dest(buildFolder))
.pipe(browserSync.stream());
});
gulp.task("china.js.dev", () => {
return gulp
.src("../src/js/main.js")
.pipe(
$.webpackStream(
requireUncached("./webpack.config.js")({
chineseVersion: true,
})
)
)
.pipe(gulp.dest(buildFolder));
});
//// STAGING
gulp.task("js.staging.transpiled", () => {
@ -161,6 +191,23 @@ function gulptasksJS($, gulp, buildFolder, browserSync) {
)
.pipe(gulp.dest(buildFolder));
});
gulp.task("china.js.standalone-prod", () => {
return gulp
.src("../src/js/main.js")
.pipe(
$.webpackStream(
requireUncached("./webpack.production.config.js")({
enableAssert: false,
environment: "prod",
es6: true,
standalone: true,
chineseVersion: true,
})
)
)
.pipe(gulp.dest(buildFolder));
});
}
module.exports = {

18
gulp/local-config.js Normal file
View File

@ -0,0 +1,18 @@
const path = require("path");
const fs = require("fs");
const fse = require("fs-extra");
const configTemplatePath = path.join(__dirname, "../src/js/core/config.local.template.js");
const configPath = path.join(__dirname, "../src/js/core/config.local.js");
function gulptasksLocalConfig($, gulp) {
gulp.task("localConfig.findOrCreate", cb => {
if (!fs.existsSync(configPath)) {
fse.copySync(configTemplatePath, configPath);
}
cb();
});
}
module.exports = { gulptasksLocalConfig };

View File

@ -34,6 +34,7 @@
"fastdom": "^1.0.9",
"flatted": "^2.0.1",
"fs-extra": "^8.1.0",
"gifsicle": "^5.2.0",
"gulp-audiosprite": "^1.1.0",
"howler": "^2.1.2",
"html-loader": "^0.5.5",
@ -61,7 +62,8 @@
"webpack-plugin-replace": "^1.1.1",
"webpack-strip-block": "^0.2.0",
"whatwg-fetch": "^3.0.0",
"worker-loader": "^2.0.0"
"worker-loader": "^2.0.0",
"yaml": "^1.10.0"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.13.0",

View File

@ -1,5 +1,6 @@
require("colors");
const packager = require("electron-packager");
const pj = require("../electron/package.json");
const path = require("path");
const { getVersion } = require("./buildutils");
const fs = require("fs");
@ -9,66 +10,69 @@ const execSync = require("child_process").execSync;
function gulptasksStandalone($, gulp) {
const electronBaseDir = path.join(__dirname, "..", "electron");
const targets = [
{
tempDestDir: path.join(__dirname, "..", "tmp_standalone_files"),
suffix: "",
taskPrefix: ""
},
{
tempDestDir: path.join(__dirname, "..", "tmp_standalone_files_china"),
suffix: "china",
taskPrefix: "china.",
},
];
const tempDestDir = path.join(__dirname, "..", "tmp_standalone_files");
const tempDestBuildDir = path.join(tempDestDir, "built");
for (const { tempDestDir, suffix, taskPrefix } of targets) {
const tempDestBuildDir = path.join(tempDestDir, "built");
gulp.task("standalone.prepare.cleanup", () => {
return gulp.src(tempDestDir, { read: false, allowEmpty: true }).pipe($.clean({ force: true }));
});
gulp.task(taskPrefix + "standalone.prepare.cleanup", () => {
return gulp.src(tempDestDir, { read: false, allowEmpty: true })
.pipe($.clean({ force: true }));
});
gulp.task("standalone.prepare.copyPrefab", () => {
// const requiredFiles = $.glob.sync("../electron/");
const requiredFiles = [
path.join(electronBaseDir, "lib", "**", "*.node"),
path.join(electronBaseDir, "node_modules", "**", "*.*"),
path.join(electronBaseDir, "node_modules", "**", ".*"),
path.join(electronBaseDir, "favicon*"),
gulp.task(taskPrefix + "standalone.prepare.copyPrefab", () => {
const requiredFiles = [
path.join(electronBaseDir, "node_modules", "**", "*.*"),
path.join(electronBaseDir, "node_modules", "**", ".*"),
path.join(electronBaseDir, "steam_appid.txt"),
path.join(electronBaseDir, "favicon*"),
// fails on platforms which support symlinks
// https://github.com/gulpjs/gulp/issues/1427
// path.join(electronBaseDir, "node_modules", "**", "*"),
];
return gulp.src(requiredFiles, { base: electronBaseDir }).pipe(gulp.dest(tempDestBuildDir));
});
// fails on platforms which support symlinks
// https://github.com/gulpjs/gulp/issues/1427
// path.join(electronBaseDir, "node_modules", "**", "*"),
];
return gulp.src(requiredFiles, { base: electronBaseDir })
.pipe(gulp.dest(tempDestBuildDir));
});
gulp.task("standalone.prepare.writePackageJson", cb => {
fs.writeFileSync(
path.join(tempDestBuildDir, "package.json"),
JSON.stringify(
{
devDependencies: {
electron: "6.1.12",
},
gulp.task(taskPrefix + "standalone.prepare.writePackageJson", cb => {
const packageJsonString = JSON.stringify({
scripts: {
start: pj.scripts.start
},
null,
4
)
);
cb();
});
devDependencies: pj.devDependencies,
optionalDependencies: pj.optionalDependencies
}, null, 4);
gulp.task("standalone.prepareVDF", cb => {
const hash = buildutils.getRevision();
fs.writeFileSync(path.join(tempDestBuildDir, "package.json"), packageJsonString);
const steampipeDir = path.join(__dirname, "steampipe", "scripts");
const templateContents = fs
.readFileSync(path.join(steampipeDir, "app.vdf.template"), { encoding: "utf-8" })
.toString();
cb();
});
const convertedContents = templateContents.replace("$DESC$", "Commit " + hash);
fs.writeFileSync(path.join(steampipeDir, "app.vdf"), convertedContents);
gulp.task(taskPrefix + "standalone.prepareVDF", cb => {
const hash = buildutils.getRevision();
cb();
});
const steampipeDir = path.join(__dirname, "steampipe", "scripts");
const templateContents = fs
.readFileSync(path.join(steampipeDir, "app.vdf.template"), { encoding: "utf-8" })
.toString();
gulp.task("standalone.prepare.minifyCode", () => {
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
});
const convertedContents = templateContents.replace("$DESC$", "Commit " + hash);
fs.writeFileSync(path.join(steampipeDir, "app.vdf"), convertedContents);
gulp.task("standalone.prepare.copyGamefiles", () => {
return gulp.src("../build/**/*.*", { base: "../build" }).pipe(gulp.dest(tempDestBuildDir));
});
cb();
});
gulp.task("standalone.killRunningInstances", cb => {
try {
@ -171,73 +175,125 @@ function gulptasksStandalone($, gulp) {
"For more information, see " +
"https://github.com/electron/electron-packager/issues/71".underline
);
gulp.task(taskPrefix + "standalone.prepare.minifyCode", () => {
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
});
// Clear up framework folders
fs.writeFileSync(
path.join(appPath, "play.sh"),
'#!/usr/bin/env bash\n./shapez.io-standalone.app/Contents/MacOS/shapezio --no-sandbox "$@"\n'
);
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
fs.chmodSync(
path.join(appPath, "shapez.io-standalone.app", "Contents", "MacOS", "shapezio"),
0o775
);
gulp.task(taskPrefix + "standalone.prepare.copyGamefiles", () => {
return gulp.src("../build/**/*.*", { base: "../build" })
.pipe(gulp.dest(tempDestBuildDir));
});
const finalPath = path.join(appPath, "shapez.io-standalone.app");
const frameworksDir = path.join(finalPath, "Contents", "Frameworks");
const frameworkFolders = fs
.readdirSync(frameworksDir)
.filter(fname => fname.endsWith(".framework"));
for (let i = 0; i < frameworkFolders.length; ++i) {
const folderName = frameworkFolders[i];
const frameworkFolder = path.join(frameworksDir, folderName);
console.log(" -> ", frameworkFolder);
const filesToDelete = fs
.readdirSync(frameworkFolder)
.filter(fname => fname.toLowerCase() !== "versions");
filesToDelete.forEach(fname => {
console.log(" -> Deleting", fname);
fs.unlinkSync(path.join(frameworkFolder, fname));
});
const frameworkSourceDir = path.join(frameworkFolder, "Versions", "A");
fse.copySync(frameworkSourceDir, frameworkFolder);
}
}
});
cb();
},
err => {
console.error("Packaging error:", err);
cb();
gulp.task(taskPrefix + "standalone.killRunningInstances", cb => {
try {
execSync("taskkill /F /IM shapezio.exe");
} catch (ex) {
console.warn("Failed to kill running instances, maybe none are up.");
}
cb();
});
gulp.task(
taskPrefix + "standalone.prepare",
gulp.series(
taskPrefix + "standalone.killRunningInstances",
taskPrefix + "standalone.prepare.cleanup",
taskPrefix + "standalone.prepare.copyPrefab",
taskPrefix + "standalone.prepare.writePackageJson",
taskPrefix + "standalone.prepare.minifyCode",
taskPrefix + "standalone.prepare.copyGamefiles"
)
);
/**
*
* @param {'win32'|'linux'} platform
* @param {'x64'|'ia32'} arch
* @param {function():void} cb
*/
function packageStandalone(platform, arch, cb) {
const tomlFile = fs.readFileSync(path.join(__dirname, ".itch.toml"));
const privateArtifactsPath = "node_modules/shapez.io-private-artifacts";
let asar;
if (fs.existsSync(path.join(tempDestBuildDir, privateArtifactsPath))) {
asar = { unpackDir: privateArtifactsPath };
} else {
asar = true;
}
packager({
dir: tempDestBuildDir,
appCopyright: "Tobias Springer",
appVersion: getVersion(),
buildVersion: "1.0.0",
arch,
platform,
asar: asar,
executableName: "shapezio",
icon: path.join(electronBaseDir, "favicon"),
name: "shapez.io-standalone" + suffix,
out: tempDestDir,
overwrite: true,
appBundleId: "io.shapez.standalone",
appCategoryType: "public.app-category.games",
}).then(
appPaths => {
console.log("Packages created:", appPaths);
appPaths.forEach(appPath => {
if (!fs.existsSync(appPath)) {
console.error("Bad app path gotten:", appPath);
return;
}
fs.writeFileSync(
path.join(appPath, "LICENSE"),
fs.readFileSync(path.join(__dirname, "..", "LICENSE"))
);
fse.copySync(
path.join(tempDestBuildDir, "steam_appid.txt"),
path.join(appPath, "steam_appid.txt")
);
fs.writeFileSync(path.join(appPath, ".itch.toml"), tomlFile);
if (platform === "linux") {
fs.writeFileSync(
path.join(appPath, "play.sh"),
'#!/usr/bin/env bash\n./shapezio --no-sandbox "$@"\n'
);
fs.chmodSync(path.join(appPath, "play.sh"), 0o775);
}
});
cb();
},
err => {
console.error("Packaging error:", err);
cb();
}
);
}
gulp.task(taskPrefix + "standalone.package.prod.win64", cb =>
packageStandalone("win32", "x64", cb)
);
gulp.task(taskPrefix + "standalone.package.prod.linux64", cb =>
packageStandalone("linux", "x64", cb)
);
gulp.task(
taskPrefix + "standalone.package.prod",
gulp.series(
taskPrefix + "standalone.prepare",
gulp.parallel(
taskPrefix + "standalone.package.prod.win64",
taskPrefix + "standalone.package.prod.linux64"
)
)
);
}
gulp.task("standalone.package.prod.win64", cb => packageStandalone("win32", "x64", cb));
gulp.task("standalone.package.prod.win32", cb => packageStandalone("win32", "ia32", cb));
gulp.task("standalone.package.prod.linux64", cb => packageStandalone("linux", "x64", cb));
gulp.task("standalone.package.prod.linux32", cb => packageStandalone("linux", "ia32", cb));
gulp.task("standalone.package.prod.darwin64", cb => packageStandalone("darwin", "x64", cb));
gulp.task("standalone.package.prod.darwin64.unsigned", cb =>
packageStandalone("darwin", "x64", cb, false)
);
gulp.task(
"standalone.package.prod",
gulp.series(
"standalone.prepare",
gulp.parallel(
"standalone.package.prod.win64",
"standalone.package.prod.linux64",
"standalone.package.prod.darwin64"
)
)
);
}
module.exports = { gulptasksStandalone };

View File

@ -2,14 +2,16 @@
{
"appid" "1318690"
"desc" "$DESC$"
"buildoutput" "C:\work\shapez\shapez.io\gulp\steampipe\steamtemp"
"buildoutput" "C:\work\shapez.io\gulp\steampipe\steamtemp"
"contentroot" ""
"setlive" ""
"preview" "0"
"local" ""
"depots"
{
"1318691" "C:\work\shapez\shapez.io\gulp\steampipe\scripts\windows.vdf"
"1318692" "C:\work\shapez\shapez.io\gulp\steampipe\scripts\linux.vdf"
"1318691" "C:\work\shapez.io\gulp\steampipe\scripts\windows.vdf"
"1318694" "C:\work\shapez.io\gulp\steampipe\scripts\china-windows.vdf"
"1318692" "C:\work\shapez.io\gulp\steampipe\scripts\linux.vdf"
"1318695" "C:\work\shapez.io\gulp\steampipe\scripts\china-linux.vdf"
}
}

View File

@ -0,0 +1,12 @@
"DepotBuildConfig"
{
"DepotID" "1318695"
"contentroot" "C:\work\shapez.io\tmp_standalone_files_china\shapez.io-standalonechina-linux-x64"
"FileMapping"
{
"LocalPath" "*"
"DepotPath" "."
"recursive" "1"
}
"FileExclusion" "*.pdb"
}

View File

@ -0,0 +1,12 @@
"DepotBuildConfig"
{
"DepotID" "1318694"
"contentroot" "C:\work\shapez.io\tmp_standalone_files_china\shapez.io-standalonechina-win32-x64"
"FileMapping"
{
"LocalPath" "*"
"DepotPath" "."
"recursive" "1"
}
"FileExclusion" "*.pdb"
}

View File

@ -1,7 +1,7 @@
"DepotBuildConfig"
{
"DepotID" "1318692"
"contentroot" "C:\work\shapez\shapez.io\tmp_standalone_files\shapez.io-standalone-linux-x64"
"contentroot" "C:\work\shapez.io\tmp_standalone_files\shapez.io-standalone-linux-x64"
"FileMapping"
{
"LocalPath" "*"

View File

@ -1,7 +1,7 @@
"DepotBuildConfig"
{
"DepotID" "1318691"
"contentroot" "C:\work\shapez\shapez.io\tmp_standalone_files\shapez.io-standalone-win32-x64"
"contentroot" "C:\work\shapez.io\tmp_standalone_files\shapez.io-standalone-win32-x64"
"FileMapping"
{
"LocalPath" "*"

View File

@ -25,6 +25,7 @@ function gulptasksTranslations($, gulp) {
files
.filter(name => name.endsWith(".yaml"))
.forEach(fname => {
console.log("Loading", fname);
const languageName = fname.replace(".yaml", "");
const abspath = path.join(translationsSourceDir, fname);
@ -40,39 +41,13 @@ function gulptasksTranslations($, gulp) {
${storePage.intro.replace(/\n/gi, "\n\n")}
[h2]${storePage.title_advantages}[/h2]
[h2]${storePage.what_others_say}[/h2]
[list]
${storePage.advantages
.map(x => "[*] " + x.replace(/<b>/, "[b]").replace(/<\/b>/, "[/b]"))
.join("\n")}
[*] [i]${storePage.northernlion_comment}[/i] [b]- Northernlion, YouTube[/b]
[*] [i]${storePage.notch_comment}[/i] [b]- Notch[/b]
[*] [i]${storePage.steam_review_comment}[/i] [b]- Steam User[/b]
[/list]
[h2]${storePage.title_future}[/h2]
[list]
${storePage.planned
.map(x => "[*] " + x.replace(/<b>/, "[b]").replace(/<\/b>/, "[/b]"))
.join("\n")}
[/list]
[h2]${storePage.title_open_source}[/h2]
${storePage.text_open_source.replace(/\n/gi, "\n\n")}
[h2]${storePage.title_links}[/h2]
[list]
[*] [url=https://discord.com/invite/HN7EVzV]${storePage.links.discord}[/url]
[*] [url=https://trello.com/b/ISQncpJP/shapezio]${storePage.links.roadmap}[/url]
[*] [url=https://www.reddit.com/r/shapezio]${storePage.links.subreddit}[/url]
[*] [url=https://github.com/tobspr/shapez.io]${storePage.links.source_code}[/url]
[*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]${
storePage.links.translate
}[/url]
[/list]
`;
fs.writeFileSync(destpath, trim(content.replace(/(\n[ \t\r]*)/gi, "\n")), {

View File

@ -6,7 +6,7 @@ const { getRevision, getVersion, getAllResourceImages } = require("./buildutils"
const lzString = require("lz-string");
const CircularDependencyPlugin = require("circular-dependency-plugin");
module.exports = ({ watch = false, standalone = false }) => {
module.exports = ({ watch = false, standalone = false, chineseVersion = false }) => {
return {
mode: "development",
devtool: "cheap-source-map",
@ -34,6 +34,7 @@ module.exports = ({ watch = false, standalone = false }) => {
G_TRACKING_ENDPOINT: JSON.stringify(
lzString.compressToEncodedURIComponent("http://localhost:10005/v1")
),
G_CHINA_VERSION: JSON.stringify(chineseVersion),
G_IS_DEV: "true",
G_IS_RELEASE: "false",
G_IS_MOBILE_APP: "false",

View File

@ -16,12 +16,15 @@ module.exports = ({
standalone = false,
isBrowser = true,
mobileApp = false,
chineseVersion = false,
}) => {
const globalDefs = {
assert: enableAssert ? "window.assert" : "false && window.assert",
assertAlways: "window.assert",
abstract: "window.assert(false, 'abstract method called');",
G_IS_DEV: "false",
G_CHINA_VERSION: JSON.stringify(chineseVersion),
G_IS_RELEASE: environment === "prod" ? "true" : "false",
G_IS_STANDALONE: standalone ? "true" : "false",
G_IS_BROWSER: isBrowser ? "true" : "false",

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
"private": true,
"scripts": {
"dev": "cd gulp && yarn gulp main.serveDev",
"devStandalone": "cd gulp && yarn gulp main.serveStandalone",
"tslint": "cd src/js && tsc",
"lint": "eslint src/js",
"prettier-all": "prettier --write src/**/*.* && prettier --write gulp/**/*.* && prettier --write electron/**/*.*",

BIN
res/logo_cn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,18 @@
[data-changelog-skin="achievements"] {
background: #f8f8f8;
@include DarkThemeOverride {
background: rgba(0, 10, 20, 0.2);
}
@include S(border-radius, 5px);
&::before {
content: " ";
width: 100%;
display: block;
background: uiResource("changelog_skins/achievements.noinline.png") center center / cover no-repeat !important;
@include S(height, 80px);
@include S(border-radius, 5px);
@include S(margin-bottom, 5px);
}
}

View File

@ -165,5 +165,15 @@
color: #e72d2d;
}
}
&.achievements {
& {
/* @load-async */
background-image: uiResource("res/ui/icons/advantage_achievements.png");
}
> strong {
color: #ffac0f;
}
}
}
}

View File

@ -19,6 +19,7 @@
@import "application_error";
@import "textual_game_state";
@import "adinplay";
@import "changelog_skins";
@import "states/preload";
@import "states/main_menu";
@ -56,8 +57,8 @@
@import "ingame_hud/cat_memes";
// prettier-ignore
$elements:
// Base
$elements:
// Base
ingame_Canvas,
ingame_VignetteOverlay,
@ -119,11 +120,3 @@ body.uiHidden {
display: none !important;
}
}
body.modalDialogActive,
body.externalAdOpen,
body.ingameDialogOpen {
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs):not(.noBlur) {
// filter: blur(5px) !important;
}
}

View File

@ -145,12 +145,10 @@
pointer-events: all;
transition: all 0.12s ease-in;
transition-property: opacity, transform;
transform: skewX(-0.5deg);
@include S(border-radius, $globalBorderRadius);
&:hover {
transform: scale(1.02);
opacity: 0.9;
}
}
@ -184,7 +182,7 @@
.updateLabel {
position: absolute;
transform: translateX(50%) rotate(-5deg);
color: rgb(231, 78, 58);
color: rgb(133, 58, 231);
@include Heading;
font-weight: bold;
@include S(right, 40px);
@ -484,6 +482,10 @@
box-sizing: border-box;
@include S(grid-gap, 4px);
&.china {
grid-template-columns: auto auto 1fr;
}
.author {
flex-grow: 1;
text-align: right;

View File

@ -14,6 +14,7 @@
padding: 10px;
box-sizing: border-box;
background: #eef1f4;
@include S(border-radius, 3px);
@include DarkThemeOverride {
background: #424242;

View File

@ -11,6 +11,7 @@ import { getPlatformName, waitNextFrame } from "./core/utils";
import { Vector } from "./core/vector";
import { AdProviderInterface } from "./platform/ad_provider";
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
import { NoAchievementProvider } from "./platform/browser/no_achievement_provider";
import { AnalyticsInterface } from "./platform/analytics";
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
import { SoundImplBrowser } from "./platform/browser/sound";
@ -29,6 +30,7 @@ import { PreloadState } from "./states/preload";
import { SettingsState } from "./states/settings";
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
import { RestrictionManager } from "./core/restriction_manager";
import { AchievementProviderInterface } from "./platform/achievement_provider";
const logger = createLogger("application");
@ -91,6 +93,9 @@ export class Application {
/** @type {PlatformWrapperInterface} */
this.platformWrapper = null;
/** @type {AchievementProviderInterface} */
this.achievementProvider = null;
/** @type {AdProviderInterface} */
this.adProvider = null;
@ -143,6 +148,7 @@ export class Application {
this.sound = new SoundImplBrowser(this);
this.analytics = new GoogleAnalyticsImpl(this);
this.gameAnalytics = new ShapezGameAnalytics(this);
this.achievementProvider = new NoAchievementProvider(this);
}
/**

View File

@ -3,11 +3,14 @@
*/
export const CHANGELOG = [
{
version: "1.2.3",
date: "unreleased",
version: "1.3.0",
date: "12.03.2020",
skin: "achievements",
entries: [
"There are now <strong>45 Steam Achievements!</strong>",
"Fixed constant signals being editable from the regular layer",
"Fixed items still overlapping sometimes between buildings and belts",
"Updated translations (Thanks to all contributors!)",
],
},
{

View File

@ -13,7 +13,7 @@ import { cachebust } from "./cachebust";
const logger = createLogger("background_loader");
const essentialMainMenuSprites = [
"logo.png",
G_CHINA_VERSION ? "logo_cn.png" : "logo.png",
...G_ALL_UI_IMAGES.filter(src => src.startsWith("ui/") && src.indexOf(".gif") < 0),
];
const essentialMainMenuSounds = [

View File

@ -40,6 +40,9 @@ export const globalConfig = {
assetsSharpness: 1.5,
shapesSharpness: 1.4,
// Achievements
achievementSliceDuration: 10, // Seconds
// Production analytics
statisticsGraphDpi: 2.5,
statisticsGraphSlices: 100,

View File

@ -59,6 +59,9 @@ export default {
// Enables ads in the local build (normally they are deactivated there)
// testAds: true,
// -----------------------------------------------------------------------------------
// Allows unlocked achievements to be logged to console in the local build
// testAchievements: true,
// -----------------------------------------------------------------------------------
// Disables the automatic switch to an overview when zooming out
// disableMapOverview: true,
// -----------------------------------------------------------------------------------

View File

@ -0,0 +1,148 @@
/* typehints:start */
import { Entity } from "./entity";
import { GameRoot } from "./root";
/* typehints:end */
import { globalConfig } from "../core/config";
import { createLogger } from "../core/logging";
import { ACHIEVEMENTS } from "../platform/achievement_provider";
import { getBuildingDataFromCode } from "./building_codes";
const logger = createLogger("achievement_proxy");
const ROTATER = "rotater";
const DEFAULT = "default";
export class AchievementProxy {
/** @param {GameRoot} root */
constructor(root) {
this.root = root;
this.provider = this.root.app.achievementProvider;
this.disabled = true;
if (G_IS_DEV && globalConfig.debug.testAchievements) {
// still enable the proxy
} else if (!this.provider.hasAchievements()) {
return;
}
this.sliceTime = 0;
this.root.signals.postLoadHook.add(this.onLoad, this);
}
onLoad() {
this.provider
.onLoad(this.root)
.then(() => {
this.disabled = false;
logger.log("Recieving achievement signals");
this.initialize();
})
.catch(err => {
this.disabled = true;
logger.error("Ignoring achievement signals", err);
});
}
initialize() {
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.darkMode);
if (this.has(ACHIEVEMENTS.mam)) {
this.root.signals.entityAdded.add(this.onMamFailure, this);
this.root.signals.entityDestroyed.add(this.onMamFailure, this);
this.root.signals.storyGoalCompleted.add(this.onStoryGoalCompleted, this);
}
if (this.has(ACHIEVEMENTS.noInverseRotater)) {
this.root.signals.entityAdded.add(this.onEntityAdded, this);
}
this.startSlice();
}
startSlice() {
this.sliceTime = this.root.time.now();
this.root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.storeShape,
this.sliceTime,
ACHIEVEMENTS.throughputBp25,
this.sliceTime,
ACHIEVEMENTS.throughputBp50,
this.sliceTime,
ACHIEVEMENTS.throughputLogo25,
this.sliceTime,
ACHIEVEMENTS.throughputLogo50,
this.sliceTime,
ACHIEVEMENTS.throughputRocket10,
this.sliceTime,
ACHIEVEMENTS.throughputRocket20,
this.sliceTime,
ACHIEVEMENTS.play1h,
this.sliceTime,
ACHIEVEMENTS.play10h,
this.sliceTime,
ACHIEVEMENTS.play20h,
this.sliceTime
);
}
update() {
if (this.disabled) {
return;
}
if (this.root.time.now() - this.sliceTime > globalConfig.achievementSliceDuration) {
this.startSlice();
}
}
/**
* @param {string} key
* @returns {boolean}
*/
has(key) {
if (!this.provider.collection) {
return false;
}
return this.provider.collection.map.has(key);
}
/** @param {Entity} entity */
onEntityAdded(entity) {
if (!entity.components.StaticMapEntity) {
return;
}
const building = getBuildingDataFromCode(entity.components.StaticMapEntity.code);
if (building.metaInstance.id !== ROTATER) {
return;
}
if (building.variant === DEFAULT) {
return;
}
this.root.savegame.currentData.stats.usedInverseRotater = true;
this.root.signals.entityAdded.remove(this.onEntityAdded);
}
/** @param {number} level */
onStoryGoalCompleted(level) {
if (level > 26) {
this.root.signals.entityAdded.add(this.onMamFailure, this);
this.root.signals.entityDestroyed.add(this.onMamFailure, this);
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.mam);
// reset on every level
this.root.savegame.currentData.stats.failedMam = false;
}
onMamFailure() {
this.root.savegame.currentData.stats.failedMam = true;
}
}

View File

@ -3,6 +3,7 @@ import { DrawParameters } from "../core/draw_parameters";
import { findNiceIntegerValue } from "../core/utils";
import { Vector } from "../core/vector";
import { Entity } from "./entity";
import { ACHIEVEMENTS } from "../platform/achievement_provider";
import { GameRoot } from "./root";
export class Blueprint {
@ -148,7 +149,7 @@ export class Blueprint {
*/
tryPlace(root, tile) {
return root.logic.performBulkOperation(() => {
let anyPlaced = false;
let count = 0;
for (let i = 0; i < this.entities.length; ++i) {
const entity = this.entities[i];
if (!root.logic.checkCanPlaceEntity(entity, tile, true)) {
@ -160,9 +161,17 @@ export class Blueprint {
root.logic.freeEntityAreaBeforeBuild(clone);
root.map.placeStaticEntity(clone);
root.entityMgr.registerEntity(clone, null, true);
anyPlaced = true;
count++;
}
return anyPlaced;
root.signals.bulkAchievementCheck.dispatch(
ACHIEVEMENTS.placeBlueprint,
count,
ACHIEVEMENTS.placeBp1000,
count
);
return count !== 0;
});
}
}

View File

@ -1,5 +1,6 @@
import { generateMatrixRotations } from "../../core/utils";
import { enumDirection, Vector } from "../../core/vector";
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
import { ItemAcceptorComponent } from "../components/item_acceptor";
import { enumItemProcessorTypes, ItemProcessorComponent } from "../components/item_processor";
import { Entity } from "../entity";
@ -93,6 +94,25 @@ export class MetaTrashBuilding extends MetaBuilding {
return MetaTrashBuilding.renderPins[variant]();
}
addAchievementReceiver(entity) {
if (!entity.root) {
return;
}
const itemProcessor = entity.components.ItemProcessor;
const tryTakeItem = itemProcessor.tryTakeItem.bind(itemProcessor);
itemProcessor.tryTakeItem = () => {
const taken = tryTakeItem(...arguments);
if (taken) {
entity.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.trash1000, 1);
}
return taken;
};
}
/**
* Creates the entity at the given location
* @param {Entity} entity
@ -107,7 +127,7 @@ export class MetaTrashBuilding extends MetaBuilding {
* @param {string} variant
*/
updateVariants(entity, rotationVariant, variant) {
MetaTrashBuilding.componentVariations[variant](entity, rotationVariant);
MetaTrashBuilding.componentVariations[variant].bind(this)(entity, rotationVariant);
}
static setupEntityComponents = [
@ -134,6 +154,10 @@ export class MetaTrashBuilding extends MetaBuilding {
processorType: enumItemProcessorTypes.trash,
})
),
function (entity) {
// @ts-ignore
this.addAchievementReceiver(entity);
},
];
static overlayMatrices = {

View File

@ -36,6 +36,7 @@ import { RegularGameMode } from "./modes/regular";
import { ProductionAnalytics } from "./production_analytics";
import { GameRoot } from "./root";
import { ShapeDefinitionManager } from "./shape_definition_manager";
import { AchievementProxy } from "./achievement_proxy";
import { SoundProxy } from "./sound_proxy";
import { GameTime } from "./time/game_time";
@ -114,6 +115,7 @@ export class GameCore {
root.logic = new GameLogic(root);
root.hud = new GameHUD(root);
root.time = new GameTime(root);
root.achievementProxy = new AchievementProxy(root);
root.automaticSave = new AutomaticSave(root);
root.soundProxy = new SoundProxy(root);
@ -164,6 +166,9 @@ export class GameCore {
// Update analytics
root.productionAnalytics.update();
// Check achievements
root.achievementProxy.update();
}
});
}
@ -294,6 +299,9 @@ export class GameCore {
// Update analytics
root.productionAnalytics.update();
// Check achievements
root.achievementProxy.update();
}
// Update automatic save after everything finished

View File

@ -169,7 +169,7 @@ export class HubGoals extends BasicSerializableObject {
getCurrentGoalDelivered() {
if (this.currentGoal.throughputOnly) {
return (
this.root.productionAnalytics.getCurrentShapeRate(
this.root.productionAnalytics.getCurrentShapeRateRaw(
enumAnalyticsDataSource.delivered,
this.currentGoal.definition
) / globalConfig.analyticsSliceDurationSeconds

View File

@ -8,6 +8,7 @@ import { globalConfig } from "../../../core/config";
import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { createLogger } from "../../../core/logging";
import { ACHIEVEMENTS } from "../../../platform/achievement_provider";
import { enumMouseButton } from "../../camera";
import { T } from "../../../translations";
import { KEYMAPPINGS } from "../../key_action_mapper";
@ -100,6 +101,7 @@ export class HUDMassSelector extends BaseHUDPart {
*/
const mapUidToEntity = this.root.entityMgr.getFrozenUidSearchMap();
let count = 0;
this.root.logic.performBulkOperation(() => {
for (let i = 0; i < entityUids.length; ++i) {
const uid = entityUids[i];
@ -111,8 +113,12 @@ export class HUDMassSelector extends BaseHUDPart {
if (!this.root.logic.tryDeleteBuilding(entity)) {
logger.error("Error in mass delete, could not remove building");
} else {
count++;
}
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.destroy1000, count);
});
// Clear uids later

View File

@ -273,7 +273,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
if (handle.throughputOnly) {
currentValue =
this.root.productionAnalytics.getCurrentShapeRate(
this.root.productionAnalytics.getCurrentShapeRateRaw(
enumAnalyticsDataSource.delivered,
handle.definition
) / globalConfig.analyticsSliceDurationSeconds;

View File

@ -5,7 +5,7 @@ import { T } from "../../../translations";
import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach";
const showIntervalSeconds = 30 * 60;
const showIntervalSeconds = 9 * 60;
export class HUDStandaloneAdvantages extends BaseHUDPart {
createElements(parent) {
@ -25,7 +25,7 @@ export class HUDStandaloneAdvantages extends BaseHUDPart {
([key, trans]) => `
<div class="point ${key}">
<strong>${trans.title}</strong>
<p>${trans.desc}</p>
<p>${trans.desc}</p>
</div>`
)
.join("")}
@ -60,7 +60,7 @@ export class HUDStandaloneAdvantages extends BaseHUDPart {
this.inputReciever = new InputReceiver("standalone-advantages");
this.close();
this.lastShown = this.root.gameIsFresh ? this.root.time.now() : 0;
this.lastShown = -1e10;
}
show() {

View File

@ -209,7 +209,9 @@ export class HUDStatistics extends BaseHUDPart {
}
case enumAnalyticsDataSource.produced:
case enumAnalyticsDataSource.delivered: {
entries = Object.entries(this.root.productionAnalytics.getCurrentShapeRates(this.dataSource));
entries = Object.entries(
this.root.productionAnalytics.getCurrentShapeRatesRaw(this.dataSource)
);
break;
}
}

View File

@ -100,7 +100,7 @@ export class HUDShapeStatisticsHandle {
case enumAnalyticsDataSource.delivered:
case enumAnalyticsDataSource.produced: {
let rate =
this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) /
this.root.productionAnalytics.getCurrentShapeRateRaw(dataSource, this.definition) /
globalConfig.analyticsSliceDurationSeconds;
this.counter.innerText = T.ingame.statistics.shapesDisplayUnits[unit].replace(

View File

@ -15,6 +15,7 @@ import {
removeAllChildren,
} from "../../../core/utils";
import { Vector } from "../../../core/vector";
import { ACHIEVEMENTS } from "../../../platform/achievement_provider";
import { T } from "../../../translations";
import { BaseItem } from "../../base_item";
import { MetaHubBuilding } from "../../buildings/hub";
@ -352,6 +353,10 @@ export class HUDWaypoints extends BaseHUDPart {
T.ingame.waypoints.creationSuccessNotification,
enumNotificationType.success
);
this.root.signals.achievementCheck.dispatch(
ACHIEVEMENTS.mapMarkers15,
this.waypoints.length - 1 // Disregard HUB
);
// Re-render the list and thus add it
this.rerenderWaypointList();

View File

@ -83,7 +83,7 @@ export class ProductionAnalytics extends BasicSerializableObject {
* @param {enumAnalyticsDataSource} dataSource
* @param {ShapeDefinition} definition
*/
getCurrentShapeRate(dataSource, definition) {
getCurrentShapeRateRaw(dataSource, definition) {
const slices = this.history[dataSource];
return slices[slices.length - 2][definition.getHash()] || 0;
}
@ -108,7 +108,7 @@ export class ProductionAnalytics extends BasicSerializableObject {
* Returns the rates of all shapes
* @param {enumAnalyticsDataSource} dataSource
*/
getCurrentShapeRates(dataSource) {
getCurrentShapeRatesRaw(dataSource) {
const slices = this.history[dataSource];
// First, copy current slice

View File

@ -8,6 +8,7 @@ import { createLogger } from "../core/logging";
import { GameTime } from "./time/game_time";
import { EntityManager } from "./entity_manager";
import { GameSystemManager } from "./game_system_manager";
import { AchievementProxy } from "./achievement_proxy";
import { GameHUD } from "./hud/hud";
import { MapView } from "./map_view";
import { Camera } from "./camera";
@ -119,6 +120,9 @@ export class GameRoot {
/** @type {SoundProxy} */
this.soundProxy = null;
/** @type {AchievementProxy} */
this.achievementProxy = null;
/** @type {ShapeDefinitionManager} */
this.shapeDefinitionMgr = null;
@ -175,6 +179,10 @@ export class GameRoot {
// Called before actually placing an entity, use to perform additional logic
// for freeing space before actually placing.
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
// Called with an achievement key and necessary args to validate it can be unlocked.
achievementCheck: /** @type {TypedSignal<[string, *]>} */ (new Signal()),
bulkAchievementCheck: /** @type {TypedSignal<(string|any)[]>} */ (new Signal()),
};
// RNG's

View File

@ -4,6 +4,7 @@ import { enumColors } from "./colors";
import { ShapeItem } from "./items/shape_item";
import { GameRoot } from "./root";
import { enumSubShape, ShapeDefinition } from "./shape_definition";
import { ACHIEVEMENTS } from "../platform/achievement_provider";
const logger = createLogger("shape_definition_manager");
@ -96,6 +97,8 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rightSide = definition.cloneFilteredByQuadrants([2, 3]);
const leftSide = definition.cloneFilteredByQuadrants([0, 1]);
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.cutShape);
return /** @type {[ShapeDefinition, ShapeDefinition]} */ (this.operationCache[key] = [
this.registerOrReturnHandle(rightSide),
this.registerOrReturnHandle(leftSide),
@ -137,6 +140,8 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
const rotated = definition.cloneRotateCW();
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.rotateShape);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
rotated
));
@ -189,6 +194,9 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
if (this.operationCache[key]) {
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.stackShape);
const stacked = lowerDefinition.cloneAndStackWith(upperDefinition);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
stacked
@ -206,6 +214,9 @@ export class ShapeDefinitionManager extends BasicSerializableObject {
if (this.operationCache[key]) {
return /** @type {ShapeDefinition} */ (this.operationCache[key]);
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.paintShape);
const colorized = definition.cloneAndPaintWith(color);
return /** @type {ShapeDefinition} */ (this.operationCache[key] = this.registerOrReturnHandle(
colorized

View File

@ -4,6 +4,7 @@ import { createLogger } from "../../core/logging";
import { Rectangle } from "../../core/rectangle";
import { StaleAreaDetector } from "../../core/stale_area_detector";
import { enumDirection, enumDirectionToVector } from "../../core/vector";
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
import { BaseItem } from "../base_item";
import { BeltComponent } from "../components/belt";
import { ItemAcceptorComponent } from "../components/item_acceptor";

View File

@ -13,6 +13,7 @@ import {
enumInvertedDirections,
Vector,
} from "../../core/vector";
import { ACHIEVEMENTS } from "../../platform/achievement_provider";
import { BaseItem } from "../base_item";
import { MetaWireBuilding } from "../buildings/wire";
import { getCodeFromBuildingData } from "../building_codes";
@ -702,6 +703,8 @@ export class WireSystem extends GameSystemWithFilter {
return;
}
this.root.signals.achievementCheck.dispatch(ACHIEVEMENTS.place5000Wires, entity);
// Invalidate affected area
const originalRect = staticComp.getTileSpaceBounds();
const affectedArea = originalRect.expandedInAllDirections(1);

2
src/js/globals.d.ts vendored
View File

@ -19,6 +19,8 @@ declare const G_BUILD_VERSION: string;
declare const G_ALL_UI_IMAGES: Array<string>;
declare const G_IS_RELEASE: boolean;
declare const G_CHINA_VERSION : boolean;
// Polyfills
declare interface String {
replaceAll(search: string, replacement: string): string;

View File

@ -0,0 +1,634 @@
/* typehints:start */
import { Application } from "../application";
import { Entity } from "../game/entity";
import { GameRoot } from "../game/root";
import { ShapeDefinition } from "../game/shape_definition";
import { THEMES } from "../game/theme";
/* typehints:end */
import { enumAnalyticsDataSource } from "../game/production_analytics";
import { ShapeItem } from "../game/items/shape_item";
import { globalConfig } from "../core/config";
export const ACHIEVEMENTS = {
belt500Tiles: "belt500Tiles",
blueprint100k: "blueprint100k",
blueprint1m: "blueprint1m",
completeLvl26: "completeLvl26",
cutShape: "cutShape",
darkMode: "darkMode",
destroy1000: "destroy1000",
irrelevantShape: "irrelevantShape",
level100: "level100",
level50: "level50",
logoBefore18: "logoBefore18",
mam: "mam",
mapMarkers15: "mapMarkers15",
noBeltUpgradesUntilBp: "noBeltUpgradesUntilBp",
noInverseRotater: "noInverseRotater",
oldLevel17: "oldLevel17",
openWires: "openWires",
paintShape: "paintShape",
place5000Wires: "place5000Wires",
placeBlueprint: "placeBlueprint",
placeBp1000: "placeBp1000",
play1h: "play1h",
play10h: "play10h",
play20h: "play20h",
produceLogo: "produceLogo",
produceMsLogo: "produceMsLogo",
produceRocket: "produceRocket",
rotateShape: "rotateShape",
speedrunBp30: "speedrunBp30",
speedrunBp60: "speedrunBp60",
speedrunBp120: "speedrunBp120",
stack4Layers: "stack4Layers",
stackShape: "stackShape",
store100Unique: "store100Unique",
storeShape: "storeShape",
throughputBp25: "throughputBp25",
throughputBp50: "throughputBp50",
throughputLogo25: "throughputLogo25",
throughputLogo50: "throughputLogo50",
throughputRocket10: "throughputRocket10",
throughputRocket20: "throughputRocket20",
trash1000: "trash1000",
unlockWires: "unlockWires",
upgradesTier5: "upgradesTier5",
upgradesTier8: "upgradesTier8",
};
/** @type {keyof typeof THEMES} */
const DARK_MODE = "dark";
const HOUR_1 = 3600; // Seconds
const HOUR_10 = HOUR_1 * 10;
const HOUR_20 = HOUR_1 * 20;
const ITEM_SHAPE = ShapeItem.getId();
const MINUTE_30 = 1800; // Seconds
const MINUTE_60 = MINUTE_30 * 2;
const MINUTE_120 = MINUTE_30 * 4;
const ROTATER_CCW_CODE = 12;
const ROTATER_180_CODE = 13;
const SHAPE_BP = "CbCbCbRb:CwCwCwCw";
const SHAPE_LOGO = "RuCw--Cw:----Ru--";
const SHAPE_MS_LOGO = "RgRyRbRr";
const SHAPE_OLD_LEVEL_17 = "WrRgWrRg:CwCrCwCr:SgSgSgSg";
const SHAPE_ROCKET = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
/** @type {Layer} */
const WIRE_LAYER = "wires";
export class AchievementProviderInterface {
/* typehints:start */
collection = /** @type {AchievementCollection|undefined} */ (null);
/* typehints:end */
/** @param {Application} app */
constructor(app) {
this.app = app;
}
/**
* Initializes the achievement provider.
* @returns {Promise<void>}
*/
initialize() {
abstract;
return Promise.reject();
}
/**
* Opportunity to do additional initialization work with the GameRoot.
* @param {GameRoot} root
* @returns {Promise<void>}
*/
onLoad(root) {
abstract;
return Promise.reject();
}
/** @returns {boolean} */
hasLoaded() {
abstract;
return false;
}
/**
* Call to activate an achievement with the provider
* @param {string} key - Maps to an Achievement
* @returns {Promise<void>}
*/
activate(key) {
abstract;
return Promise.reject();
}
/**
* Checks if achievements are supported in the current build
* @returns {boolean}
*/
hasAchievements() {
abstract;
return false;
}
}
export class Achievement {
/** @param {string} key - An ACHIEVEMENTS key */
constructor(key) {
this.key = key;
this.activate = null;
this.activatePromise = null;
this.receiver = null;
this.signal = null;
}
init() {}
isValid() {
return true;
}
unlock() {
if (!this.activatePromise) {
this.activatePromise = this.activate(this.key);
}
return this.activatePromise;
}
}
export class AchievementCollection {
/**
* @param {function} activate - Resolves when provider activation is complete
*/
constructor(activate) {
this.map = new Map();
this.activate = activate;
this.add(ACHIEVEMENTS.belt500Tiles, {
isValid: this.isBelt500TilesValid,
signal: "entityAdded",
});
this.add(ACHIEVEMENTS.blueprint100k, {
isValid: this.isBlueprint100kValid,
signal: "shapeDelivered",
});
this.add(ACHIEVEMENTS.blueprint1m, {
isValid: this.isBlueprint1mValid,
signal: "shapeDelivered",
});
this.add(ACHIEVEMENTS.completeLvl26, this.createLevelOptions(26));
this.add(ACHIEVEMENTS.cutShape);
this.add(ACHIEVEMENTS.darkMode, {
isValid: this.isDarkModeValid,
});
this.add(ACHIEVEMENTS.destroy1000, {
isValid: this.isDestroy1000Valid,
});
this.add(ACHIEVEMENTS.irrelevantShape, {
isValid: this.isIrrelevantShapeValid,
signal: "shapeDelivered",
});
this.add(ACHIEVEMENTS.level100, this.createLevelOptions(100));
this.add(ACHIEVEMENTS.level50, this.createLevelOptions(50));
this.add(ACHIEVEMENTS.logoBefore18, {
isValid: this.isLogoBefore18Valid,
signal: "itemProduced",
});
this.add(ACHIEVEMENTS.mam, {
isValid: this.isMamValid,
});
this.add(ACHIEVEMENTS.mapMarkers15, {
isValid: this.isMapMarkers15Valid,
});
this.add(ACHIEVEMENTS.noBeltUpgradesUntilBp, {
isValid: this.isNoBeltUpgradesUntilBpValid,
signal: "storyGoalCompleted",
});
this.add(ACHIEVEMENTS.noInverseRotater, {
init: this.initNoInverseRotater,
isValid: this.isNoInverseRotaterValid,
signal: "storyGoalCompleted",
});
this.add(ACHIEVEMENTS.oldLevel17, this.createShapeOptions(SHAPE_OLD_LEVEL_17));
this.add(ACHIEVEMENTS.openWires, {
isValid: this.isOpenWiresValid,
signal: "editModeChanged",
});
this.add(ACHIEVEMENTS.paintShape);
this.add(ACHIEVEMENTS.place5000Wires, {
isValid: this.isPlace5000WiresValid,
});
this.add(ACHIEVEMENTS.placeBlueprint, {
isValid: this.isPlaceBlueprintValid,
});
this.add(ACHIEVEMENTS.placeBp1000, {
isValid: this.isPlaceBp1000Valid,
});
this.add(ACHIEVEMENTS.play1h, this.createTimeOptions(HOUR_1));
this.add(ACHIEVEMENTS.play10h, this.createTimeOptions(HOUR_10));
this.add(ACHIEVEMENTS.play20h, this.createTimeOptions(HOUR_20));
this.add(ACHIEVEMENTS.produceLogo, this.createShapeOptions(SHAPE_LOGO));
this.add(ACHIEVEMENTS.produceRocket, this.createShapeOptions(SHAPE_ROCKET));
this.add(ACHIEVEMENTS.produceMsLogo, this.createShapeOptions(SHAPE_MS_LOGO));
this.add(ACHIEVEMENTS.rotateShape);
this.add(ACHIEVEMENTS.speedrunBp30, this.createSpeedOptions(12, MINUTE_30));
this.add(ACHIEVEMENTS.speedrunBp60, this.createSpeedOptions(12, MINUTE_60));
this.add(ACHIEVEMENTS.speedrunBp120, this.createSpeedOptions(12, MINUTE_120));
this.add(ACHIEVEMENTS.stack4Layers, {
isValid: this.isStack4LayersValid,
signal: "itemProduced",
});
this.add(ACHIEVEMENTS.stackShape);
this.add(ACHIEVEMENTS.store100Unique, {
isValid: this.isStore100UniqueValid,
signal: "shapeDelivered",
});
this.add(ACHIEVEMENTS.storeShape, {
isValid: this.isStoreShapeValid,
});
this.add(ACHIEVEMENTS.throughputBp25, this.createRateOptions(SHAPE_BP, 25));
this.add(ACHIEVEMENTS.throughputBp50, this.createRateOptions(SHAPE_BP, 50));
this.add(ACHIEVEMENTS.throughputLogo25, this.createRateOptions(SHAPE_LOGO, 25));
this.add(ACHIEVEMENTS.throughputLogo50, this.createRateOptions(SHAPE_LOGO, 50));
this.add(ACHIEVEMENTS.throughputRocket10, this.createRateOptions(SHAPE_ROCKET, 25));
this.add(ACHIEVEMENTS.throughputRocket20, this.createRateOptions(SHAPE_ROCKET, 50));
this.add(ACHIEVEMENTS.trash1000, {
init: this.initTrash1000,
isValid: this.isTrash1000Valid,
});
this.add(ACHIEVEMENTS.unlockWires, this.createLevelOptions(20));
this.add(ACHIEVEMENTS.upgradesTier5, this.createUpgradeOptions(5));
this.add(ACHIEVEMENTS.upgradesTier8, this.createUpgradeOptions(8));
}
/** @param {GameRoot} root */
initialize(root) {
this.root = root;
this.root.signals.achievementCheck.add(this.unlock, this);
this.root.signals.bulkAchievementCheck.add(this.bulkUnlock, this);
for (let [key, achievement] of this.map.entries()) {
if (achievement.init) {
achievement.init();
}
if (achievement.signal) {
achievement.receiver = this.unlock.bind(this, key);
this.root.signals[achievement.signal].add(achievement.receiver);
}
}
if (!this.hasDefaultReceivers()) {
this.root.signals.achievementCheck.remove(this.unlock);
this.root.signals.bulkAchievementCheck.remove(this.bulkUnlock);
}
}
/**
* @param {string} key - Maps to an Achievement
* @param {object} [options]
* @param {function} [options.init]
* @param {function} [options.isValid]
* @param {string} [options.signal]
*/
add(key, options = {}) {
if (G_IS_DEV) {
assert(ACHIEVEMENTS[key], "Achievement key not found: ", key);
}
const achievement = new Achievement(key);
achievement.activate = this.activate;
if (options.init) {
achievement.init = options.init.bind(this, achievement);
}
if (options.isValid) {
achievement.isValid = options.isValid.bind(this);
}
if (options.signal) {
achievement.signal = options.signal;
}
this.map.set(key, achievement);
}
bulkUnlock() {
for (let i = 0; i < arguments.length; i += 2) {
this.unlock(arguments[i], arguments[i + 1]);
}
}
/**
* @param {string} key - Maps to an Achievement
* @param {?*} data - Data received from signal dispatches for validation
*/
unlock(key, data) {
if (!this.map.has(key)) {
return;
}
const achievement = this.map.get(key);
if (!achievement.isValid(data)) {
return;
}
achievement
.unlock()
.then(() => {
this.onActivate(null, key);
})
.catch(err => {
this.onActivate(err, key);
});
}
/**
* Cleans up after achievement activation attempt with the provider. Could
* utilize err to retry some number of times if needed.
* @param {?Error} err - Error is null if activation was successful
* @param {string} key - Maps to an Achievement
*/
onActivate(err, key) {
this.remove(key);
if (!this.hasDefaultReceivers()) {
this.root.signals.achievementCheck.remove(this.unlock);
}
}
/** @param {string} key - Maps to an Achievement */
remove(key) {
const achievement = this.map.get(key);
if (achievement) {
if (achievement.receiver) {
this.root.signals[achievement.signal].remove(achievement.receiver);
}
this.map.delete(key);
}
}
/**
* Check if the collection-level achievementCheck receivers are still
* necessary.
*/
hasDefaultReceivers() {
if (!this.map.size) {
return false;
}
for (let achievement of this.map.values()) {
if (!achievement.signal) {
return true;
}
}
return false;
}
/*
* Remaining methods exist to extend Achievement instances within the
* collection.
*/
hasAllUpgradesAtLeastAtTier(tier) {
const upgrades = this.root.gameMode.getUpgrades();
for (let upgradeId in upgrades) {
if (this.root.hubGoals.getUpgradeLevel(upgradeId) < tier - 1) {
return false;
}
}
return true;
}
/**
* @param {ShapeItem} item
* @param {string} shape
* @returns {boolean}
*/
isShape(item, shape) {
return item.getItemType() === ITEM_SHAPE && item.definition.getHash() === shape;
}
createLevelOptions(level) {
return {
isValid: currentLevel => currentLevel >= level,
signal: "storyGoalCompleted",
};
}
createRateOptions(shape, rate) {
return {
isValid: () => {
return (
this.root.productionAnalytics.getCurrentShapeRateRaw(
enumAnalyticsDataSource.delivered,
this.root.shapeDefinitionMgr.getShapeFromShortKey(shape)
) /
globalConfig.analyticsSliceDurationSeconds >=
rate
);
},
};
}
createShapeOptions(shape) {
return {
isValid: item => this.isShape(item, shape),
signal: "itemProduced",
};
}
createSpeedOptions(level, time) {
return {
isValid: currentLevel => currentLevel >= level && this.root.time.now() < time,
signal: "storyGoalCompleted",
};
}
createTimeOptions(duration) {
return {
isValid: () => this.root.time.now() >= duration,
};
}
createUpgradeOptions(tier) {
return {
isValid: () => this.hasAllUpgradesAtLeastAtTier(tier),
signal: "upgradePurchased",
};
}
/** @param {Entity} entity @returns {boolean} */
isBelt500TilesValid(entity) {
return entity.components.Belt && entity.components.Belt.assignedPath.totalLength >= 500;
}
/** @param {ShapeDefinition} definition @returns {boolean} */
isBlueprint100kValid(definition) {
return definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= 100000;
}
/** @param {ShapeDefinition} definition @returns {boolean} */
isBlueprint1mValid(definition) {
return definition.cachedHash === SHAPE_BP && this.root.hubGoals.storedShapes[SHAPE_BP] >= 1000000;
}
/** @returns {boolean} */
isDarkModeValid() {
return this.root.app.settings.currentData.settings.theme === DARK_MODE;
}
/** @param {number} count @returns {boolean} */
isDestroy1000Valid(count) {
return count >= 1000;
}
/** @param {ShapeDefinition} definition @returns {boolean} */
isIrrelevantShapeValid(definition) {
if (definition.cachedHash === this.root.hubGoals.currentGoal.definition.cachedHash) {
return false;
}
if (definition.cachedHash === this.root.gameMode.getBlueprintShapeKey()) {
return false;
}
const upgrades = this.root.gameMode.getUpgrades();
for (let upgradeId in upgrades) {
for (const tier in upgrades[upgradeId]) {
const requiredShapes = upgrades[upgradeId][tier].required;
for (let i = 0; i < requiredShapes.length; i++) {
if (definition.cachedHash === requiredShapes[i].shape) {
return false;
}
}
}
}
return true;
}
/** @param {ShapeItem} item @returns {boolean} */
isLogoBefore18Valid(item) {
return this.root.hubGoals.level < 18 && this.isShape(item, SHAPE_LOGO);
}
/** @params {number} level @returns {boolean} */
isMamValid() {
return this.root.hubGoals.level > 27 && !this.root.savegame.currentData.stats.failedMam;
}
/** @param {number} count @returns {boolean} */
isMapMarkers15Valid(count) {
return count >= 15;
}
/**
* @param {number} level
* @returns {boolean}
*/
isNoBeltUpgradesUntilBpValid(level) {
return level >= 12 && this.root.hubGoals.upgradeLevels.belt === 0;
}
initNoInverseRotater() {
if (this.root.savegame.currentData.stats.usedInverseRotater === true) {
return;
}
const entities = this.root.entityMgr.componentToEntity.StaticMapEntity;
let usedInverseRotater = false;
for (var i = 0; i < entities.length; i++) {
const entity = entities[i].components.StaticMapEntity;
if (entity.code === ROTATER_CCW_CODE || entity.code === ROTATER_180_CODE) {
usedInverseRotater = true;
break;
}
}
this.root.savegame.currentData.stats.usedInverseRotater = usedInverseRotater;
}
/** @param {number} level @returns {boolean} */
isNoInverseRotaterValid(level) {
return level >= 14 && !this.root.savegame.currentData.stats.usedInverseRotater;
}
/** @param {string} currentLayer @returns {boolean} */
isOpenWiresValid(currentLayer) {
return currentLayer === WIRE_LAYER;
}
/** @param {Entity} entity @returns {boolean} */
isPlace5000WiresValid(entity) {
return (
entity.components.Wire &&
entity.registered &&
entity.root.entityMgr.componentToEntity.Wire.length >= 5000
);
}
/** @param {number} count @returns {boolean} */
isPlaceBlueprintValid(count) {
return count != 0;
}
/** @param {number} count @returns {boolean} */
isPlaceBp1000Valid(count) {
return count >= 1000;
}
/** @param {ShapeItem} item @returns {boolean} */
isStack4LayersValid(item) {
return item.getItemType() === ITEM_SHAPE && item.definition.layers.length === 4;
}
/** @returns {boolean} */
isStore100UniqueValid() {
return Object.keys(this.root.hubGoals.storedShapes).length >= 100;
}
/** @returns {boolean} */
isStoreShapeValid() {
const entities = this.root.systemMgr.systems.storage.allEntities;
if (entities.length === 0) {
return false;
}
for (var i = 0; i < entities.length; i++) {
if (entities[i].components.Storage.storedCount > 0) {
return true;
}
}
return false;
}
initTrash1000() {
if (Number(this.root.savegame.currentData.stats.trashedCount)) {
return;
}
this.root.savegame.currentData.stats.trashedCount = 0;
}
/** @params {number} count @returns {boolean} */
isTrash1000Valid(count) {
this.root.savegame.currentData.stats.trashedCount += count;
return this.root.savegame.currentData.stats.trashedCount >= 1000;
}
}

View File

@ -0,0 +1,23 @@
import { AchievementProviderInterface } from "../achievement_provider";
export class NoAchievementProvider extends AchievementProviderInterface {
hasAchievements() {
return false;
}
hasLoaded() {
return false;
}
initialize() {
return Promise.resolve();
}
onLoad() {
return Promise.reject(new Error("No achievements to load"));
}
activate() {
return Promise.resolve();
}
}

View File

@ -4,7 +4,9 @@ import { queryParamOptions } from "../../core/query_parameters";
import { clamp } from "../../core/utils";
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
import { NoAdProvider } from "../ad_providers/no_ad_provider";
import { SteamAchievementProvider } from "../electron/steam_achievement_provider";
import { PlatformWrapperInterface } from "../wrapper";
import { NoAchievementProvider } from "./no_achievement_provider";
import { StorageImplBrowser } from "./storage";
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
@ -71,6 +73,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
return this.detectStorageImplementation()
.then(() => this.initializeAdProvider())
.then(() => this.initializeAchievementProvider())
.then(() => super.initialize());
}
@ -196,6 +199,20 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
});
}
initializeAchievementProvider() {
if (G_IS_DEV && globalConfig.debug.testAchievements) {
this.app.achievementProvider = new SteamAchievementProvider(this.app);
return this.app.achievementProvider.initialize().catch(err => {
logger.error("Failed to initialize achievement provider, disabling:", err);
this.app.achievementProvider = new NoAchievementProvider(this.app);
});
}
return this.app.achievementProvider.initialize();
}
exitApp() {
// Can not exit app
}

View File

@ -0,0 +1,148 @@
/* typehints:start */
import { Application } from "../../application";
import { GameRoot } from "../../game/root";
/* typehints:end */
import { createLogger } from "../../core/logging";
import { getIPCRenderer } from "../../core/utils";
import {
ACHIEVEMENTS,
AchievementCollection,
AchievementProviderInterface
} from "../achievement_provider";
const logger = createLogger("achievements/steam");
const ACHIEVEMENT_IDS = {
[ACHIEVEMENTS.belt500Tiles]: "belt_500_tiles",
[ACHIEVEMENTS.blueprint100k]: "blueprint_100k",
[ACHIEVEMENTS.blueprint1m]: "blueprint_1m",
[ACHIEVEMENTS.completeLvl26]: "complete_lvl_26",
[ACHIEVEMENTS.cutShape]: "cut_shape",
[ACHIEVEMENTS.darkMode]: "dark_mode",
[ACHIEVEMENTS.destroy1000]: "destroy_1000",
[ACHIEVEMENTS.irrelevantShape]: "irrelevant_shape",
[ACHIEVEMENTS.level100]: "level_100",
[ACHIEVEMENTS.level50]: "level_50",
[ACHIEVEMENTS.logoBefore18]: "logo_before_18",
[ACHIEVEMENTS.mam]: "mam",
[ACHIEVEMENTS.mapMarkers15]: "map_markers_15",
[ACHIEVEMENTS.openWires]: "open_wires",
[ACHIEVEMENTS.oldLevel17]: "old_level_17",
[ACHIEVEMENTS.noBeltUpgradesUntilBp]: "no_belt_upgrades_until_bp",
[ACHIEVEMENTS.noInverseRotater]: "no_inverse_rotator", // [sic]
[ACHIEVEMENTS.paintShape]: "paint_shape",
[ACHIEVEMENTS.place5000Wires]: "place_5000_wires",
[ACHIEVEMENTS.placeBlueprint]: "place_blueprint",
[ACHIEVEMENTS.placeBp1000]: "place_bp_1000",
[ACHIEVEMENTS.play1h]: "play_1h",
[ACHIEVEMENTS.play10h]: "play_10h",
[ACHIEVEMENTS.play20h]: "play_20h",
[ACHIEVEMENTS.produceLogo]: "produce_logo",
[ACHIEVEMENTS.produceMsLogo]: "produce_ms_logo",
[ACHIEVEMENTS.produceRocket]: "produce_rocket",
[ACHIEVEMENTS.rotateShape]: "rotate_shape",
[ACHIEVEMENTS.speedrunBp30]: "speedrun_bp_30",
[ACHIEVEMENTS.speedrunBp60]: "speedrun_bp_60",
[ACHIEVEMENTS.speedrunBp120]: "speedrun_bp_120",
[ACHIEVEMENTS.stack4Layers]: "stack_4_layers",
[ACHIEVEMENTS.stackShape]: "stack_shape",
[ACHIEVEMENTS.store100Unique]: "store_100_unique",
[ACHIEVEMENTS.storeShape]: "store_shape",
[ACHIEVEMENTS.throughputBp25]: "throughput_bp_25",
[ACHIEVEMENTS.throughputBp50]: "throughput_bp_50",
[ACHIEVEMENTS.throughputLogo25]: "throughput_logo_25",
[ACHIEVEMENTS.throughputLogo50]: "throughput_logo_50",
[ACHIEVEMENTS.throughputRocket10]: "throughput_rocket_10",
[ACHIEVEMENTS.throughputRocket20]: "throughput_rocket_20",
[ACHIEVEMENTS.trash1000]: "trash_1000",
[ACHIEVEMENTS.unlockWires]: "unlock_wires",
[ACHIEVEMENTS.upgradesTier5]: "upgrades_tier_5",
[ACHIEVEMENTS.upgradesTier8]: "upgrades_tier_8",
};
export class SteamAchievementProvider extends AchievementProviderInterface {
/** @param {Application} app */
constructor(app) {
super(app);
this.initialized = false;
this.collection = new AchievementCollection(this.activate.bind(this));
if (G_IS_DEV) {
for (let key in ACHIEVEMENT_IDS) {
assert(this.collection.map.has(key), "Key not found in collection: " + key);
}
}
logger.log("Collection created with", this.collection.map.size, "achievements");
}
/** @returns {boolean} */
hasAchievements() {
return true;
}
/**
* @param {GameRoot} root
* @returns {Promise<void>}
*/
onLoad(root) {
this.root = root;
try {
this.collection = new AchievementCollection(this.activate.bind(this));
this.collection.initialize(root);
logger.log("Initialized", this.collection.map.size, "relevant achievements");
return Promise.resolve();
} catch (err) {
logger.error("Failed to initialize the collection");
return Promise.reject(err);
}
}
/** @returns {Promise<void>} */
initialize() {
if (!G_IS_STANDALONE) {
logger.warn("Steam unavailable. Achievements won't sync.");
return Promise.resolve();
}
this.ipc = getIPCRenderer();
return this.ipc.invoke("steam:is-initialized")
.then(initialized => {
this.initialized = initialized;
if (!this.initialized) {
logger.warn("Steam failed to intialize. Achievements won't sync.");
} else {
logger.log("Steam achievement provider initialized");
}
});
}
/**
* @param {string} key
* @returns {Promise<void>}
*/
activate(key) {
let promise;
if (!this.initialized) {
promise = Promise.resolve();
} else {
promise = this.ipc.invoke("steam:activate-achievement", ACHIEVEMENT_IDS[key]);
}
return promise
.then(() => {
logger.log("Achievement activated:", key);
})
.catch(err => {
logger.error("Failed to activate achievement:", key, err);
throw err;
});
}
}

View File

@ -1,15 +1,34 @@
import { NoAchievementProvider } from "../browser/no_achievement_provider";
import { PlatformWrapperImplBrowser } from "../browser/wrapper";
import { getIPCRenderer } from "../../core/utils";
import { createLogger } from "../../core/logging";
import { StorageImplElectron } from "./storage";
import { SteamAchievementProvider } from "./steam_achievement_provider";
import { PlatformWrapperInterface } from "../wrapper";
const logger = createLogger("electron-wrapper");
export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
initialize() {
this.steamOverlayCanvasFix = document.createElement("canvas");
this.steamOverlayCanvasFix.width = 1;
this.steamOverlayCanvasFix.height = 1;
this.steamOverlayCanvasFix.id = "steamOverlayCanvasFix";
this.steamOverlayContextFix = this.steamOverlayCanvasFix.getContext("2d");
document.documentElement.appendChild(this.steamOverlayCanvasFix);
this.app.ticker.frameEmitted.add(this.steamOverlayFixRedrawCanvas, this);
this.app.storage = new StorageImplElectron(this);
return PlatformWrapperInterface.prototype.initialize.call(this);
this.app.achievementProvider = new SteamAchievementProvider(this.app);
return this.initializeAchievementProvider()
.then(() => PlatformWrapperInterface.prototype.initialize.call(this));
}
steamOverlayFixRedrawCanvas() {
this.steamOverlayContextFix.clearRect(0, 0, 1, 1);
}
getId() {
@ -38,6 +57,15 @@ export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
return Promise.resolve();
}
initializeAchievementProvider() {
return this.app.achievementProvider.initialize()
.catch(err => {
logger.error("Failed to initialize achievement provider, disabling:", err);
this.app.achievementProvider = new NoAchievementProvider(this.app);
});
}
getSupportsFullscreen() {
return true;
}

View File

@ -12,6 +12,7 @@ import { SavegameInterface_V1004 } from "./schemas/1004";
import { SavegameInterface_V1005 } from "./schemas/1005";
import { SavegameInterface_V1006 } from "./schemas/1006";
import { SavegameInterface_V1007 } from "./schemas/1007";
import { SavegameInterface_V1008 } from "./schemas/1008";
import { SavegameInterface_ML01 } from "./schemas/ML01";
import { RegularGameMode } from "../game/modes/regular";
@ -70,7 +71,11 @@ export class Savegame extends ReadWriteProxy {
return {
version: this.getCurrentVersion(),
dump: null,
stats: {},
stats: {
failedMam: false,
trashedCount: 0,
usedInverseRotater: false,
},
lastUpdate: Date.now(),
gamemode: RegularGameMode.getId(),
};
@ -121,7 +126,12 @@ export class Savegame extends ReadWriteProxy {
}
if (data.version === 1007) {
SavegameInterface_ML01.migrate1007toML01(data);
SavegameInterface_V1008.migrate1007to1008(data);
data.version = 1008;
}
if (data.version === 1008) {
SavegameInterface_ML01.migrate1008toML01(data);
data.version = "ML01";
}

View File

@ -8,6 +8,7 @@ import { SavegameInterface_V1004 } from "./schemas/1004";
import { SavegameInterface_V1005 } from "./schemas/1005";
import { SavegameInterface_V1006 } from "./schemas/1006";
import { SavegameInterface_V1007 } from "./schemas/1007";
import { SavegameInterface_V1008 } from "./schemas/1008";
import { SavegameInterface_ML01 } from "./schemas/ML01";
/** @type {Object.<any, typeof BaseSavegameInterface>} */
@ -20,6 +21,7 @@ export const savegameInterfaces = {
1005: SavegameInterface_V1005,
1006: SavegameInterface_V1006,
1007: SavegameInterface_V1007,
1008: SavegameInterface_V1008,
ML01: SavegameInterface_ML01,
};

View File

@ -1,5 +1,11 @@
/**
* @typedef {{}} SavegameStats
* @typedef {import("../game/entity").Entity} Entity
*
* @typedef {{
* failedMam: boolean,
* trashedCount: number,
* usedInverseRotater: boolean
* }} SavegameStats
*
* @typedef {{
* camera: any,

View File

@ -0,0 +1,32 @@
import { createLogger } from "../../core/logging.js";
import { SavegameInterface_V1007 } from "./1007.js";
const schema = require("./1008.json");
const logger = createLogger("savegame_interface/1008");
export class SavegameInterface_V1008 extends SavegameInterface_V1007 {
getVersion() {
return 1008;
}
getSchemaUncached() {
return schema;
}
/**
* @param {import("../savegame_typedefs.js").SavegameData} data
*/
static migrate1007to1008(data) {
logger.log("Migrating 1007 to 1008");
const dump = data.dump;
if (!dump) {
return true;
}
Object.assign(data.stats, {
failedMam: true,
trashedCount: 0,
usedInverseRotater: true,
});
}
}

View File

@ -0,0 +1,5 @@
{
"type": "object",
"required": [],
"additionalProperties": true
}

View File

@ -15,7 +15,9 @@ export class AboutState extends TextualGameState {
getMainContentHTML() {
return `
<div class="head">
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<img src="${cachebust(
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
)}" alt="shapez.io Logo">
</div>
<div class="text">
${T.about.body

View File

@ -19,7 +19,7 @@ export class ChangelogState extends TextualGameState {
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
html += `
<div class="entry">
<div class="entry" data-changelog-skin="${entry.skin || "default"}">
<span class="version">${entry.version}</span>
<span class="date">${entry.date}</span>
<ul class="changes">

View File

@ -42,7 +42,12 @@ export class MainMenuState extends GameState {
return `
<div class="topButtons">
<button class="languageChoose" data-languageicon="${this.app.settings.getLanguage()}"></button>
${
G_CHINA_VERSION
? ""
: `<button class="languageChoose" data-languageicon="${this.app.settings.getLanguage()}"></button>`
}
<button class="settingsButton"></button>
${this.getExtraTopButtons()}
${
@ -59,8 +64,10 @@ export class MainMenuState extends GameState {
</video>
<div class="logo">
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<span class="updateLabel">v${G_BUILD_VERSION}</span>
<img src="${cachebust(
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
)}" alt="shapez.io Logo">
<span class="updateLabel">v${G_BUILD_VERSION} - Achievements!</span>
</div>
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">
@ -78,11 +85,17 @@ export class MainMenuState extends GameState {
</div>
</div>
<div class="footer">
<div class="footer ${G_CHINA_VERSION ? "china" : ""}">
${
G_CHINA_VERSION
? ""
: `
<a class="githubLink boxLink" target="_blank">
${T.mainMenu.openSourceHint}
<span class="thirdpartyLogo githubLogo"></span>
</a>
</a>`
}
<a class="discordLink boxLink" target="_blank">
${T.mainMenu.discordLink}
@ -90,11 +103,11 @@ export class MainMenuState extends GameState {
</a>
<div class="sidelinks">
<a class="redditLink">${T.mainMenu.subreddit}</a>
${G_CHINA_VERSION ? "" : `<a class="redditLink">${T.mainMenu.subreddit}</a>`}
<a class="changelog">${T.changelog.title}</a>
${G_CHINA_VERSION ? "" : `<a class="changelog">${T.changelog.title}</a>`}
<a class="helpTranslate">${T.mainMenu.helpTranslate}</a>
${G_CHINA_VERSION ? "" : `<a class="helpTranslate">${T.mainMenu.helpTranslate}</a>`}
</div>
<div class="author">${T.mainMenu.madeBy.replace(
@ -226,10 +239,13 @@ export class MainMenuState extends GameState {
);
}
this.trackClicks(qs(".settingsButton"), this.onSettingsButtonClicked);
this.trackClicks(qs(".changelog"), this.onChangelogClicked);
this.trackClicks(qs(".redditLink"), this.onRedditClicked);
this.trackClicks(qs(".languageChoose"), this.onLanguageChooseClicked);
this.trackClicks(qs(".helpTranslate"), this.onTranslationHelpLinkClicked);
if (!G_CHINA_VERSION) {
this.trackClicks(qs(".languageChoose"), this.onLanguageChooseClicked);
this.trackClicks(qs(".redditLink"), this.onRedditClicked);
this.trackClicks(qs(".changelog"), this.onChangelogClicked);
this.trackClicks(qs(".helpTranslate"), this.onTranslationHelpLinkClicked);
}
if (G_IS_STANDALONE) {
this.trackClicks(qs(".exitAppButton"), this.onExitAppButtonClicked);
@ -251,11 +267,13 @@ export class MainMenuState extends GameState {
);
const githubLink = this.htmlElement.querySelector(".githubLink");
this.trackClicks(
githubLink,
() => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github),
{ preventClick: true }
);
if (githubLink) {
this.trackClicks(
githubLink,
() => this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github),
{ preventClick: true }
);
}
const producerLink = this.htmlElement.querySelector(".producerLink");
this.trackClicks(

View File

@ -9,17 +9,19 @@ export class MobileWarningState extends GameState {
getInnerHTML() {
return `
<img class="logo" src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<img class="logo" src="${cachebust(
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
)}" alt="shapez.io Logo">
<p>
I'm sorry, but shapez.io is not available on mobile devices yet!
There is also no estimate when this will change, but feel to make a contribution! It's
&nbsp;<a href="https://github.com/tobspr/shapez.io" target="_blank">open source</a>!</p>
<p>If you want to play on your computer, you can also get the standalone on Steam:</p>
<a href="${
THIRDPARTY_URLS.standaloneStorePage + "?ref=mobile"
}" class="standaloneLink" target="_blank">Get the shapez.io standalone!</a>

View File

@ -3,7 +3,6 @@ import { cachebust } from "../core/cachebust";
import { globalConfig } from "../core/config";
import { GameState } from "../core/game_state";
import { createLogger } from "../core/logging";
import { findNiceValue } from "../core/utils";
import { getRandomHint } from "../game/hints";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
@ -20,7 +19,7 @@ export class PreloadState extends GameState {
return `
<div class="loadingImage"></div>
<div class="loadingStatus">
<span class="desc">Booting</span>
<span class="desc">${G_CHINA_VERSION ? "加载中" : "Booting"}</span>
</div>
</div>
<span class="prefab_GameHint"></span>
@ -115,6 +114,10 @@ export class PreloadState extends GameState {
.then(() => this.setStatus("Initializing language"))
.then(() => {
if (G_CHINA_VERSION) {
return this.app.settings.updateLanguage("zh-CN");
}
if (this.app.settings.getLanguage() === "auto-detect") {
const language = autoDetectLanguageId();
logger.log("Setting language to", language);
@ -163,6 +166,10 @@ export class PreloadState extends GameState {
return;
}
if (G_CHINA_VERSION) {
return;
}
return this.app.storage
.readFileAsync("lastversion.bin")
.catch(err => {
@ -192,7 +199,9 @@ export class PreloadState extends GameState {
for (let i = 0; i < changelogEntries.length; ++i) {
const entry = changelogEntries[i];
dialogHtml += `
<div class="changelogDialogEntry">
<div class="changelogDialogEntry" data-changelog-skin="${
entry.skin || "default"
}">
<span class="version">${entry.version}</span>
<span class="date">${entry.date}</span>
<ul class="changes">
@ -220,6 +229,9 @@ export class PreloadState extends GameState {
}
update() {
if (G_CHINA_VERSION) {
return;
}
const now = performance.now();
if (now - this.lastHintShown > this.nextHintDuration) {
this.lastHintShown = now;
@ -250,6 +262,9 @@ export class PreloadState extends GameState {
*/
setStatus(text) {
logger.log("✅ " + text);
if (G_CHINA_VERSION) {
return Promise.resolve();
}
this.currentStatus = text;
this.statusText.innerText = text;
return Promise.resolve();
@ -265,7 +280,9 @@ export class PreloadState extends GameState {
subElement.innerHTML = `
<div class="logo">
<img src="${cachebust("res/logo.png")}" alt="Shapez.io Logo">
<img src="${cachebust(
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
)}" alt="Shapez.io Logo">
</div>
<div class="failureInner">
<div class="errorHeader">

View File

@ -63,6 +63,10 @@ export class SettingsState extends TextualGameState {
for (let i = 0; i < allApplicationSettings().length; ++i) {
const setting = allApplicationSettings()[i];
if (G_CHINA_VERSION && setting.id === "language") {
continue;
}
categoriesHTML[setting.categoryId] += setting.getHtml(this.app);
}
@ -90,13 +94,15 @@ export class SettingsState extends TextualGameState {
onEnter(payload) {
this.renderBuildText();
for (let i = 0; i < SettingsState.trackClicks.length; i++) {
const trackClick = SettingsState.trackClicks[i];
this.trackClicks(
this.htmlElement.querySelector(trackClick.htmlElement),
trackClick.action(this),
trackClick.options
);
if (!G_CHINA_VERSION) {
for (let i = 0; i < SettingsState.trackClicks.length; i++) {
const trackClick = SettingsState.trackClicks[i];
this.trackClicks(
this.htmlElement.querySelector(trackClick.htmlElement),
trackClick.action(this),
trackClick.options
);
}
}
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
@ -132,6 +138,10 @@ export class SettingsState extends TextualGameState {
initSettings() {
allApplicationSettings().forEach(setting => {
if (G_CHINA_VERSION && setting.id === "language") {
return;
}
/** @type {HTMLElement} */
const element = this.htmlElement.querySelector("[data-setting='" + setting.id + "']");
setting.bind(this.app, element, this.dialogs);

View File

@ -291,7 +291,7 @@ ingame:
interactiveTutorial:
title: Tutoriál
hints:
1_1_extractor: Umístěte <strong>extraktor</strong> na naleziště<strong>kruhového
1_1_extractor: Umístěte <strong>extraktor</strong> na naleziště <strong>kruhového
tvaru</strong> a vytěžte jej!
1_2_conveyor: "Připojte extraktor pomocí <strong>dopravníkového pásu</strong> k
vašemu HUBu!<br><br>Tip: <strong>Klikněte a táhněte</strong>
@ -404,7 +404,7 @@ buildings:
name: Extraktor
description: Umístěte na naleziště tvaru nebo barvy pro zahájení těžby.
chainable:
name: Extraktor (Navazující)
name: Extraktor (Řetěz)
description: Umístěte na naleziště tvaru nebo barvy pro zahájení těžby. Lze
zapojit po skupinách.
underground_belt:
@ -449,10 +449,10 @@ buildings:
name: Barvič
description: Obarví celý tvar v levém vstupu barvou z pravého vstupu.
double:
name: Barvič (dvojnásobný)
name: Barvič (2x)
description: Obarví tvary z levých vstupů barvou z horního vstupu.
quad:
name: Barvič (čtyřnásobný)
name: Barvič (4x)
description: Umožňuje obarvit každou čtvrtinu tvaru individuálně. Jen čtvrtiny
se vstupy barev s <strong>logickým signálem</strong> na vrstvě
kabelů budou obarveny!
@ -475,16 +475,16 @@ buildings:
name: Vyvažovač
description: Multifunkční - Rovnoměrně rozděluje vstupy na výstupech.
merger:
name: Spojovač (kompaktní)
name: Spojovač
description: Spojí dva pásy do jednoho.
merger-inverse:
name: Spojovač (kompaktní)
name: Spojovač
description: Spojí dva pásy do jednoho.
splitter:
name: Rozdělovač (kompaktní)
name: Rozdělovač
description: Rozdělí jeden pás na dva.
splitter-inverse:
name: Rozdělovač (kompaktní)
name: Rozdělovač
description: Rozdělí jeden pás na dva.
storage:
default:
@ -558,12 +558,12 @@ buildings:
name: Virtuální rotor
description: Virtuálně otáčí tvary o 90 stupňů po směru hodinových ručiček.
unstacker:
name: Virtuální extrahátor
name: Virt. extrahátor
description: Virtuálně extrahuje nejvyšší vrstvu do pravého výstupu a zbývající
do levé.
stacker:
name: Virtuální kombinátor
description: Virtuálně Spojí tvary dohromady. Pokud nemohou být spojeny, pravý
name: Virt. kombinátor
description: Virtuálně spojí tvary dohromady. Pokud nemohou být spojeny, pravý
tvar je položen na levý.
painter:
name: Virtuální barvič
@ -615,8 +615,8 @@ storyRewards:
proti směru hodinových ručiček. Vyberte rotor a <strong>zmáčkněte
'T' pro přepnutí mezi variantami</strong>!
reward_miner_chainable:
title: Napojovací extraktor
desc: "Právě jste odemkli <strong>napojovací extraktor</strong>! Může
title: Řetězový extraktor
desc: "Právě jste odemkli <strong>řetězový extraktor</strong>! Může
<strong>předat své zdroje</strong> ostatním extraktorům, čímž můžete
efektivněji těžit více zdrojů!<br><br> PS: Starý extraktor bude od
teď nahrazen ve vašem panelu nástrojů!"
@ -1008,7 +1008,7 @@ tips:
- Můžete proplétat různé úrovně tunelů.
- Snažte se postavit kompaktní továrny - vyplatí se to!
- Barvič má zrcadlově otočenou variantu, kterou můžete vybrat klávesou
<b>T</b>
<b>T</b>.
- Užití správné kombinace vylepšení maximalizuje efektivitu.
- Na maximální úrovní, 5 extraktorů zaplní jeden celý pás.
- Nezapomeňte na tunely!

View File

@ -30,49 +30,20 @@ steamPage:
intro: >-
Do you like automation games? Then you are in the right place!
shapez.io is a relaxed game in which you have to build factories for the automated production of geometric shapes. As the level increases, the shapes become more and more complex, and you
have to spread out on the infinite map.
shapez.io is a relaxed game in which you have to build factories for the automated production of geometric shapes. As the level increases, the shapes become more and more complex, and you have to spread out on the infinite map.
And as if that wasn't enough, you also have to produce exponentially more to satisfy the demands - the only thing that helps is scaling! While you only have to process shapes at the
beginning, you will later have to color them - by extracting and mixing colors!
And as if that wasn't enough, you also have to produce exponentially more to satisfy the demands - the only thing that helps is scaling! While you only have to process shapes at the beginning, you will later have to color them - by extracting and mixing colors!
Buying the game on Steam gives you access to the full version, but you can also play a demo at shapez.io first and decide later!
title_advantages: Standalone Advantages
advantages:
- <b>12 New Levels</b> for a total of 26 levels
- <b>18 New Buildings</b> for a fully automated factory!
- <b>Unlimited Upgrade Tiers</b> for many hours of fun!
- <b>Wires Update</b> for an entirely new dimension!
- <b>Dark Mode</b>!
- Unlimited Savegames
- Unlimited Markers
- Support me! ❤️
what_others_say: What people say about shapez.io
title_future: Planned Content
planned:
- Blueprint Library
- Steam Achievements
- Puzzle Mode
- Minimap
- Mods
- Sandbox mode
- ... and a lot more!
title_open_source: This game is open source!
text_open_source: >-
Anybody can contribute, I'm actively involved in the community and attempt to review all suggestions and take feedback into consideration where possible.
Be sure to check out my trello board for the full roadmap!
title_links: Links
links:
discord: Official Discord
roadmap: Roadmap
subreddit: Subreddit
source_code: Source code (GitHub)
translate: Help translate
nothernlion_comment: >-
This game is great - I'm having a wonderful time playing, and time has flown by.
notch_comment: >-
Oh crap. I really should sleep, but I think I just figured out how to make a computer in shapez.io
steam_review_comment: >-
This game has stolen my life and I don't want it back. Very chill factory game that won't let me stop making my lines more efficient.
global:
loading: Loading
@ -482,9 +453,9 @@ ingame:
title: 18 New Buildings
desc: Fully automate your factory!
savegames:
title: ∞ Savegames
desc: As many as your heart desires!
achievements:
title: Achievements
desc: Hunt them all!
upgrades:
title: ∞ Upgrade Tiers

View File

@ -168,13 +168,14 @@ dialogs:
desc: Suorita taso 12 avataksesi piirustukset!
keybindingsIntroduction:
title: Hyödyllisiä pikanäppäimiä
desc: "Tässä pelissä on paljon pikanäppäimiä, jotka tekevät isojen tehtaiden
desc: >-
Tässä pelissä on paljon pikanäppäimiä, jotka tekevät isojen tehtaiden
rakentamisesta helpompaa. Tässä on muutama, mutta <strong>katso
kaikki pikanäppäimet</strong>!<br><br> <code
class='keybinding'>CTRL</code> + Raahaus: Valitse alue.<br> <code
class='keybinding'>SHIFT</code>: Pidä pohjassa sijoittaaksesi
useita samoja rakennuksia.<br> <code class='keybinding'>ALT</code>:
Käännä sijoitettavien kuljettimien suunta.<br>"
Käännä sijoitettavien kuljettimien suunta.<br>
createMarker:
title: Uusi merkki
desc: Anna merkille kuvaava nimi. Voit myös liittää <strong>lyhyen koodin</strong>
@ -306,26 +307,31 @@ ingame:
hints:
1_1_extractor: Laita <strong>Poimija</strong> <strong>ympyrämuodon</strong>
päälle käyttääksesi sitä!
1_2_conveyor: "Yhdistä poimija <strong>kuljettimella</strong>
1_2_conveyor: >-
Yhdistä poimija <strong>kuljettimella</strong>
keskusrakennukseen!<br><br>Vihje: <strong>Paina ja
raahaa</strong> kuljetinta hiirellä!"
1_3_expand: "Tämä <strong>EI OLE</strong> tyhjäkäyntipeli! Rakenna lisää
raahaa</strong> kuljetinta hiirellä!
1_3_expand: >-
Tämä <strong>EI OLE</strong> tyhjäkäyntipeli! Rakenna lisää
poimijoita ja kuljettimia saavuttaaksesi tavoitteen nopeammin
valmiiksi.<br><br>Vihje: Pidä <strong>SHIFT</strong> pohjassa
laittaaksesi useampia poimijoita ja käytä <strong>R</strong>
kääntääksesi niitä."
2_1_place_cutter: "Nyt aseta <strong>Leikkuri</strong> leikataksesi ympyrä
kääntääksesi niitä.
2_1_place_cutter: >-
Nyt aseta <strong>Leikkuri</strong> leikataksesi ympyrä
puoliksi!<br><br> PS: Leikkuri aina leikkaa <strong>ylhäältä alaspäin</strong>
riippumatta sen asennosta."
riippumatta sen asennosta.
2_2_place_trash: Leikkuri voi <strong>tukkeutua</strong>!<br><br> Käytä
<strong>roskakoria</strong> hävittääksesi (vielä!) tarpeeton jäte.
2_3_more_cutters: "Hienoa! Lisää <strong>kaksi leikkuria</strong> nopeuttaaksesi
2_3_more_cutters: >-
Hienoa! Lisää <strong>kaksi leikkuria</strong> nopeuttaaksesi
hidasta prosessia!<br><br> PS: Käytä <strong>pikanäppäimiä 0-9</strong>
valitaksesi rakennuksen nopeammin!"
3_1_rectangles: "Poimitaanpa nyt neliöitä! <strong>Rakenna 4
valitaksesi rakennuksen nopeammin!
3_1_rectangles: >-
Poimitaanpa nyt neliöitä! <strong>Rakenna 4
poimijaa</strong> ja yhdistä ne keskusrakennukseen.<br><br> PS:
Pidä <strong>SHIFT</strong> painettuna, kun raahaat kuljetinta
aktivoidaksesi kuljetinsuunnittelijan!"
aktivoidaksesi kuljetinsuunnittelijan!
21_1_place_quad_painter: Aseta <strong>nelimaalain</strong> ja hanki
<strong>ympyröitä</strong>, <strong>valkoista</strong> ja
<strong>punaista</strong> väriä!
@ -333,9 +339,10 @@ ingame:
<strong>E</strong>!<br><br> Sitten <strong>yhdistä kaikki neljä tuloa</strong> maalaimeen johdoilla!
21_3_place_button: MahtaVATA! Aseta nyt <strong>kytkin</strong> ja yhdistä
se johdoilla!
21_4_press_button: "Paina kytkintä <strong>lähettääksesi tosi-
21_4_press_button: >-
Paina kytkintä <strong>lähettääksesi tosi-
signaalin</strong> ja aktivoidaksesi maalaimen.<br><br> PS: Kaikkia
tuloja ei tarvitse kytkeä! Kokeile vaikka vain kahta."
tuloja ei tarvitse kytkeä! Kokeile vaikka vain kahta.
connectedMiners:
one_miner: 1 poimija
n_miners: <amount> poimijaa
@ -587,10 +594,11 @@ storyRewards:
desc: Avasit <strong>Kääntäjän</strong>! Se kääntää muotoja myötäpäivään 90 astetta.
reward_painter:
title: Värjäys
desc: "Avasit <strong>Maalaimen</strong> - Poimi joitain värialueita
desc: >-
Avasit <strong>Maalaimen</strong> - Poimi joitain värialueita
(Samoin kuin muotoja) ja yhdistä se muotoon maalaimen
avulla!<br><br>PS: Jos olet värisokea, asetuksissa on <strong> tila
värisokeille</strong>!"
värisokeille</strong>!
reward_mixer:
title: Värin Sekoitus
desc: Avasit <strong>Värinsekoittajan</strong> - Yhdistä kaksi väriä
@ -617,10 +625,11 @@ storyRewards:
<strong>painamalla 'T' vaihtaaksesi sen versioita</strong>!
reward_miner_chainable:
title: Sarjapoimija
desc: "Avasit juuri <strong>Sarjapoimijan</strong>! Se voi
desc: >-
Avasit juuri <strong>Sarjapoimijan</strong>! Se voi
<strong>siirtää resurssejaan</strong> muihin poimijoihin, joten
voit hankkia resursseja tehokkaammin!<br><br> PS: Vanha
poimija on nyt korvattu työkalupalkissa!"
poimija on nyt korvattu työkalupalkissa!
reward_underground_belt_tier_2:
title: Tunneli Taso II
desc: Avasit uuden version <strong>Tunnelista</strong> - Siinä on <strong>pidempi
@ -657,9 +666,10 @@ storyRewards:
jotta sinulla on varaa siihen! (Ne mitkä juuri toimitit).
no_reward:
title: Seuraava taso
desc: "Et saanut palkintoa tältä tasolta, mutta seuraavalta tasolta saat! <br><br> PS: Parempi
desc: >-
Et saanut palkintoa tältä tasolta, mutta seuraavalta tasolta saat! <br><br> PS: Parempi
olla tuhoamatta vanhoja tehtaita - Tarvitset <strong>kaikkia</strong>
muotoja myöhemmin <strong>avataksesi päivityksiä</strong>!"
muotoja myöhemmin <strong>avataksesi päivityksiä</strong>!
no_reward_freeplay:
title: Seuraava taso
desc: Onnittelut! Muuten, lisää sisältöä on suunniteltu täysversioon!
@ -682,8 +692,9 @@ storyRewards:
kääntää muotoa 180 astetta (Ylläripylläri! :D)
reward_display:
title: Näyttö
desc: "Avasit juuri <strong>Näytön</strong> - Yhdistä signaali näyttöön
Johto-tasolla visualisoidaksesi sen<br><br> PS: Huomasitko, että kuljetinanturi ja varasto näyttävät viimeisimmän esineen? Yritäpä saada se näkyviin näytölle!"
desc: >-
Avasit juuri <strong>Näytön</strong> - Yhdistä signaali näyttöön
Johto-tasolla visualisoidaksesi sen<br><br> PS: Huomasitko, että kuljetinanturi ja varasto näyttävät viimeisimmän esineen? Yritäpä saada se näkyviin näytölle!
reward_constant_signal:
title: Jatkuva Signaali
desc: Avasit <strong>Jatkuvan Signaalin</strong> laitteen johtotasolla!
@ -708,12 +719,13 @@ storyRewards:
tavallisesti.<br><br> Mitä valitsetkin, muista pitää hauskaa!
reward_wires_painter_and_levers:
title: Johdot & Nelimaalain
desc: "Avasit juuri <strong>Johtotason</strong>: Se on erillinen
desc: >-
Avasit juuri <strong>Johtotason</strong>: Se on erillinen
taso tavallisen tason päällä ja sieltä löytyy useita uusia
mekaniikkoja!<br><br> Aluksi avasin sinulle <strong>Nelimaalaimen</strong>
- Yhdistä johtotasolla lokerot, joihin haluat maalia<br><br>
Vaihtaaksesi johtotasolle, paina <strong>E</strong>. <br><br>
PS: <strong>Aktivoi vinkit</strong> asetuksissa nähdäksesi Johdot-tutoriaalin!"
PS: <strong>Aktivoi vinkit</strong> asetuksissa nähdäksesi Johdot-tutoriaalin!
reward_filter:
title: Esinesuodatin
desc: Olet avannut <strong>Esinesuodattimen</strong>! Se lähettää esineet
@ -878,8 +890,9 @@ settings:
rangeSliderPercentage: <amount> %
keybindings:
title: Pikanäppäimet
hint: "Vinkki: Muista käyttää CTRL, SHIFT ja ALT! Ne ottavat käyttöön erilaisia
sijoitteluvaihtoehtoja."
hint: >-
Vinkki: Muista käyttää CTRL, SHIFT ja ALT! Ne ottavat käyttöön erilaisia
sijoitteluvaihtoehtoja.
resetKeybindings: Nollaa pikanäppäimet
categoryLabels:
general: Sovellus

View File

@ -495,7 +495,7 @@ buildings:
description: 入力された信号をディスプレイに表示します。 形状、色、真偽値のいずれでも可能です。
reader:
default:
name: ベルトリーダ
name: ベルトリーダ
description: 平均スループットを計測できます。 アンロック後は、 最後に通過したアイテムの情報を出力します。
analyzer:
default:
@ -577,8 +577,8 @@ storyRewards:
desc: <strong>分配機</strong>の<strong>コンパクトバージョン</strong>が利用可能になりました! -
1つの入力を2つの出力に分配します
reward_belt_reader:
title: ベルトリーダ
desc: <strong>ベルトリーダ</strong>が利用可能になりました!ベルトのスループットを計測できます。<br><br>ワイヤーのロックが解除されれば、より便利になります!
title: ベルトリーダ
desc: <strong>ベルトリーダ</strong>が利用可能になりました!ベルトのスループットを計測できます。<br><br>ワイヤーのロックが解除されれば、より便利になります!
reward_cutter_quad:
title: 四分割
desc: <strong>切断機</strong>のバリエーションが利用可能になりました。 -
@ -846,7 +846,7 @@ keybindings:
filter: アイテムフィルタ
wire_tunnel: 交差ワイヤ
display: ディスプレイ
reader: ベルトリーダ
reader: ベルトリーダ
virtual_processor: 仮想切断機
transistor: トランジスタ
analyzer: 形状解析機
@ -894,58 +894,57 @@ demo:
settingNotAvailable: デモ版では利用できません。
tips:
- ハブは現在指定されている形状だけではなく、あらゆる種類の入力を受け付けることができます。
- あなたの工場が拡張可能か確認してください - あとで報われるでしょう
- ハブのすぐ近くに建設しないでください。ぐちゃぐちゃになりますよ。
- あなたの工場が部品単位で増築可能か確認してください。あとできっと役に立ちます
- ハブのすぐ近くに建設しないでください。あとでぐちゃぐちゃになりますよ!
- 積層が上手く行かない場合は、入力を入れ替えてみてください。
- <b>R</b>を押すと、ベルトプランナーの経由方向を切り替えることができます。
- <b>CTRL</b>を押したままドラッグすると、向きを保ったままベルトを設置できます。
- アップグレードが同じティアなら、お互いの比率は同じです。
- アップグレード段階が同じなら、比率も同じに保たれます。
- 直列処理は、並列処理より効率的です。
- 後半になると、より多くの建物のバリエーションを解除できます。
- <b>T</b>を押すと、建物のバリエーションを切り替えることができます。
- 対称性が重要です!
- ティアの違うトンネル同士は、同じラインに重ねることができます。
- コンパクトに工場を作ってみてください - あとで報われるでしょう
- 別の種類のトンネル同士は、同じラインに重ねることができます。
- コンパクトに工場を作ってみてください。あとできっと役に立ちます
- 着色機には鏡写しのバリエーションがあり、<b>T</b>で選択できます。
- 適切な比率で建設することで、効率最大化できます。
- 適切な比率で建設することで、効率最大化できます。
- 最大レベルでは、1つのベルトは5つの抽出機で満たすことができます。
- トンネルを忘れないでください。
- 最大限の効率を得るためには、アイテムを均等に分割する必要はありません。
- <b>SHIFT</b>を押したままベルトを設置するとベルトプランナーが有効になり、
- 切断機は向きを考慮せず、常に垂直に切断します。
- 白を作るためには、3色全てを混ぜます。
- ストレージは優先出力を優先して出力します。
- 増築可能なデザインを作るために時間を使ってください - それには価値があります!
- <b>SHIFT</b>を使用すると複数の建物を配置できます。
- トンネルを忘れないでください!
- アイテムを均等に分割することは、最大効率を得るために必須ではありません。
- <b>SHIFT</b>を押したままにするとベルトプランナーが有効になり、長距離のベルトを簡単に配置できます。
- 切断機は配置された向きを考慮せず、常に垂直に切断します。
- ストレージは左側の出力を優先します。
- 増築可能なデザインを作るために時間を使ってください。それだけの価値があります!
- <b>SHIFT</b>を使用すると複数の建物を一度に配置できます。
- <b>ALT</b>を押しながらベルトを設置すると、逆向きに設置できます。
- 効率が重要です!
- ハブから遠くに離れるほど、形状資源はより複雑な形になります。
- 機械の速度には上限があるので、最大効率を得るためには入力を分割します
- 機械の速度には上限があるので、最大効率を得るためには入力を分割してください
- 効率を最大化するために分配機/合流機を使用できます。
- 構成が重要です。ベルトを交差させすぎないようにしてください。
- 事前設計が重要です。さもないとぐちゃぐちゃになりますよ!
- 旧い工場を撤去しないでください!アップグレードを行うために、それらが必要になります。
- 助けなしでレベル20をクリアしてみてください!
- 複雑にしないでください。単純に保つことができれば、成功することができるでしょう
- ゲームの後半で工場を再利用する必要があるかもしれません。
- 積層機を使用することなく、必要な形状資源を発見することができるかもしれません。
- 完全な風車の形は資源としては生成されません。
- 古い工場を撤去しないでください! 各種アップグレードに必要になります。
- 自力でレベル20やレベル26をクリアしてみてください!
- 複雑にしないでください。単純に保つことが成功の秘訣です
- あとで工場を再利用する必要が出てくるかもしれません。
- 積層機を使用することなく、必要な形状資源を発見できるかもしれません。
- 完全な風車の形は資源としては生成されません。
- 最大の効率を得るためには、切断する前に着色をしてください。
- モジュールとは、知覚こそが空間を生むものである。これは、人間である限り
- 工場の設計図を蓄えておいてください。それらを再利用することで、新たな工場が作成できます。
- モジュールがあれば、空間はただの認識に過ぎなくなる――生ある人間に対する気遣いだ
- 設計図としての工場を別に作っておくと、工場のモジュール化において重要な役割を果たします。
- 混合機をよく見ると、色の混ぜ方が解ります。
- <b>CTRL</b> + クリックで範囲選択ができます。
- ハブに近すぎる設計物を作ると、のちの設計の邪魔になる可能性があります
- アップグレードリストの各形状の横にあるピンのアイコンは、それを画面左に固定します。
- 原色全てを混ぜ合わせると白になります!
- ハブに近すぎる設計物を作ると、のちの設計の邪魔になるかもしれません
- アップグレードリストの各形状の横にあるピンのアイコンは、その形状を画面左に固定表示します。
- 原色全てを混ぜ合わせると白になります!
- マップは無限の広さがあります。臆せずに拡張してください。
- Factorioもプレイしてみてください私のお気に入りのゲームです。
- 切断機(四分割)は右上から時計回りに切断します
- Factorioもプレイしてみてください 私のお気に入りのゲームです。
- 切断機(四分割)は右上から時計回りに切断します
- メインメニューからセーブデータを保存できます!
- このゲームには便利なキーバインドがたくさんあります!設定ページを見てみてください。
- このゲームにはたくさんの設定があります是非チェックしてみてください!
- ハブを示すマーカーには、その方向を示す小さなコンパスがあります。
- ベルトをクリアするには、範囲選択して同じ場所に貼り付けをします。
- このゲームには便利なキーバインドがたくさんあります! 設定ページを見てみてください。
- このゲームにはたくさんの設定があります是非チェックしてみてください!
- ハブのマーカーには、その方向を示す小さなコンパスがついています。
- ベルトの中身をクリアするには、範囲選択して同じ場所に貼り付けをします。
- F4を押すことで、FPSとTickレートを表示することができます。
- F4を2回押すと、マウスとカメラの座標を表示することができます。
- 左のピン留めされた図形をクリックして、固定を解除できます。
- 左のピン留めされた図形をクリックすると、固定を解除できます。

View File

@ -186,7 +186,7 @@ dialogs:
wersję gry dla nielimitowanych znaczników!
massCutConfirm:
title: Potwierdź wycinanie
desc: Wycinasz sporą ilość maszyn (<count> gwoli ścisłości)! Czy na pewno chcesz
desc: Wycinasz sporą ilość maszyn (<count> w roli ścisłości)! Czy na pewno chcesz
kontynuować?
exportScreenshotWarning:
title: Tworzenie zrzutu fabryki

View File

@ -215,21 +215,21 @@ ingame:
selectBuildings: Alan seç
stopPlacement: Yerleştİrmeyİ durdur
rotateBuilding: Yapıyı döndür
placeMultiple: Çoklu yerleştİr
reverseOrientation: Yönünü ters çevİr
placeMultiple: Çoklu yerleştir
reverseOrientation: Yönünü ters çevir
disableAutoOrientation: Otomatik yönü devre dışı bırak
toggleHud: Kullanıcı arayüzünü aç/kapa
placeBuilding: Yapı yerleştİr
createMarker: Yer İmi oluştur
placeBuilding: Yapı yerleştir
createMarker: Yer imi oluştur
delete: SİL
pasteLastBlueprint: Son taslağı yapıştır
lockBeltDirection: Taşıma bandı planlayıcısını kullan
plannerSwitchSide: Planlayıcıyı ters çevir
cutSelection: Kes
copySelection: Kopyala
clearSelection: Seçİmİ temİzle
pipette: Pİpet
switchLayers: Katman değİştİr
clearSelection: Seçimi temİzle
pipette: Pipet
switchLayers: Katman değiştir
buildingPlacement:
cycleBuildingVariants: Yapının farklı türlerine geçmek için <key> tuşuna bas.
hotkeyLabel: "Kısayol: <key>"
@ -245,14 +245,14 @@ ingame:
levelTitle: SEVİYE <level>
completed: Tamamlandı
unlockText: ıldı <reward>!
buttonNextLevel: Sonrakİ Sevİye
buttonNextLevel: Sonraki Seviye
notifications:
newUpgrade: Yeni geliştirme mevcut!
gameSaved: Oyun kaydedildi.
freeplayLevelComplete: Seviye <level> tamamlandı!
shop:
title: Gelİştİrmeler
buttonUnlock: Gelİştİr
title: Geliştirmeler
buttonUnlock: Geliştİr
tier: Aşama <x>
maximumLevel: SON SEVİYE (Hız x<currentMult>)
statistics:
@ -283,19 +283,19 @@ ingame:
blueprintPlacer:
cost: Bedel
waypoints:
waypoints: Yer İmi
waypoints: Yer imler
hub: MERKEZ
description: Sol-tık ile Yer İmlerine git, sağ-tık ile yer imini
description: Sol-tık ile Yer imlerine git, sağ-tık ile yer imini
sil.<br><br>Mevcut konumdan yer imi oluşturmak için <keybinding>'a
bas, <strong>sağ-tık</strong> ile mevcut konumda yer imi oluştur.
creationSuccessNotification: Yer İmi oluşturuldu.
creationSuccessNotification: Yer imi oluşturuldu.
interactiveTutorial:
title: İtİm
title: itim
hints:
1_1_extractor: Daire üretmek için <strong>daire şekli</strong> üzerine bir
<strong>üretici</strong> yerleştir!
1_2_conveyor: "Üreticiyi <strong>taşıma bandı</strong> ile merkezine
bağla!<br><br>İpucu: Taşıma bandı nı seç ve taşıma bandını
bağla!<br><br>İpucu: Taşıma bandını seç ve taşıma bandını
farenin sol tuşu ile <strong>tıkla ve sürükle</strong>!"
1_3_expand: "Bu bir boşta kalma oyunu (idle game) <strong>değil</strong>! Hedefe
ulaşmak için daha fazla üretici ve taşıma bandı
@ -394,7 +394,7 @@ shopUpgrades:
description: Hız x<currentMult> → x<newMult>
buildings:
hub:
deliver: Teslİm et
deliver: Teslim et
toUnlock: ılacak
levelShortcut: SVY
endOfDemo: Deneme Sürümünün Sonu
@ -405,10 +405,10 @@ buildings:
sürükle.
miner:
default:
name: Üretİcİ
name: Üretici
description: Bir şekli veya rengi üretmek için üzerlerini yerleştir.
chainable:
name: Üretİcİ (Zİncİrleme)
name: Üretici (Zİncİrleme)
description: Bir şekli veya rengi üretmek için üzerlerini yerleştir. Zincirleme
bağlanabilir.
underground_belt:
@ -421,12 +421,12 @@ buildings:
aktarımı sağlar.
cutter:
default:
name: Kesİcİ
name: Kesici
description: Şekilleri yukarıdan aşağıya böler ve iki yarım parçayı çıktı olarak
verir. <strong>Eğer sadece bir çıktıyı kullanıyorsanız diğer
çıkan parçayı yok etmeyi unutmayın, yoksa kesim durur!</strong>
quad:
name: Kesİcİ (Dörtlü)
name: Kesici (Dörtlü)
description: Şekilleri dört parçaya böler. <strong>Eğer sadece bir çıktıyı
kullanıyorsanız diğer çıkan parçaları yok etmeyi unutmayın,
yoksa kesim durur!</strong>
@ -435,10 +435,10 @@ buildings:
name: Döndürücü
description: Şekilleri saat yönünde 90 derece döndürür.
ccw:
name: Döndürücü (Saat Yönünün Tersİ)
name: Döndürücü (Saat Yönünün Tersi)
description: Şekilleri saat yönünün tersinde 90 derece döndürür.
rotate180:
name: dürücü (180 Derece)
name: ndürücü (180 Derece)
description: Şekilleri 180 derece döndürür.
stacker:
default:
@ -486,10 +486,10 @@ buildings:
name: Dengeleyici
description: Çok işlevli - bütün girdileri eşit olarak bütün çıkışlara dağıtır.
merger:
name: Bİrleştİrİcİ (tekİl)
name: Birleştirici (tekil)
description: İki taşıma bandını bir çıktı verecek şekilde birleştirir.
merger-inverse:
name: Birleştİrİcİ (tekİl)
name: Birleştirici (tekil)
description: İki taşıma bandını bir çıktı verecek şekilde birleştirir.
splitter:
name: Ayırıcı (tekİl)
@ -499,7 +499,7 @@ buildings:
description: Bir taşıma bandını iki çıktı verecek şekilde ayırır.
storage:
default:
name: Storage
name: Depo
description: Belirli bir sınıra kadar fazla eşyaları depolar. Taşırma kapısı
olarak kullanıla bilir.
constant_signal:
@ -589,7 +589,7 @@ buildings:
katmanda çıktı olarak verir.
storyRewards:
reward_cutter_and_trash:
title: Şekİllerİ Kesmek
title: Şekilleri Kesmek
desc: <strong>Kesici</strong> açıldı, bu alet şekilleri <strong>yönelimi ne
olursa olsun</strong> ortadan ikiye böler!<br><br> Çıkan şekilleri
kullanmayı veya çöpe atmayı unutma yoksa <strong>makine
@ -645,7 +645,7 @@ storyRewards:
desc: <strong>Kesicinin</strong> yeni bir türünü açtın - Bu tür şekilleri iki
parça yerine <strong>dört parçaya</strong> ayırabilir!
reward_painter_double:
title: Çİfte Boyama
title: Çifte Boyama
desc: <strong>Boyayıcının<strong> başka bir türünü açtın - Sıradan bir boyayıcı
gibi çalışır, fakat <strong>iki şekli birden</strong> boyayarak iki
boya yerine sadece bir boya harcar!
@ -665,13 +665,13 @@ storyRewards:
yapıştırabilmek için <strong>taslak şekilleri</strong> üretmelisin!
(Az önce teslim ettiğin şekiller).
no_reward:
title: Sonrakİ Sevİye
title: Sonraki Seviye
desc: "Bu seviyenin bir ödülü yok ama bir sonrakinin olacak!<br><br> Not: Şu
anki fabrikalarını yok etmemeni öneririm - Daha sonra
<strong>Geliştirmeleri açmak için </strong> <strong>bütün
hepsine</strong> ihtiyacın olacak!"
no_reward_freeplay:
title: Sonrakİ Sevİye
title: Sonraki Seviye
desc: Tebrikler!
reward_freeplay:
title: Özgür Mod
@ -701,7 +701,7 @@ storyRewards:
hızını ölçmeyi sağlar.<br><br>Kabloları açana kadar bekle - o zaman
çok kullanışlı olacak.
reward_rotater_180:
title: dürücü (180 derece)
title: ndürücü (180 derece)
desc: 180 derece <strong>döndürücüyü</strong> açtınız! - Şekilleri 180 derece
döndürür (Süpriz! :D)
reward_display:
@ -754,7 +754,7 @@ settings:
categories:
general: Genel
userInterface: Kullanıcı Arayüzü
advanced: Gelİşmİş
advanced: Gelişmİş
performance: Performans
versionBadges:
dev: Geliştirme
@ -764,7 +764,7 @@ settings:
rangeSliderPercentage: <amount> %
labels:
uiScale:
title: Arayüz Ölçeğİ
title: Arayüz Ölçeği
description: Kullanıcı arayüzünün boyutunu değiştirir. Arayüz cihazınızın
çözünürlüğüne göre ölçeklendirilir, ama ölçeğin miktarı burada
ayarlanabilir.
@ -775,7 +775,7 @@ settings:
large: Büyük
huge: Çok Büyük
scrollWheelSensitivity:
title: Yakınlaştırma Hassasİyeti
title: Yakınlaştırma Hassasiyeti
description: Yakınlaştırmanın ne kadar hassas olduğunu ayarlar (Fare tekerleği
veya dokunmatik farketmez).
sensitivity:
@ -785,7 +785,7 @@ settings:
fast: Hızlı
super_fast: Çok Hızlı
language:
title: Dİl
title: Dil
description: Dili değiştirir. Bütün çevirmeler kullanıcı katkılarıyla
oluşturulmuştur ve tam olmayabilir!
fullscreen:
@ -793,10 +793,10 @@ settings:
description: En iyi oyun tecrübesi için oyunun tam ekranda oynanması tavsiye
edilir. Sadece tam sürümde mevcut.
soundsMuted:
title: Ses Efektlerİnİ Sustur
title: Ses Efektlerini Sustur
description: Aktif edildiğinde bütün ses efektleri susturulur.
musicMuted:
title: Müzİğİ Sustur
title: Müziği Sustur
description: Aktif edildiğinde bütün müzikler susturulur.
theme:
title: Renk Teması
@ -805,7 +805,7 @@ settings:
dark: Karanlık
light: Aydınlık
refreshRate:
title: Sİmülasyon Hızı
title: Simülasyon Hızı
description: Eğer mönitörünüzün yenileme hızı (Hz) yüksekse oyunun akıcı bir
şekilde çalışması için yenileme hızını buradan değiştirin. Eğer
bilgisayarınız yavaşsa bu ayar oyunun gösterdiği kare hızını
@ -817,7 +817,7 @@ settings:
özellik SHIFT tuşuna sürekli basılı tutup yapı yerleştirmeyle
aynıdır.
offerHints:
title: İpuçları ve Eğİtİmler
title: İpuçları ve Eğitimler
description: İpuçları ve eğitimleri açar. Ayrıca bazı arayüz elemanlarını oyunun
daha kolay öğrenilebilmesi için gizler.
enableTunnelSmartplace:
@ -827,11 +827,11 @@ settings:
tünellerin çekilerek inşa edilmesi ve aşırı uzağa yerleştirilen
tünel uçlarının silinmesini de sağlar.
vignette:
title: Gölgelendİrme
title: Gölgelendirme
description: Gölgelendirmeyi açar. Gölgelendirme ekranın köşelerini karartır ve
yazıları daha kolay okuyabilmeinizi sağlar.
autosaveInterval:
title: Otomatİk Kayıt Sıklığı
title: Otomatik Kayıt Sıklığı
description: Oyunun hangi sıklıkta kaydedileceğini belirler. Ayrıca otomatik
kayıt tamamen kapatılabilir.
intervals:
@ -842,11 +842,11 @@ settings:
twenty_minutes: 20 Dakika
disabled: Devredışı
compactBuildingInfo:
title: Derlİ Toplu Yapı Bİlgİlerİ
title: Derli Toplu Yapı Bilgileri
description: Yapıların bilgi kutularını sadece oranlarını göstecek şekilde
kısaltır. Aksi taktirde yapının açıklaması ve resmi gösterilir.
disableCutDeleteWarnings:
title: Kes/Sİl Uyarılarını Devredışı Bırak
title: Kes/Sil Uyarılarını Devre dışı Bırak
description: 100 birimden fazla parçayı kesme/silme işlemlerinde beliren uyarı
pencerelerini devredışı bırakır.
enableColorBlindHelper:
@ -872,36 +872,36 @@ settings:
title: Ses Ayarı
description: Ses efektlerinin seviyesini ayarlar
musicVolume:
title: Müzİk Ayarı
title: Müzik Ayarı
description: Müzik seviyesini ayarlar
lowQualityMapResources:
title: Düşük Kalİte Harİta Kaynakları
title: Düşük Kalite Harita Kaynakları
description: Oyun performansını artırmak için haritada görünen kaynakların çizim
kalitesinin sadeleştirir. Kaynaklar daha açık görüneceğinde bu
özelliği bir dene!
disableTileGrid:
title: Harİta Çİzgİlerİnİ Gizle
title: Harita Çizgilerini Gizle
description: Harita çizgilerini gizlemek oyun performansına yardımcı olabilir.
Aynı zamanda oyunun daha açık görünmesini sağlar!
clearCursorOnDeleteWhilePlacing:
title: Sağ Tık İnşa İptalİ
title: Sağ Tık İnşa İptali
description: Varsayılan olarak açık. Özellik açıksa, inşa modundayken sağ yık
yapıldığında inşa modundan çıkar. Eğer özellik kapalıysa, inşa
modundan çıkmadan var olan yapıları sağ tık ile silebilirsiniz.
lowQualityTextures:
title: Düşük Kalİte Görüntü (Çirkin)
title: Düşük Kalite Görüntü (Çirkin)
description: Performans için düşük kalite görüntü kullanır. Bu oyunun daha
çirkin görünmesine sebep olur!
displayChunkBorders:
title: Harİta Alan Sınırlarını Göster
title: Harita Alan Sınırlarını Göster
description: Oyun 16'ya 16 alanlardan oluşur. Bu seçenek aktif olduğunda alan
sınırları görüntülenir.
pickMinerOnPatch:
title: Kaynak Üzerinde Üretİcİ Seç
title: Kaynak Üzerinde Üretici Seç
description: Varsayılan olarak açık. Eğer pipet bir kaynağın üzerinde
kullanılırsa, üreteç yapısı inşa için seçilir.
simplifiedBelts:
title: Sadeleştİrİlmİş Bantlar (Çirkin)
title: Sadeleştirilmiş Bantlar (Çirkin)
description: Taşıma bandı üzerindeki eşyalar fare imleci üzerinde değilse
görüntülenmez. Eğer gerçekten performansa ihtiyacınız yoksa bu
ayarla oynamanız tavsiye edilmez.
@ -929,7 +929,7 @@ keybindings:
placement: Yerleştİrme
massSelect: Çoklu Seçim
buildings: Yapı Kısayolları
placementModifiers: Yerleştİrme Özellİklerİ
placementModifiers: Yerleştirme Özellikleri
mappings:
confirm: Kabul
back: Geri
@ -947,8 +947,8 @@ keybindings:
toggleFPSInfo: FPS ve Debug Bilgisini Aç/Kapat
belt: Taşıma Bandı
underground_belt: Tünel
miner: Üretİcİ
cutter: Kesİcİ
miner: Üretici
cutter: Kesici
rotater: Döndürücü
stacker: Kaynaştırıcı
mixer: Renk Karıştırıcısı

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
1.2.2
1.3.0

16345
yarn.lock

File diff suppressed because it is too large Load Diff