1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

Merge branch 'master' into master

This commit is contained in:
Roland Szabó 2020-08-07 00:06:38 +02:00 committed by GitHub
commit 9a2044dbd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 545 additions and 1152 deletions

View File

@ -14,13 +14,9 @@ import { AdProviderInterface } from "./platform/ad_provider";
import { NoAdProvider } from "./platform/ad_providers/no_ad_provider";
import { AnalyticsInterface } from "./platform/analytics";
import { GoogleAnalyticsImpl } from "./platform/browser/google_analytics";
import { NoGameAnalytics } from "./platform/browser/no_game_analytics";
import { SoundImplBrowser } from "./platform/browser/sound";
import { PlatformWrapperImplBrowser } from "./platform/browser/wrapper";
import { PlatformWrapperImplElectron } from "./platform/electron/wrapper";
import { GameAnalyticsInterface } from "./platform/game_analytics";
import { SoundInterface } from "./platform/sound";
import { StorageInterface } from "./platform/storage";
import { PlatformWrapperInterface } from "./platform/wrapper";
import { ApplicationSettings } from "./profile/application_settings";
import { SavegameManager } from "./savegame/savegame_manager";
@ -34,6 +30,12 @@ import { PreloadState } from "./states/preload";
import { SettingsState } from "./states/settings";
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
/**
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
* @typedef {import("./platform/sound").SoundInterface} SoundInterface
* @typedef {import("./platform/storage").StorageInterface} StorageInterface
*/
const logger = createLogger("application");
// Set the name of the hidden property and the change event for visibility

View File

@ -1,20 +1,38 @@
/**
* @typedef {{ w: number, h: number }} Size
* @typedef {{ x: number, y: number }} Position
* @typedef {{
* frame: { x: number, y: number, w: number, h: number },
* rotated: false,
* spriteSourceSize: { x: number, y: number, w: number, h: number },
* sourceSize: { w: number, h: number},
* trimmed: true
* frame: Position & Size,
* rotated: boolean,
* spriteSourceSize: Position & Size,
* sourceSize: Size,
* trimmed: boolean
* }} SpriteDefinition
*
* @typedef {{
* app: string,
* version: string,
* image: string,
* format: string,
* size: Size,
* scale: string,
* smartupdate: string
* }} AtlasMeta
*
* @typedef {{
* frames: Object.<string, SpriteDefinition>,
* meta: AtlasMeta
* }} SourceData
*/
export class AtlasDefinition {
constructor(sourceData) {
this.sourceFileName = sourceData.meta.image;
this.meta = sourceData.meta;
/** @type {Object.<string, SpriteDefinition>} */
this.sourceData = sourceData.frames;
/**
* @param {SourceData} sourceData
*/
constructor({ frames, meta }) {
this.meta = meta;
this.sourceData = frames;
this.sourceFileName = meta.image;
}
getFullSourcePath() {
@ -22,6 +40,7 @@ export class AtlasDefinition {
}
}
/** @type {AtlasDefinition[]} **/
export const atlasFiles = require
// @ts-ignore
.context("../../../res_built/atlas/", false, /.*\.json/i)

View File

@ -115,7 +115,6 @@ export class BackgroundResourcesLoader {
})
.then(() => {
logger.log("⏰ Finish load: bare game");
Loader.createAtlasLinks();
this.bareGameReady = true;
initBuildingCodesAfterResourcesLoaded();
this.signalBareGameLoaded.dispatch();

View File

@ -1,9 +1,9 @@
import { Rectangle } from "./rectangle";
import { globalConfig } from "./config";
/* typehints:start */
import { GameRoot } from "../game/root";
/* typehints:end */
/**
* @typedef {import("../game/root").GameRoot} GameRoot
* @typedef {import("./rectangle").Rectangle} Rectangle
*/
export class DrawParameters {
constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) {

View File

@ -1,18 +1,13 @@
/* typehints:start */
import { AtlasSprite } from "./sprites";
import { DrawParameters } from "./draw_parameters";
/* typehints:end */
import { Vector } from "./vector";
import { Rectangle } from "./rectangle";
import { createLogger } from "./logging";
const logger = createLogger("draw_utils");
/**
* @typedef {import("./sprites").AtlasSprite} AtlasSprite
* @typedef {import("./draw_parameters").DrawParameters} DrawParameters
*/
export function initDrawUtils() {
CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) {
this.beginPath();
if (r < 0.05) {
this.beginPath();
this.rect(x, y, w, h);
return;
}
@ -20,25 +15,26 @@ export function initDrawUtils() {
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
// this.closePath();
};
CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) {
this.beginPath();
if (r < 0.05) {
this.beginPath();
this.rect(x, y, 1, 1);
return;
}
this.beginPath();
this.arc(x, y, r, 0, 2.0 * Math.PI);
};
}
@ -62,259 +58,3 @@ export function drawRotatedSprite({ parameters, sprite, x, y, angle, size, offse
parameters.context.rotate(-angle);
parameters.context.translate(-x, -y);
}
export function drawLineFast(context, { x1, x2, y1, y2, color = null, lineSize = 1 }) {
const dX = x2 - x1;
const dY = y2 - y1;
const angle = Math.atan2(dY, dX) + 0.0 * Math.PI;
const len = Math.hypot(dX, dY);
context.translate(x1, y1);
context.rotate(angle);
if (color) {
context.fillStyle = color;
}
context.fillRect(0, -lineSize / 2, len, lineSize);
context.rotate(-angle);
context.translate(-x1, -y1);
}
const INSIDE = 0;
const LEFT = 1;
const RIGHT = 2;
const BOTTOM = 4;
const TOP = 8;
// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
function computeOutCode(x, y, xmin, xmax, ymin, ymax) {
let code = INSIDE;
if (x < xmin)
// to the left of clip window
code |= LEFT;
else if (x > xmax)
// to the right of clip window
code |= RIGHT;
if (y < ymin)
// below the clip window
code |= BOTTOM;
else if (y > ymax)
// above the clip window
code |= TOP;
return code;
}
// CohenSutherland clipping algorithm clips a line from
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
// diagonal from (xmin, ymin) to (xmax, ymax).
/**
*
* @param {CanvasRenderingContext2D} context
*/
export function drawLineFastClipped(context, rect, { x0, y0, x1, y1, color = null, lineSize = 1 }) {
const xmin = rect.x;
const ymin = rect.y;
const xmax = rect.right();
const ymax = rect.bottom();
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
let outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
let outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
let accept = false;
// eslint-disable-next-line no-constant-condition
while (true) {
if (!(outcode0 | outcode1)) {
// bitwise OR is 0: both points inside window; trivially accept and exit loop
accept = true;
break;
} else if (outcode0 & outcode1) {
// bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
// or BOTTOM), so both must be outside window; exit loop (accept is false)
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
let x, y;
// At least one endpoint is outside the clip rectangle; pick it.
let outcodeOut = outcode0 ? outcode0 : outcode1;
// Now find the intersection point;
// use formulas:
// slope = (y1 - y0) / (x1 - x0)
// x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
// y = y0 + slope * (xm - x0), where xm is xmin or xmax
// No need to worry about divide-by-zero because, in each case, the
// outcode bit being tested guarantees the denominator is non-zero
if (outcodeOut & TOP) {
// point is above the clip window
x = x0 + ((x1 - x0) * (ymax - y0)) / (y1 - y0);
y = ymax;
} else if (outcodeOut & BOTTOM) {
// point is below the clip window
x = x0 + ((x1 - x0) * (ymin - y0)) / (y1 - y0);
y = ymin;
} else if (outcodeOut & RIGHT) {
// point is to the right of clip window
y = y0 + ((y1 - y0) * (xmax - x0)) / (x1 - x0);
x = xmax;
} else if (outcodeOut & LEFT) {
// point is to the left of clip window
y = y0 + ((y1 - y0) * (xmin - x0)) / (x1 - x0);
x = xmin;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut == outcode0) {
x0 = x;
y0 = y;
outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
} else {
x1 = x;
y1 = y;
outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
}
}
}
if (accept) {
// Following functions are left for implementation by user based on
// their platform (OpenGL/graphics.h etc.)
// DrawRectangle(xmin, ymin, xmax, ymax);
// LineSegment(x0, y0, x1, y1);
drawLineFast(context, {
x1: x0,
y1: y0,
x2: x1,
y2: y1,
color,
lineSize,
});
}
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {Array} The RGB representation
*/
export function hslToRgb(h, s, l) {
let r;
let g;
let b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
// tslint:disable-next-line:no-shadowed-variable
const hue2rgb = function (p, q, t) {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
let p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
export function wrapText(context, text, x, y, maxWidth, lineHeight, stroke = false) {
var words = text.split(" ");
var line = "";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + " ";
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
if (stroke) {
context.strokeText(line, x, y);
} else {
context.fillText(line, x, y);
}
line = words[n] + " ";
y += lineHeight;
} else {
line = testLine;
}
}
if (stroke) {
context.strokeText(line, x, y);
} else {
context.fillText(line, x, y);
}
}
/**
* Returns a rotated trapez, used for spotlight culling
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {number} leftHeight
* @param {number} angle
*/
export function rotateTrapezRightFaced(x, y, w, h, leftHeight, angle) {
const halfY = y + h / 2;
const points = [
new Vector(x, halfY - leftHeight / 2),
new Vector(x + w, y),
new Vector(x, halfY + leftHeight / 2),
new Vector(x + w, y + h),
];
return Rectangle.getAroundPointsRotated(points, angle);
}
/**
* Converts values from 0 .. 255 to values like 07, 7f, 5d etc
* @param {number} value
* @returns {string}
*/
export function mapClampedColorValueToHex(value) {
const hex = "0123456789abcdef";
return hex[Math.floor(value / 16)] + hex[value % 16];
}
/**
* Converts rgb to a hex string
* @param {number} r
* @param {number} g
* @param {number} b
* @returns {string}
*/
export function rgbToHex(r, g, b) {
return mapClampedColorValueToHex(r) + mapClampedColorValueToHex(g) + mapClampedColorValueToHex(b);
}

View File

@ -1,19 +1,19 @@
import { SingletonFactory } from "./singleton_factory";
import { Factory } from "./factory";
/* typehints:start */
import { BaseGameSpeed } from "../game/time/base_game_speed";
import { Component } from "../game/component";
import { BaseItem } from "../game/base_item";
import { MetaBuilding } from "../game/meta_building";
/* typehints:end */
/**
* @typedef {import("../game/time/base_game_speed").BaseGameSpeed} BaseGameSpeed
* @typedef {import("../game/component").Component} Component
* @typedef {import("../game/base_item").BaseItem} BaseItem
* @typedef {import("../game/meta_building").MetaBuilding} MetaBuilding
// These factories are here to remove circular dependencies
/** @type {SingletonFactoryTemplate<MetaBuilding>} */
export let gMetaBuildingRegistry = new SingletonFactory();
/** @type {Object.<string, Array<typeof MetaBuilding>>} */
/** @type {Object.<string, Array<Class<MetaBuilding>>>} */
export let gBuildingsByCategory = null;
/** @type {FactoryTemplate<Component>} */
@ -28,7 +28,7 @@ export let gItemRegistry = new Factory("item");
// Helpers
/**
* @param {Object.<string, Array<typeof MetaBuilding>>} buildings
* @param {Object.<string, Array<Class<MetaBuilding>>>} buildings
*/
export function initBuildingsByCategory(buildings) {
gBuildingsByCategory = buildings;

View File

@ -1,20 +1,19 @@
/* typehints:start */
import { Application } from "../application";
/* typehints:end */
import { AtlasDefinition } from "./atlas_definitions";
import { makeOffscreenBuffer } from "./buffer_utils";
import { AtlasSprite, BaseSprite, RegularSprite, SpriteAtlasLink } from "./sprites";
import { cachebust } from "./cachebust";
import { createLogger } from "./logging";
/**
* @typedef {import("../application").Application} Application
* @typedef {import("./atlas_definitions").AtlasDefinition} AtlasDefinition;
*/
const logger = createLogger("loader");
const missingSpriteIds = {};
class LoaderImpl {
constructor() {
/** @type {Application} */
this.app = null;
/** @type {Map<string, BaseSprite>} */
@ -23,6 +22,9 @@ class LoaderImpl {
this.rawImages = [];
}
/**
* @param {Application} app
*/
linkAppAfterBoot(app) {
this.app = app;
this.makeSpriteNotFoundCanvas();
@ -58,7 +60,7 @@ class LoaderImpl {
}
/**
* Retursn a regular sprite from the cache
* Returns a regular sprite from the cache
* @param {string} key
* @returns {RegularSprite}
*/
@ -155,44 +157,34 @@ class LoaderImpl {
* @param {AtlasDefinition} atlas
* @param {HTMLImageElement} loadedImage
*/
internalParseAtlas(atlas, loadedImage) {
internalParseAtlas({ meta: { scale }, sourceData }, loadedImage) {
this.rawImages.push(loadedImage);
for (const spriteKey in atlas.sourceData) {
const spriteData = atlas.sourceData[spriteKey];
for (const spriteName in sourceData) {
const { frame, sourceSize, spriteSourceSize } = sourceData[spriteName];
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteKey));
let sprite = /** @type {AtlasSprite} */ (this.sprites.get(spriteName));
if (!sprite) {
sprite = new AtlasSprite({
spriteName: spriteKey,
});
this.sprites.set(spriteKey, sprite);
sprite = new AtlasSprite(spriteName);
this.sprites.set(spriteName, sprite);
}
const link = new SpriteAtlasLink({
packedX: spriteData.frame.x,
packedY: spriteData.frame.y,
packedW: spriteData.frame.w,
packedH: spriteData.frame.h,
packOffsetX: spriteData.spriteSourceSize.x,
packOffsetY: spriteData.spriteSourceSize.y,
packedX: frame.x,
packedY: frame.y,
packedW: frame.w,
packedH: frame.h,
packOffsetX: spriteSourceSize.x,
packOffsetY: spriteSourceSize.y,
atlas: loadedImage,
w: spriteData.sourceSize.w,
h: spriteData.sourceSize.h,
w: sourceSize.w,
h: sourceSize.h,
});
sprite.linksByResolution[atlas.meta.scale] = link;
sprite.linksByResolution[scale] = link;
}
}
/**
* Creates the links for the sprites after the atlas has been loaded. Used so we
* don't have to store duplicate sprites.
*/
createAtlasLinks() {
// NOT USED
}
/**
* Makes the canvas which shows the question mark, shown when a sprite was not found
*/
@ -216,14 +208,9 @@ class LoaderImpl {
// @ts-ignore
canvas.src = "not-found";
const resolutions = ["0.1", "0.25", "0.5", "0.75", "1"];
const sprite = new AtlasSprite({
spriteName: "not-found",
});
for (let i = 0; i < resolutions.length; ++i) {
const res = resolutions[i];
const link = new SpriteAtlasLink({
const sprite = new AtlasSprite("not-found");
["0.1", "0.25", "0.5", "0.75", "1"].forEach(resolution => {
sprite.linksByResolution[resolution] = new SpriteAtlasLink({
packedX: 0,
packedY: 0,
w: dims,
@ -234,8 +221,8 @@ class LoaderImpl {
packedH: dims,
atlas: canvas,
});
sprite.linksByResolution[res] = link;
}
});
this.spriteNotFoundSprite = sprite;
}
}

View File

@ -1,5 +1,5 @@
import { globalConfig } from "./config";
import { clamp, epsilonCompare, round2Digits } from "./utils";
import { epsilonCompare, round2Digits } from "./utils";
import { Vector } from "./vector";
export class Rectangle {

View File

@ -1,6 +1,6 @@
import { DrawParameters } from "./draw_parameters";
import { Rectangle } from "./rectangle";
import { epsilonCompare, round3Digits } from "./utils";
import { round3Digits } from "./utils";
const floorSpriteCoordinates = false;
@ -63,10 +63,9 @@ export class SpriteAtlasLink {
export class AtlasSprite extends BaseSprite {
/**
*
* @param {object} param0
* @param {string} param0.spriteName
* @param {string} spriteName
*/
constructor({ spriteName = "sprite" }) {
constructor(spriteName = "sprite") {
super();
/** @type {Object.<string, SpriteAtlasLink>} */
this.linksByResolution = {};
@ -197,8 +196,6 @@ export class AtlasSprite extends BaseSprite {
destH = intersection.h;
}
// assert(epsilonCompare(scaleW, scaleH), "Sprite should be square for cached rendering");
if (floorSpriteCoordinates) {
parameters.context.drawImage(
link.atlas,

View File

@ -1,46 +1,7 @@
import { globalConfig, IS_DEBUG } from "./config";
import { Vector } from "./vector";
import { T } from "../translations";
// Constants
export const TOP = new Vector(0, -1);
export const RIGHT = new Vector(1, 0);
export const BOTTOM = new Vector(0, 1);
export const LEFT = new Vector(-1, 0);
export const ALL_DIRECTIONS = [TOP, RIGHT, BOTTOM, LEFT];
const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"];
/**
* Returns the build id
* @returns {string}
*/
export function getBuildId() {
if (G_IS_DEV && IS_DEBUG) {
return "local-dev";
} else if (G_IS_DEV) {
return "dev-" + getPlatformName() + "-" + G_BUILD_COMMIT_HASH;
} else {
return "prod-" + getPlatformName() + "-" + G_BUILD_COMMIT_HASH;
}
}
/**
* Returns the environment id (dev, prod, etc)
* @returns {string}
*/
export function getEnvironmentId() {
if (G_IS_DEV && IS_DEBUG) {
return "local-dev";
} else if (G_IS_DEV) {
return "dev-" + getPlatformName();
} else if (G_IS_RELEASE) {
return "release-" + getPlatformName();
} else {
return "staging-" + getPlatformName();
}
}
/**
* Returns if this platform is android
* @returns {boolean}
@ -66,7 +27,7 @@ export function isIos() {
/**
* Returns a platform name
* @returns {string}
* @returns {"android" | "browser" | "ios" | "standalone" | "unknown"}
*/
export function getPlatformName() {
if (G_IS_STANDALONE) {
@ -96,60 +57,13 @@ export function getIPCRenderer() {
return ipcRenderer;
}
/**
* Formats a sensitive token by only displaying the first digits of it. Use for
* stuff like savegame keys etc which should not appear in the log.
* @param {string} key
*/
export function formatSensitive(key) {
if (!key) {
return "<null>";
}
key = key || "";
return "[" + key.substr(0, 8) + "...]";
}
/**
* Creates a new 2D array with the given fill method
* @param {number} w Width
* @param {number} h Height
* @param {(function(number, number) : any) | number | boolean | string | null | undefined} filler Either Fill method, which should return the content for each cell, or static content
* @param {string=} context Optional context for memory tracking
* @returns {Array<Array<any>>}
*/
export function make2DArray(w, h, filler, context = null) {
if (typeof filler === "function") {
const tiles = new Array(w);
for (let x = 0; x < w; ++x) {
const row = new Array(h);
for (let y = 0; y < h; ++y) {
row[y] = filler(x, y);
}
tiles[x] = row;
}
return tiles;
} else {
const tiles = new Array(w);
const row = new Array(h);
for (let y = 0; y < h; ++y) {
row[y] = filler;
}
for (let x = 0; x < w; ++x) {
tiles[x] = row.slice();
}
return tiles;
}
}
/**
* Makes a new 2D array with undefined contents
* @param {number} w
* @param {number} h
* @param {string=} context
* @returns {Array<Array<any>>}
*/
export function make2DUndefinedArray(w, h, context = null) {
export function make2DUndefinedArray(w, h) {
const result = new Array(w);
for (let x = 0; x < w; ++x) {
result[x] = new Array(h);
@ -157,33 +71,6 @@ export function make2DUndefinedArray(w, h, context = null) {
return result;
}
/**
* Clears a given 2D array with the given fill method
* @param {Array<Array<any>>} array
* @param {number} w Width
* @param {number} h Height
* @param {(function(number, number) : any) | number | boolean | string | null | undefined} filler Either Fill method, which should return the content for each cell, or static content
*/
export function clear2DArray(array, w, h, filler) {
assert(array.length === w, "Array dims mismatch w");
assert(array[0].length === h, "Array dims mismatch h");
if (typeof filler === "function") {
for (let x = 0; x < w; ++x) {
const row = array[x];
for (let y = 0; y < h; ++y) {
row[y] = filler(x, y);
}
}
} else {
for (let x = 0; x < w; ++x) {
const row = array[x];
for (let y = 0; y < h; ++y) {
row[y] = filler;
}
}
}
}
/**
* Creates a new map (an empty object without any props)
*/
@ -215,7 +102,9 @@ export function accessNestedPropertyReverse(obj, keys) {
/**
* Chooses a random entry of an array
* @param {Array | string} arr
* @template T
* @param {T[]} arr
* @returns {T}
*/
export function randomChoice(arr) {
return arr[Math.floor(Math.random() * arr.length)];
@ -304,23 +193,6 @@ export function arrayDeleteValue(array, value) {
return arrayDelete(array, index);
}
// Converts a direction into a 0 .. 7 index
/**
* Converts a direction into a index from 0 .. 7, used for miners, zombies etc which have 8 sprites
* @param {Vector} offset direction
* @param {boolean} inverse if inverse, the direction is reversed
* @returns {number} in range [0, 7]
*/
export function angleToSpriteIndex(offset, inverse = false) {
const twoPi = 2.0 * Math.PI;
const factor = inverse ? -1 : 1;
const offs = inverse ? 2.5 : 3.5;
const angle = (factor * Math.atan2(offset.y, offset.x) + offs * Math.PI) % twoPi;
const index = Math.round((angle / twoPi) * 8) % 8;
return index;
}
/**
* Compare two floats for epsilon equality
* @param {number} a
@ -331,15 +203,6 @@ export function epsilonCompare(a, b, epsilon = 1e-5) {
return Math.abs(a - b) < epsilon;
}
/**
* Compare a float for epsilon equal to 0
* @param {number} a
* @returns {boolean}
*/
export function epsilonIsZero(a) {
return epsilonCompare(a, 0);
}
/**
* Interpolates two numbers
* @param {number} a
@ -399,17 +262,6 @@ export function findNiceIntegerValue(num) {
return Math.ceil(findNiceValue(num));
}
/**
* Smart rounding + fractional handling
* @param {number} n
*/
function roundSmart(n) {
if (n < 100) {
return n.toFixed(1);
}
return Math.round(n);
}
/**
* Formats a big number
* @param {number} num
@ -477,92 +329,12 @@ export function formatBigNumberFull(num, divider = T.global.thousandsDivider) {
return out.substring(0, out.length - 1);
}
/**
* Delayes a promise so that it will resolve after a *minimum* amount of time only
* @param {Promise<any>} promise The promise to delay
* @param {number} minTimeMs The time to make it run at least
* @returns {Promise<any>} The delayed promise
*/
export function artificialDelayedPromise(promise, minTimeMs = 500) {
if (G_IS_DEV && globalConfig.debug.noArtificialDelays) {
return promise;
}
const startTime = performance.now();
return promise.then(
result => {
const timeTaken = performance.now() - startTime;
const waitTime = Math.floor(minTimeMs - timeTaken);
if (waitTime > 0) {
return new Promise(resolve => {
setTimeout(() => {
resolve(result);
}, waitTime);
});
} else {
return result;
}
},
error => {
const timeTaken = performance.now() - startTime;
const waitTime = Math.floor(minTimeMs - timeTaken);
if (waitTime > 0) {
// @ts-ignore
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(error);
}, waitTime);
});
} else {
throw error;
}
}
);
}
/**
* Computes a sine-based animation which pulsates from 0 .. 1 .. 0
* @param {number} time Current time in seconds
* @param {number} duration Duration of the full pulse in seconds
* @param {number} seed Seed to offset the animation
*/
export function pulseAnimation(time, duration = 1.0, seed = 0.0) {
return Math.sin((time * Math.PI * 2.0) / duration + seed * 5642.86729349) * 0.5 + 0.5;
}
/**
* Returns the smallest angle between two angles
* @param {number} a
* @param {number} b
* @returns {number} 0 .. 2 PI
*/
export function smallestAngle(a, b) {
return safeMod(a - b + Math.PI, 2.0 * Math.PI) - Math.PI;
}
/**
* Modulo which works for negative numbers
* @param {number} n
* @param {number} m
*/
export function safeMod(n, m) {
return ((n % m) + m) % m;
}
/**
* Wraps an angle between 0 and 2 pi
* @param {number} angle
*/
export function wrapAngle(angle) {
return safeMod(angle, 2.0 * Math.PI);
}
/**
* Waits two frames so the ui is updated
* @returns {Promise<void>}
*/
export function waitNextFrame() {
return new Promise(function (resolve, reject) {
return new Promise(function (resolve) {
window.requestAnimationFrame(function () {
window.requestAnimationFrame(function () {
resolve();
@ -617,27 +389,13 @@ export function clamp(v, minimum = 0, maximum = 1) {
return Math.max(minimum, Math.min(maximum, v));
}
/**
* Measures how long a function took
* @param {string} name
* @param {function():void} target
*/
export function measure(name, target) {
const now = performance.now();
for (let i = 0; i < 25; ++i) {
target();
}
const dur = (performance.now() - now) / 25.0;
console.warn("->", name, "took", dur.toFixed(2), "ms");
}
/**
* Helper method to create a new div element
* @param {string=} id
* @param {Array<string>=} classes
* @param {string=} innerHTML
*/
export function makeDivElement(id = null, classes = [], innerHTML = "") {
function makeDivElement(id = null, classes = [], innerHTML = "") {
const div = document.createElement("div");
if (id) {
div.id = id;
@ -662,20 +420,6 @@ export function makeDiv(parent, id = null, classes = [], innerHTML = "") {
return div;
}
/**
* Helper method to create a new div and place before reference Node
* @param {Element} parent
* @param {Element} referenceNode
* @param {string=} id
* @param {Array<string>=} classes
* @param {string=} innerHTML
*/
export function makeDivBefore(parent, referenceNode, id = null, classes = [], innerHTML = "") {
const div = makeDivElement(id, classes, innerHTML);
parent.insertBefore(div, referenceNode);
return div;
}
/**
* Helper method to create a new button element
* @param {Array<string>=} classes
@ -703,19 +447,6 @@ export function makeButton(parent, classes = [], innerHTML = "") {
return element;
}
/**
* Helper method to create a new button and place before reference Node
* @param {Element} parent
* @param {Element} referenceNode
* @param {Array<string>=} classes
* @param {string=} innerHTML
*/
export function makeButtonBefore(parent, referenceNode, classes = [], innerHTML = "") {
const element = makeButtonElement(classes, innerHTML);
parent.insertBefore(element, referenceNode);
return element;
}
/**
* Removes all children of the given element
* @param {Element} elem
@ -728,20 +459,10 @@ export function removeAllChildren(elem) {
}
}
export function smartFadeNumber(current, newOne, minFade = 0.01, maxFade = 0.9) {
const tolerance = Math.min(current, newOne) * 0.5 + 10;
let fade = minFade;
if (Math.abs(current - newOne) < tolerance) {
fade = maxFade;
}
return current * fade + newOne * (1 - fade);
}
/**
* Fixes lockstep simulation by converting times like 34.0000000003 to 34.00.
* We use 3 digits of precision, this allows to store sufficient precision of 1 ms without
* the risk to simulation errors due to resync issues
* We use 3 digits of precision, this allows us to store precision of 1 ms without
* the risking simulation errors due to resync issues
* @param {number} value
*/
export function quantizeFloat(value) {
@ -840,37 +561,6 @@ export function isSupportedBrowser() {
}
}
/**
* Helper function to create a json schema object
* @param {any} properties
*/
export function schemaObject(properties) {
return {
type: "object",
required: Object.keys(properties).slice(),
additionalProperties: false,
properties,
};
}
/**
* Quickly
* @param {number} x
* @param {number} y
* @param {number} deg
* @returns {Vector}
*/
export function fastRotateMultipleOf90(x, y, deg) {
switch (deg) {
case 0: {
return new Vector(x, y);
}
case 90: {
return new Vector(x, y);
}
}
}
/**
* Formats an amount of seconds into something like "5s ago"
* @param {number} secs Seconds
@ -928,31 +618,6 @@ export function formatSeconds(secs) {
}
}
/**
* Generates a file download
* @param {string} filename
* @param {string} text
*/
export function generateFileDownload(filename, text) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
/**
* Capitalizes the first letter
* @param {string} str
*/
export function capitalizeFirstLetter(str) {
return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase();
}
/**
* Formats a number like 2.5 to "2.5 items / s"
* @param {number} speed

View File

@ -6,7 +6,7 @@ import { MetaBuilding, defaultBuildingVariant } from "../meta_building";
import { GameRoot } from "../root";
import { enumHubGoalRewards } from "../tutorial_goals";
import { T } from "../../translations";
import { round1Digit, round2Digits, formatItemsPerSecond } from "../../core/utils";
import { formatItemsPerSecond } from "../../core/utils";
/** @enum {string} */
export const enumMinerVariants = { chainable: "chainable" };

View File

@ -44,3 +44,9 @@ export class Component extends BasicSerializableObject {
}
/* dev:end */
}
/**
* TypeScript does not support Abstract Static methods (https://github.com/microsoft/TypeScript/issues/34516)
* One workaround is to declare the type of the component and reference that for static methods
* @typedef {typeof Component} StaticComponent
*/

View File

@ -1,7 +1,6 @@
import { GameRoot } from "./root";
import { createLogger } from "../core/logging";
import { globalConfig } from "../core/config";
import { round3Digits } from "../core/utils";
const logger = createLogger("dynamic_tickrate");

View File

@ -1,5 +1,4 @@
import { globalConfig } from "../core/config";
import { queryParamOptions } from "../core/query_parameters";
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { enumColors } from "./colors";
@ -7,7 +6,7 @@ import { enumItemProcessorTypes } from "./components/item_processor";
import { GameRoot, enumLayer } from "./root";
import { enumSubShape, ShapeDefinition } from "./shape_definition";
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
import { UPGRADES, blueprintShape } from "./upgrades";
import { UPGRADES } from "./upgrades";
export class HubGoals extends BasicSerializableObject {
static getId() {
@ -328,9 +327,7 @@ export class HubGoals extends BasicSerializableObject {
/** @type {Array<import("./shape_definition").ShapeLayer>} */
let layers = [];
// @ts-ignore
const randomColor = () => randomChoice(Object.values(enumColors));
// @ts-ignore
const randomShape = () => randomChoice(Object.values(enumSubShape));
let anyIsMissingTwo = false;

View File

@ -1,7 +1,7 @@
import { ClickDetector } from "../../../core/click_detector";
import { globalConfig, THIRDPARTY_URLS } from "../../../core/config";
import { globalConfig } from "../../../core/config";
import { DrawParameters } from "../../../core/draw_parameters";
import { drawRotatedSprite, rotateTrapezRightFaced } from "../../../core/draw_utils";
import { drawRotatedSprite } from "../../../core/draw_utils";
import { Loader } from "../../../core/loader";
import { clamp, makeDiv, removeAllChildren } from "../../../core/utils";
import {
@ -323,7 +323,6 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
// Fade in / out
parameters.context.lineWidth = 1;
// parameters.context.globalAlpha = 0.3 + pulseAnimation(this.root.time.realtimeNow(), 0.9) * 0.7;
// Determine the bounds and visualize them
const entityBounds = staticComp.getTileSpaceBounds();

View File

@ -1,10 +1,9 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv, randomInt } from "../../../core/utils";
import { makeDiv } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound";
import { enumNotificationType } from "./notifications";
import { T } from "../../../translations";
import { KEYMAPPINGS } from "../../key_action_mapper";
import { IS_DEMO } from "../../../core/config";
import { DynamicDomAttach } from "../dynamic_dom_attach";
export class HUDGameMenu extends BaseHUDPart {

View File

@ -1,5 +1,5 @@
import { ClickDetector } from "../../../core/click_detector";
import { formatBigNumber, makeDiv, arrayDelete, arrayDeleteValue } from "../../../core/utils";
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape, UPGRADES } from "../../upgrades";

View File

@ -1,13 +1,11 @@
import { BaseHUDPart } from "../base_hud_part";
import { makeDiv, formatSeconds, formatBigNumberFull } from "../../../core/utils";
import { makeDiv, formatBigNumberFull } from "../../../core/utils";
import { DynamicDomAttach } from "../dynamic_dom_attach";
import { InputReceiver } from "../../../core/input_receiver";
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
import { T } from "../../../translations";
import { StaticMapEntityComponent } from "../../components/static_map_entity";
import { ItemProcessorComponent } from "../../components/item_processor";
import { BeltComponent } from "../../components/belt";
import { IS_DEMO } from "../../../core/config";
export class HUDSettingsMenu extends BaseHUDPart {
createElements(parent) {
@ -57,16 +55,7 @@ export class HUDSettingsMenu extends BaseHUDPart {
}
returnToMenu() {
// if (IS_DEMO) {
// const { cancel, deleteGame } = this.root.hud.parts.dialogs.showWarning(
// T.dialogs.leaveNotPossibleInDemo.title,
// T.dialogs.leaveNotPossibleInDemo.desc,
// ["cancel:good", "deleteGame:bad"]
// );
// deleteGame.add(() => this.root.gameState.goBackToMenu());
// } else {
this.root.gameState.goBackToMenu();
// }
}
goToSettings() {
@ -102,7 +91,6 @@ export class HUDSettingsMenu extends BaseHUDPart {
show() {
this.visible = true;
document.body.classList.add("ingameDialogOpen");
// this.background.classList.add("visible");
this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever);
const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60);

View File

@ -1,5 +1,5 @@
import { InputReceiver } from "../../../core/input_receiver";
import { makeButton, makeDiv, removeAllChildren, capitalizeFirstLetter } from "../../../core/utils";
import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils";
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
import { enumAnalyticsDataSource } from "../../production_analytics";
import { BaseHUDPart } from "../base_hud_part";
@ -7,6 +7,14 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle";
import { T } from "../../../translations";
/**
* Capitalizes the first letter
* @param {string} str
*/
function capitalizeFirstLetter(str) {
return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase();
}
export class HUDStatistics extends BaseHUDPart {
createElements(parent) {
this.background = makeDiv(parent, "ingame_HUD_Statistics", ["ingameDialog"]);

View File

@ -5,7 +5,6 @@ import { Entity } from "./entity";
import { createLogger } from "../core/logging";
import { BaseItem } from "./base_item";
import { MapChunkView } from "./map_chunk_view";
import { randomInt } from "../core/utils";
import { BasicSerializableObject, types } from "../savegame/serialization";
const logger = createLogger("map");

View File

@ -28,25 +28,13 @@ export class MapChunk {
this.tileY = y * globalConfig.mapChunkSize;
/** @type {Array<Array<?Entity>>} */
this.contents = make2DUndefinedArray(
globalConfig.mapChunkSize,
globalConfig.mapChunkSize,
"map-chunk@" + this.x + "|" + this.y
);
this.contents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
/** @type {Array<Array<?Entity>>} */
this.wireContents = make2DUndefinedArray(
globalConfig.mapChunkSize,
globalConfig.mapChunkSize,
"map-chunk-wires@" + this.x + "|" + this.y
);
this.wireContents = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
/** @type {Array<Array<?BaseItem>>} */
this.lowerLayer = make2DUndefinedArray(
globalConfig.mapChunkSize,
globalConfig.mapChunkSize,
"map-chunk-lower@" + this.x + "|" + this.y
);
this.lowerLayer = make2DUndefinedArray(globalConfig.mapChunkSize, globalConfig.mapChunkSize);
/** @type {Array<Entity>} */
this.containedEntities = [];

View File

@ -1,15 +1,6 @@
import { MapChunk } from "./map_chunk";
import { GameRoot } from "./root";
import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters";
import { round1Digit } from "../core/utils";
import { Rectangle } from "../core/rectangle";
import { createLogger } from "../core/logging";
import { smoothenDpi } from "../core/dpi_manager";
import { THEME } from "./theme";
const logger = createLogger("chunk");
const chunkSizePixels = globalConfig.mapChunkSize * globalConfig.tileSize;
export class MapChunkView extends MapChunk {
/**

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

@ -131,22 +131,24 @@ declare interface Math {
degrees(number): number;
}
declare type Class<T = unknown> = new (...args: any[]) => T;
declare interface String {
padStart(size: number, fill?: string): string;
padEnd(size: number, fill: string): string;
}
declare interface FactoryTemplate<T> {
entries: Array<new (...args: any[]) => T>;
entries: Array<Class<T>>;
entryIds: Array<string>;
idToEntry: any;
getId(): string;
getAllIds(): Array<string>;
register(entry: new (...args: any[]) => T): void;
register(entry: Class<T>): void;
hasId(id: string): boolean;
findById(id: string): new (...args: any[]) => T;
getEntries(): Array<new (...args: any[]) => T>;
findById(id: string): Class<T>;
getEntries(): Array<Class<T>>;
getNumEntries(): number;
}
@ -156,10 +158,10 @@ declare interface SingletonFactoryTemplate<T> {
getId(): string;
getAllIds(): Array<string>;
register(classHandle: new (...args: any[]) => T): void;
register(classHandle: Class<T>): void;
hasId(id: string): boolean;
findById(id: string): T;
findByClass(classHandle: new (...args: any[]) => T): T;
findByClass(classHandle: Class<T>): T;
getEntries(): Array<T>;
getNumEntries(): number;
}

View File

@ -1,8 +1,6 @@
/* typehints:start */
import { Application } from "../application";
import { ShapeDefinition } from "../game/shape_definition";
import { Savegame } from "../savegame/savegame";
/* typehints:end */
/**
* @typedef {import("../application").Application} Application
*/
export class GameAnalyticsInterface {
constructor(app) {

View File

@ -221,7 +221,7 @@ export const allApplicationSettings = [
}),
new EnumSetting("refreshRate", {
options: ["60", "100", "120", "144", "165", "250", G_IS_DEV ? "10" : "500"],
options: ["60", "75", "100", "120", "144", "165", "250", G_IS_DEV ? "10" : "500"],
valueGetter: rate => rate,
textGetter: rate => rate + " Hz",
category: enumCategories.advanced,

View File

@ -1,8 +1,3 @@
/* typehints:start */
import { Application } from "../application";
import { GameRoot } from "../game/root";
/* typehints:end */
import { ReadWriteProxy } from "../core/read_write_proxy";
import { ExplainedResult } from "../core/explained_result";
import { SavegameSerializer } from "./savegame_serializer";
@ -18,20 +13,29 @@ import { SavegameInterface_V1005 } from "./schemas/1005";
const logger = createLogger("savegame");
/**
* @typedef {import("../application").Application} Application
* @typedef {import("../game/root").GameRoot} GameRoot
* @typedef {import("./savegame_typedefs").SavegameData} SavegameData
* @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
* @typedef {import("./savegame_typedefs").SavegameStats} SavegameStats
* @typedef {import("./savegame_typedefs").SerializedGame} SerializedGame
*/
export class Savegame extends ReadWriteProxy {
/**
*
* @param {Application} app
* @param {object} param0
* @param {string} param0.internalId
* @param {import("./savegame_manager").SavegameMetadata} param0.metaDataRef Handle to the meta data
* @param {SavegameMetadata} param0.metaDataRef Handle to the meta data
*/
constructor(app, { internalId, metaDataRef }) {
super(app, "savegame-" + internalId + ".bin");
this.internalId = internalId;
this.metaDataRef = metaDataRef;
/** @type {import("./savegame_typedefs").SavegameData} */
/** @type {SavegameData} */
this.currentData = this.getDefaultData();
assert(
@ -65,7 +69,7 @@ export class Savegame extends ReadWriteProxy {
/**
* Returns the savegames default data
* @returns {import("./savegame_typedefs").SavegameData}
* @returns {SavegameData}
*/
getDefaultData() {
return {
@ -78,7 +82,7 @@ export class Savegame extends ReadWriteProxy {
/**
* Migrates the savegames data
* @param {import("./savegame_typedefs").SavegameData} data
* @param {SavegameData} data
*/
migrate(data) {
if (data.version < 1000) {
@ -115,7 +119,7 @@ export class Savegame extends ReadWriteProxy {
/**
* Verifies the savegames data
* @param {import("./savegame_typedefs").SavegameData} data
* @param {SavegameData} data
*/
verify(data) {
if (!data.dump) {
@ -140,7 +144,7 @@ export class Savegame extends ReadWriteProxy {
}
/**
* Returns the statistics of the savegame
* @returns {import("./savegame_typedefs").SavegameStats}
* @returns {SavegameStats}
*/
getStatistics() {
return this.currentData.stats;
@ -163,7 +167,7 @@ export class Savegame extends ReadWriteProxy {
/**
* Returns the current game dump
* @returns {import("./savegame_typedefs").SerializedGame}
* @returns {SerializedGame}
*/
getCurrentDump() {
return this.currentData.dump;

View File

@ -7,31 +7,21 @@ const logger = createLogger("savegame_manager");
const Rusha = require("rusha");
/**
* @typedef {import("./savegame_typedefs").SavegamesData} SavegamesData
* @typedef {import("./savegame_typedefs").SavegameMetadata} SavegameMetadata
*/
/** @enum {string} */
export const enumLocalSavegameStatus = {
offline: "offline",
synced: "synced",
};
/**
* @typedef {{
* lastUpdate: number,
* version: number,
* internalId: string,
* level: number
* }} SavegameMetadata
*
* @typedef {{
* version: number,
* savegames: Array<SavegameMetadata>
* }} SavegamesData
*/
export class SavegameManager extends ReadWriteProxy {
constructor(app) {
super(app, "savegames.bin");
/** @type {SavegamesData} */
this.currentData = this.getDefaultData();
}

View File

@ -1,18 +1,20 @@
/* typehints:start */
import { Component } from "../game/component";
import { GameRoot } from "../game/root";
/* typehints:end */
import { ExplainedResult } from "../core/explained_result";
import { createLogger } from "../core/logging";
// import { BuildingComponent } from "../components/impl/building";
import { gComponentRegistry } from "../core/global_registries";
import { SerializerInternal } from "./serializer_internal";
/**
* @typedef {import("../game/component").Component} Component
* @typedef {import("../game/component").StaticComponent} StaticComponent
* @typedef {import("../game/entity").Entity} Entity
* @typedef {import("../game/root").GameRoot} GameRoot
* @typedef {import("../savegame/savegame_typedefs").SerializedGame} SerializedGame
*/
const logger = createLogger("savegame_serializer");
/**
* Allows to serialize a savegame
* Serializes a savegame
*/
export class SavegameSerializer {
constructor() {
@ -26,7 +28,7 @@ export class SavegameSerializer {
* @returns {object}
*/
generateDumpFromGameRoot(root, sanityChecks = true) {
// Now store generic savegame payload
/** @type {SerializedGame} */
const data = {
camera: root.camera.serialize(),
time: root.time.serialize(),
@ -35,11 +37,10 @@ export class SavegameSerializer {
hubGoals: root.hubGoals.serialize(),
pinnedShapes: root.hud.parts.pinnedShapes.serialize(),
waypoints: root.hud.parts.waypoints.serialize(),
entities: this.internal.serializeEntityArray(root.entityMgr.entities),
beltPaths: root.systemMgr.systems.belt.serializePaths(),
};
data.entities = this.internal.serializeEntityArray(root.entityMgr.entities);
if (!G_IS_RELEASE) {
if (sanityChecks) {
// Sanity check
@ -55,7 +56,7 @@ export class SavegameSerializer {
/**
* Verifies if there are logical errors in the savegame
* @param {object} savegame
* @param {SerializedGame} savegame
* @returns {ExplainedResult}
*/
verifyLogicalErrors(savegame) {
@ -66,47 +67,44 @@ export class SavegameSerializer {
const seenUids = [];
// Check for duplicate UIDS
for (const entityListId in savegame.entities) {
for (let i = 0; i < savegame.entities[entityListId].length; ++i) {
const list = savegame.entities[entityListId][i];
for (let k = 0; k < list.length; ++k) {
const entity = list[k];
const uid = entity.uid;
if (!Number.isInteger(uid)) {
return ExplainedResult.bad("Entity has invalid uid: " + uid);
}
if (seenUids.indexOf(uid) >= 0) {
return ExplainedResult.bad("Duplicate uid " + uid);
}
seenUids.push(uid);
for (let i = 0; i < savegame.entities.length; ++i) {
/** @type {Entity} */
const entity = savegame.entities[i];
// Verify components
if (!entity.components) {
return ExplainedResult.bad(
"Entity is missing key 'components': " + JSON.stringify(entity)
);
}
const components = entity.components;
for (const componentId in components) {
// Verify component data
const componentData = components[componentId];
const componentClass = gComponentRegistry.findById(componentId);
const uid = entity.uid;
if (!Number.isInteger(uid)) {
return ExplainedResult.bad("Entity has invalid uid: " + uid);
}
if (seenUids.indexOf(uid) >= 0) {
return ExplainedResult.bad("Duplicate uid " + uid);
}
seenUids.push(uid);
// Check component id is known
if (!componentClass) {
return ExplainedResult.bad("Unknown component id: " + componentId);
}
// Verify components
if (!entity.components) {
return ExplainedResult.bad("Entity is missing key 'components': " + JSON.stringify(entity));
}
// Check component data is ok
const componentVerifyError = /** @type {typeof Component} */ (componentClass).verify(
componentData
);
if (componentVerifyError) {
return ExplainedResult.bad(
"Component " + componentId + " has invalid data: " + componentVerifyError
);
}
}
const components = entity.components;
for (const componentId in components) {
const componentClass = gComponentRegistry.findById(componentId);
// Check component id is known
if (!componentClass) {
return ExplainedResult.bad("Unknown component id: " + componentId);
}
// Verify component data
const componentData = components[componentId];
const componentVerifyError = /** @type {StaticComponent} */ (componentClass).verify(
componentData
);
// Check component data is ok
if (componentVerifyError) {
return ExplainedResult.bad(
"Component " + componentId + " has invalid data: " + componentVerifyError
);
}
}
}
@ -116,7 +114,7 @@ export class SavegameSerializer {
/**
* Tries to load the savegame from a given dump
* @param {import("./savegame_typedefs").SerializedGame} savegame
* @param {SerializedGame} savegame
* @param {GameRoot} root
* @returns {ExplainedResult}
*/

View File

@ -1,15 +1,8 @@
import { Entity } from "../game/entity";
/**
* @typedef {{
* }} SavegameStats
*/
/**
* @typedef {import("../game/entity").Entity} Entity
*
* @typedef {{}} SavegameStats
*
*/
/**
* @typedef {{
* camera: any,
* time: any,
@ -21,13 +14,25 @@ import { Entity } from "../game/entity";
* entities: Array<Entity>,
* beltPaths: Array<any>
* }} SerializedGame
*/
/**
*
* @typedef {{
* version: number,
* dump: SerializedGame,
* stats: SavegameStats,
* lastUpdate: number,
* }} SavegameData
*
* @typedef {{
* lastUpdate: number,
* version: number,
* internalId: string,
* level: number
* }} SavegameMetadata
*
* @typedef {{
* version: number,
* savegames: Array<SavegameMetadata>
* }} SavegamesData
*/
export default {};

View File

@ -43,6 +43,9 @@ export class SavegameInterface_V1001 extends SavegameInterface_V1000 {
for (let i = 0; i < entities.length; ++i) {
const entity = entities[i];
// FIXME - https://github.com/tobspr/shapez.io/issues/514
// Broken in https://github.com/tobspr/shapez.io/commit/bf2eee908fedb84dbbabd359a200c446020a340e
/** @type any **/
const staticComp = entity.components.StaticMapEntity;
const beltComp = entity.components.Belt;
if (staticComp) {

View File

@ -4,7 +4,7 @@ import { BasicSerializableObject } from "./serialization";
/* typehints:end */
import { Vector } from "../core/vector";
import { round4Digits, schemaObject, accessNestedPropertyReverse } from "../core/utils";
import { round4Digits } from "../core/utils";
export const globalJsonSchemaDefs = {};
/**
@ -28,6 +28,19 @@ export function schemaToJsonSchema(schema) {
return jsonSchema;
}
/**
* Helper function to create a json schema object
* @param {any} properties
*/
function schemaObject(properties) {
return {
type: "object",
required: Object.keys(properties).slice(),
additionalProperties: false,
properties,
};
}
/**
* Base serialization data type
*/
@ -75,23 +88,6 @@ export class BaseDataType {
return {
$ref: "#/definitions/" + key,
};
// return this.getAsJsonSchemaUncached();
// if (!globalJsonSchemaDefs[key]) {
// // schema.$id = key;
// globalJsonSchemaDefs[key] = {
// $id: key,
// definitions: {
// ["d-" + key]: schema
// }
// };
// }
// return {
// $ref: key + "#/definitions/d-" + key
// }
// // return this.getAsJsonSchemaUncached();
}
/**

View File

@ -1,11 +1,10 @@
import { GameState } from "../core/game_state";
import { cachebust } from "../core/cachebust";
import { globalConfig, IS_DEBUG, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
import {
makeDiv,
makeButtonElement,
formatSecondsToTimeAgo,
generateFileDownload,
waitNextFrame,
isSupportedBrowser,
makeButton,
@ -14,9 +13,29 @@ import {
import { ReadWriteProxy } from "../core/read_write_proxy";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { T } from "../translations";
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
import { getApplicationSettingById } from "../profile/application_settings";
import { EnumSetting } from "../profile/setting_types";
/**
* @typedef {import("../savegame/savegame_typedefs").SavegameMetadata} SavegameMetadata
* @typedef {import("../profile/setting_types").EnumSetting} EnumSetting
*/
/**
* Generates a file download
* @param {string} filename
* @param {string} text
*/
function generateFileDownload(filename, text) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
export class MainMenuState extends GameState {
constructor() {
@ -128,7 +147,6 @@ export class MainMenuState extends GameState {
const closeLoader = this.dialogs.showLoadingDialog();
const reader = new FileReader();
reader.addEventListener("load", event => {
// @ts-ignore
const contents = event.target.result;
let realContent;
@ -394,7 +412,7 @@ export class MainMenuState extends GameState {
}
/**
* @param {object} game
* @param {SavegameMetadata} game
*/
resumeGame(game) {
this.app.analytics.trackUiClick("resume_game");
@ -419,7 +437,7 @@ export class MainMenuState extends GameState {
}
/**
* @param {object} game
* @param {SavegameMetadata} game
*/
deleteGame(game) {
this.app.analytics.trackUiClick("delete_game");
@ -447,7 +465,7 @@ export class MainMenuState extends GameState {
}
/**
* @param {object} game
* @param {SavegameMetadata} game
*/
downloadGame(game) {
this.app.analytics.trackUiClick("download_game");

View File

@ -1,6 +1,6 @@
import { GameState } from "../core/game_state";
import { createLogger } from "../core/logging";
import { findNiceValue, waitNextFrame } from "../core/utils";
import { findNiceValue } from "../core/utils";
import { cachebust } from "../core/cachebust";
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
import { T, autoDetectLanguageId, updateApplicationLanguage } from "../translations";
@ -228,11 +228,7 @@ export class PreloadState extends GameState {
this.statusBar.style.width = percentage + "%";
this.statusBarText.innerText = findNiceValue(percentage) + "%";
if (G_IS_DEV) {
return Promise.resolve();
}
return Promise.resolve();
// return waitNextFrame();
}
showFailMessage(text) {
@ -279,11 +275,6 @@ export class PreloadState extends GameState {
if (confirm("Are you sure you want to reset the app? This will delete all your savegames")) {
this.resetApp();
}
// const signals = this.dialogs.showWarning(T.preload.reset_app_warning.title, T.preload.reset_app_warning.desc, [
// "delete:bad:timeout",
// "cancel:good",
// ]);
// signals.delete.add(this.resetApp, this);
}
resetApp() {

View File

@ -33,13 +33,13 @@ steamPage:
shapez.io est un jeu dans lequel vous devrez construire des usines pour automatiser la création et la combinaison de formes de plus en plus complexes sur une carte infinie.
Lors de la livraison des formes requises vous progresserez et débloquerez des améliorations pour accélerer votre usine.
Au vu de l'augmantation des demandes de formes, vous devrez agrandir votre usine pour répondre à la forte demande - Mais n'oubliez pas les ressources, vous drevrez vous étendre au milieu de cette [b]carte infinie[/b] !
Au vu de l'augmentation des demandes de formes, vous devrez agrandir votre usine pour répondre à la forte demande - Mais n'oubliez pas les ressources, vous drevrez vous étendre au milieu de cette [b]carte infinie[/b] !
Bientôt vous devrez mixer les couleurs et peindre vos formes avec - Combinez les ressources de couleurs rouge, verte et bleue pour produire différentes couleurs et peindre les formes avec pour satisfaire la demande.
Ce jeu propose 18 niveaux progressifs (qui devraient déjà vous occuper des heures!) mais j'ajoute constamment de nouveau contenus - Il y en a beaucoup de prévus !
Ce jeu propose 18 niveaux progressifs (qui devraient déjà vous occuper quelques heures !) mais j'ajoute constamment de nouveau contenus - Il y en a beaucoup de prévus !
Acheter le jeu vous donne accès à la version complète qui a des fonctionnalitées additionnelles et vous recevrez aussi un accès à des fonctionnalitées fraîchement développées.
Acheter le jeu vous donne accès à la version complète qui a des fonctionnalités additionnelles et vous recevrez aussi un accès à des fonctionnalités fraîchement développées.
[b]Avantages de la version complète (standalone)[/b]
@ -59,11 +59,11 @@ steamPage:
[list]
[*] Différentes cartes et challenges (e.g. carte avec obstacles)
[*] Puzzles (Délivrer la forme requise avec une zone limitée/jeu de batîments)
[*] Un mode histoire où les batîments ont un coût
[*] Générateur de carte configurable (configuration des ressources/formes taille/densitée, graine et plus)
[*] Puzzles (Délivrer la forme requise avec une zone limitée/jeu de bâtiments)
[*] Un mode histoire où les bâtiments ont un coût
[*] Générateur de carte configurable (configuration des ressources/formes/taille/densitée, seed et plus)
[*] Plus de formes
[*] Amélioration des performances (Le jeu tourne déjà plutot bien!)
[*] Amélioration des performances (Le jeu tourne déjà plutot bien !)
[*] Et bien plus !
[/list]
@ -127,7 +127,7 @@ global:
control: CTRL
alt: ALT
escape: ESC
shift: SHIFT
shift: MAJ
space: ESPACE
demoBanners:
@ -140,13 +140,13 @@ mainMenu:
play: Jouer
changelog: Historique
importSavegame: Importer
openSourceHint: Ce jeu est open source!
openSourceHint: Ce jeu est open source !
discordLink: Serveur Discord officiel
helpTranslate: Contribuez à la traduction!
helpTranslate: Contribuez à la traduction !
# This is shown when using firefox and other browsers which are not supported.
browserWarning: >-
Désolé, mais ce jeu est connu pour tourner lentement sur votre navigateur web ! Procurez-vous la version complète ou téléchargez Chrome pour une meilleure expérience.
Désolé, ce jeu est connu pour tourner lentement sur votre navigateur web ! Procurez-vous la version complète ou téléchargez Chrome pour une meilleure expérience.
savegameLevel: Niveau <x>
savegameLevelUnknown: Niveau inconnu
@ -234,12 +234,12 @@ dialogs:
massDeleteConfirm:
title: Confirmation de suppression
desc: >-
Vous allez supprimer pas mal de bâtiments (<count> pour être exact) ! Êtes vous certains de vouloir faire cela ?
Vous allez supprimer pas mal de bâtiments (<count> pour être exact) ! Êtes vous certains de vouloir faire ça ?
massCutConfirm:
title: Confirmer la coupure
desc: >-
Vous vous apprêtez à couper beaucoup de bâtiments (<count> pour être précis) ! Êtes-vous certains de vouloir faire cela ?
Vous vous apprêtez à couper beaucoup de bâtiments (<count> pour être précis) ! Êtes-vous certains de vouloir faire ça ?
blueprintsNotUnlocked:
title: Pas encore débloqué
@ -351,10 +351,10 @@ ingame:
description: Affiche le nombre de formes stockées dans votre bâtiment central.
produced:
title: Produit
description: Affiche tous les formes que votre usine entière produit, en incluant les formes intermédiaires.
description: Affiche tous les formes que votre usine produit, en incluant les formes intermédiaires.
delivered:
title: Délivré
description: Affiche les formes qui ont été livrées dans votre centre.
description: Affiche les formes qui ont été livrées dans votre bâtiment central.
noShapesProduced: Aucune forme n'a été produite jusqu'à présent.
# Displays the shapes per minute, e.g. '523 / m'
@ -398,7 +398,7 @@ ingame:
Connectez l'extracteur avec un <strong>convoyeur</strong> vers votre centre !<br><br>Astuce: <strong>Cliquez et faites glisser</strong> le convoyeur avec votre souris !
1_3_expand: >-
Ceci n'est <strong>PAS</strong> un jeu incrémental et inactif ! Construisez plus d'extracteurs et de convoyeurs pour atteindre plus vite votre votre but.<br><br>Astuce: Gardez <strong>SHIFT</strong> enfoncé pour placer plusieurs extracteurs, et utilisez <strong>R</strong> pour les faire pivoter.
Ceci n'est <strong>PAS</strong> un jeu incrémental et inactif ! Construisez plus d'extracteurs et de convoyeurs pour atteindre plus vite votre votre but.<br><br>Astuce: Gardez <strong>MAJ</strong> enfoncé pour placer plusieurs extracteurs, et utilisez <strong>R</strong> pour les faire pivoter.
colors:
red: Rouge
@ -487,8 +487,8 @@ buildings:
name: Pivoteur inversé
description: Fait pivoter une forme de 90 degrés vers la gauche.
fl:
name: Rotate (180)
description: Rotates shapes by 180 degrees.
name: Pivoteur (180)
description: Fait pivoter les formes de 90 degrés.
stacker:
default:
@ -707,9 +707,9 @@ settings:
Affiche ou non le bouton 'Afficher un indice' dans le coin inférieur gauche.
language:
title: Langage
title: Langue
description: >-
Change le langage. Toutes les traductions sont des contributions des utilisateurs et pourraient être partiellement incomplètes !
Change la langue. Toutes les traductions sont des contributions des utilisateurs et pourraient être partiellement incomplètes !
movementSpeed:
title: Vitesse de déplacement

View File

@ -155,6 +155,7 @@ mainMenu:
savegameLevel: <x>. szint
savegameLevelUnknown: Ismeretlen szint
dialogs:
buttons:
ok: OK
@ -327,7 +328,7 @@ ingame:
# Notifications on the lower right
notifications:
newUpgrade: Egy új fejlesztés elérhető!
gameSaved: A játékállás el lett mentve.
gameSaved: A játékállás mentve.
# The "Upgrades" window
shop:
@ -353,7 +354,7 @@ ingame:
title: Gyártva
description: Az összes eddig legyártott alakzatod, beleértve a köztes alakzatokat is.
delivered:
title: Kézbesítve
title: Beszállítva
description: Az összes alakzat, amely jelenleg kézbesítés alatt van a központba.
noShapesProduced: Még nem gyártottál egy alazkatot sem.
@ -364,8 +365,8 @@ ingame:
settingsMenu:
playtime: Játékidő
buildingsPlaced: Épület
beltsPlaced: Futószalag
buildingsPlaced: Épületek száma:
beltsPlaced: Futószalagok hossza:
buttons:
continue: Folytatás
@ -406,7 +407,7 @@ ingame:
blue: Kék
yellow: Sárga
purple: Lila
cyan: Cián
cyan: Világoskék
white: Fehér
uncolored: Színezetlen
black: Fekete
@ -434,91 +435,91 @@ shopUpgrades:
buildings:
belt:
default:
name: &belt Conveyor Belt
description: Transports items, hold and drag to place multiple.
name: &belt Futószalag
description: Elemeket szállít, tartsd nyomva az egérgombot egyszerre több lerakásához.
miner: # Internal name for the Extractor
default:
name: &miner Extractor
description: Place over a shape or color to extract it.
name: &miner Bányász
description: Tedd egy alakzatra vagy színre a kibányászásához.
chainable:
name: Extractor (Chain)
description: Place over a shape or color to extract it. Can be chained.
name: Bányász (összekapcsolható)
description: Tedd egy alakzatra vagy színre a kibányászásához. Több egymáshoz kapcsolható.
underground_belt: # Internal name for the Tunnel
default:
name: &underground_belt Tunnel
description: Allows to tunnel resources under buildings and belts.
name: &underground_belt Alagút
description: Segítségével futószalagok és épületek alatt átvezethetők az elemek.
tier2:
name: Tunnel Tier II
description: Allows to tunnel resources under buildings and belts.
name: Alagút II
description: Segítségével futószalagok és épületek alatt átvezethetők az elemek.
splitter: # Internal name for the Balancer
default:
name: &splitter Balancer
description: Multifunctional - Evenly distributes all inputs onto all outputs.
name: &splitter Elosztó
description: Többfunkciós - Egyenletesen szétosztja a bementeket a kimenetekre.
compact:
name: Merger (compact)
description: Merges two conveyor belts into one.
name: Egyesítő (kompakt)
description: Két futószalagot egyesít.
compact-inverse:
name: Merger (compact)
description: Merges two conveyor belts into one.
name: Egyesítő (kompakt)
description: Két futószalagot egyesít.
cutter:
default:
name: &cutter Cutter
description: Cuts shapes from top to bottom and outputs both halfs. <strong>If you use only one part, be sure to destroy the other part or it will stall!</strong>
name: &cutter Vágó
description: Függőlegesen félbevágja az alakzatokat. <strong>Ha csak az egyik felet akarod használni, ne felejtsd el a másikat kukába küldeni, különben eldugítja!</strong>
quad:
name: Cutter (Quad)
description: Cuts shapes into four parts. <strong>If you use only one part, be sure to destroy the other part or it will stall!</strong>
name: Vágó (negyedelő)
description: Négyfelé vágja az alakzatokat. Cuts shapes into four parts. <strong>Ha csak az egyik felet akarod használni, ne felejtsd el a többit a kukába küldeni, különben eldugítja!</strong>
rotater:
default:
name: &rotater Rotate
description: Rotates shapes clockwise by 90 degrees.
name: &rotater Forgató
description: Elforgatja az alakzatot óramutató irányában 90 fokkal.
ccw:
name: Rotate (CCW)
description: Rotates shapes counter clockwise by 90 degrees.
name: Forgató (fordított)
description: Elforgatja az alakzatot óramutatóval ellentétesen 90 fokkal.
fl:
name: Rotate (180)
description: Rotates shapes by 180 degrees.
name: Forgató (180)
description: Elforgatja az alakzatot 180 fokkal.
stacker:
default:
name: &stacker Stacker
description: Stacks both items. If they can not be merged, the right item is placed above the left item.
name: &stacker Egyesítő
description: Egyesít két elemet. Ha nem lehet összeilleszteni őket, a jobboldali elem a baloldali tetejére kerül.
mixer:
default:
name: &mixer Color Mixer
description: Mixes two colors using additive blending.
name: &mixer Színkeverő
description: Összekever két színt összeadó színkeveréssel.
painter:
default:
name: &painter Painter
description: &painter_desc Colors the whole shape on the left input with the color from the right input.
name: &painter Festő
description: &painter_desc Beszínezi az alakzatot a baloldali bemeneten a jobboldali bemeneten érkező színnel.
double:
name: Painter (Double)
description: Colors the shapes on the left inputs with the color from the top input.
name: Festő (Dupla)
description: Beszínezi az alakzatokat a baloldali bemeneteken a fenti bemeneten érkező színnel.
quad:
name: Painter (Quad)
description: Allows to color each quadrant of the shape with a different color.
name: Festő (Négyszeres)
description: Az alakzat négy negyedét különböző színekkel lehet vele színezni.
mirrored:
name: *painter
description: *painter_desc
trash:
default:
name: &trash Trash
description: Accepts inputs from all sides and destroys them. Forever.
name: &trash Kuka
description: Bármelyik irányból lehet hozzá csatlakozni, és megsemmisíti a beleküldött elemeket. Örökre.
storage:
name: Storage
description: Stores excess items, up to a given capacity. Can be used as an overflow gate.
name: Tároló
description: Tárolja a fölös elemeket egy bizonyos kapacitásig.
hub:
deliver: Deliver
toUnlock: to unlock

View File

@ -411,7 +411,7 @@ ingame:
cyan: シアン
white:
uncolored: 無色
black: Black
black:
shapeViewer:
title: レイヤー
empty:
@ -491,8 +491,8 @@ buildings:
name: 回転機 (逆)
description: 形を反時計回り方向に90度回転します。
fl:
name: Rotate (180)
description: Rotates shapes by 180 degrees.
name: 回転機 (180)
description: 形を180度回転します。
stacker:
default:
@ -533,20 +533,20 @@ buildings:
advanced_processor:
default:
name: Color Inverter
description: Accepts a color or shape and inverts it.
description: 入力された色や形の色を反転します。
energy_generator:
deliver: Deliver
toGenerateEnergy: For
default:
name: Energy Generator
description: Generates energy by consuming shapes.
name: エネルギー発電機
description: 入力された形を使って、エネルギーを発電します。
wire_crossings:
default:
name: Wire Splitter
description: Splits a energy wire into two.
name: ワイヤー分配機
description: 1つのワイヤーを2つのワイヤーに分配します。
merger:
name: Wire Merger
description: Merges two energy wires into one.
name: ワイヤー合流機
description: 2つのワイヤーを1つのワイヤーに合流します。
storyRewards:
# Those are the rewards gained from completing the store

View File

@ -23,6 +23,9 @@ steamPage:
# This is the short text appearing on the steam page
shortText: shapez.io é um jogo sobre construir fábricas, automatizando a criação e combinação de formas cada vez mais complexas num mapa infinito.
# This is the text shown above the Discord link
discordLink: Discord Oficial - Converse comigo!
# This is the long description for the steam page - It is contained here so you can help to translate it, and I will regulary update the store page.
# NOTICE:
# - Do not translate the first line (This is the gif image at the start of the store)
@ -31,6 +34,7 @@ steamPage:
[img]{STEAM_APP_IMAGE}/extras/store_page_gif.gif[/img]
shapez.io é um jogo sobre construir fábricas, automatizando a criação e combinação de formas cada vez mais complexas num mapa infinito.
Após a entrega das formas requisitadas você progredirá no jogo e desbloqueará melhorias para acelerar sua fábrica.
Conforme sua demanda por formas aumenta, você irá que aumentar sua fábrica para alcançar-la - Mas não se esqueça dos recursos, você precisará expandir pelo [b]mapa infinito[/b]!
@ -82,8 +86,6 @@ steamPage:
[*] [url=https://github.com/tobspr/shapez.io/blob/master/translations/README.md]Ajude a traduzir[/url]
[/list]
discordLink: Discord Oficial - Converse comigo!
global:
loading: Carregando
error: Erro
@ -134,14 +136,19 @@ demoBanners:
# This is the "advertisement" shown in the main menu and other various places
title: Versão Demo
intro: >-
Pegue a versão completa para desbloquear todas os recursos
Pegue a versão completa para desbloquear todas os recursos!
mainMenu:
play: Jogar
continue: Continuar
newGame: Novo jogo
changelog: Changelog
subreddit: Reddit
importSavegame: Importar
openSourceHint: Esse jogo tem código aberto!
discordLink: Discord oficial
helpTranslate: Ajude a traduzir!
madeBy: Feito por <author-link>
# This is shown when using firefox and other browsers which are not supported.
browserWarning: >-
@ -150,12 +157,6 @@ mainMenu:
savegameLevel: Nível <x>
savegameLevelUnknown: Nível desconhecido
helpTranslate: Ajude a traduzir!
continue: Continuar
newGame: Novo jogo
madeBy: Feito por <author-link>
subreddit: Reddit
dialogs:
buttons:
ok: OK
@ -163,11 +164,11 @@ dialogs:
cancel: Cancelar
later: Voltar
restart: Reiniciar
reset: Reset
reset: Resetar
getStandalone: Obter versão completa
deleteGame: Sei o que estou fazendo
viewUpdate: Atualizações
showUpgrades: Ver melhorias
showUpgrades: Melhorias
showKeybindings: Controles
importSavegameError:
@ -198,7 +199,7 @@ dialogs:
restartRequired:
title: Ação necessária
text: >-
Voce precisa reiniciar o jogo para aplicar as mudanças.
Você precisa reiniciar o jogo para aplicar as mudanças.
editKeybinding:
title: Alterar tecla
@ -228,7 +229,7 @@ dialogs:
upgradesIntroduction:
title: Desbloquear melhorias
desc: >-
Todas as formas que você produz podem ser usadas para desbloquear melhorias - <strong> Não destrua suas antigas fábricas!!</strong>
Todas as formas que você produz podem ser usadas para desbloquear melhorias - <strong>Não destrua suas antigas fábricas!!</strong>
O guia de melhorias pode ser encontrado no canto superior direito da tela.
massDeleteConfirm:
@ -236,6 +237,16 @@ dialogs:
desc: >-
Você está deletando vários objetos (<count> para ser exato)! Você quer continuar?
massCutConfirm:
title: Confirmar corte
desc: >-
Você está cortando vários objetos (<count> para ser exato)! Você quer continuar?
massCutInsufficientConfirm:
title: Confirmar Corte
desc: >-
You can not afford to paste this area! Are you sure you want to cut it?
blueprintsNotUnlocked:
title: Não desbloqueado ainda
desc: >-
@ -245,42 +256,31 @@ dialogs:
title: Teclas úteis
desc: >-
Este jogo possui muitas combinações de teclas que facilitam a construção de grandes fábricas
Aqui estão algumas, certifique-se de <strong> verificar as combinações de teclas </strong>!<br><br>
Aqui estão algumas, certifique-se de <strong>verificar as combinações de teclas</strong>!<br><br>
<code class='keybinding'>CTRL</code> + Arrastar: Seleciona área para copiar/deletar.<br>
<code class='keybinding'>SHIFT</code>: Mantenha pressionado para colocar várias construções.<br>
<code class='keybinding'>ALT</code>: Inverte as posições.<br>
createMarker:
title: Nova Marcação
desc: Dê um nome com significado, também pode adicionar <strong>um pequeno código</strong> de uma forma. (Pode ser gerado <a href="https://viewer.shapez.io" target="_blank">aqui</a>)
titleEdit: Editar Marcador
desc: Dê um nome com significado, também pode adicionar <strong>um pequeno código</strong> de uma forma. (Pode ser gerado <a href="https://viewer.shapez.io" target="_blank">aqui</a>)
markerDemoLimit:
desc: >-
Você só pode criar dois marcadores na versão demo. Adquira a versão completa para marcadores ilimitados!
massCutConfirm:
title: Confirmar corte
desc: >-
Você está cortando vários objetos (<count> para ser exato)! Você quer continuar?
desc: Você só pode criar dois marcadores na versão demo. Adquira a versão completa para marcadores ilimitados!
exportScreenshotWarning:
title: Exportar captura de tela
desc: >-
Você está prestes a exportar uma captura de tela da sua base. Note que isso pode ser bastante lento para uma base grande, e até mesmo pode travar o jogo!
massCutInsufficientConfirm:
title: Confirmar Corte
desc: You can not afford to paste this area! Are you sure you want to cut it?
desc: Você está prestes a exportar uma captura de tela da sua base. Note que isso pode ser bastante lento para uma base grande, e até mesmo pode travar o jogo!
ingame:
# This is shown in the top left corner and displays useful keybindings in
# every situation
keybindingsOverlay:
moveMap: Mover
selectBuildings: Selecionar área
stopPlacement: Parar
rotateBuilding: Rotação
rotateBuilding: Rotacionar
placeMultiple: Colocar vários
reverseOrientation: Inverter orientação
disableAutoOrientation: Desligar orientação automática
@ -288,17 +288,27 @@ ingame:
placeBuilding: Construir objeto
createMarker: Criar marcador
delete: Destruir
selectBuildings: Selecionar área
pasteLastBlueprint: Colar último projeto
lockBeltDirection: Ativar Planejador de Esteiras
plannerSwitchSide: Flip planner side
plannerSwitchSide: Girar Planejador
cutSelection: Cortar
copySelection: Copiar
clearSelection: Limpar Seleção
pipette: Conta-Gotas
switchLayers: Trocar Camadas
# Names of the colors, used for the color blind mode
colors:
red: Vermelho
green: Verde
blue: Azul
yellow: Amarelo
purple: Roxo
cyan: Ciano
white: Branco
black: Preto
uncolored: Sem cor
# Everything related to placing buildings (I.e. as soon as you selected a building
# from the toolbar)
buildingPlacement:
@ -324,7 +334,7 @@ ingame:
levelCompleteNotification:
# <level> is replaced by the actual level, so this gets 'Level 03' for example.
levelTitle: Nível <level>
completed: Completado
completed: Concluído
unlockText: Desbloqueado <reward>!
buttonNextLevel: Próximo Nível
@ -336,13 +346,14 @@ ingame:
# The "Upgrades" window
shop:
title: Melhorias
buttonUnlock: Comprar
buttonUnlock: Melhorar
# Gets replaced to e.g. "Tier IX"
tier: Nível <x>
# The roman number for each tier
tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X]
maximumLevel: NÍVEL MÁXIMO (Velocidade x<currentMult>)
# The "Statistics" window
@ -389,60 +400,58 @@ ingame:
waypoints:
waypoints: Marcadores
hub: HUB
description: Clique com o botão esquerdo do mouse em um marcador para pular, clique com o botão direito do mouse para excluí-lo. <br> <br> Pressione <keybinding> para criar um marcador a partir da exibição atual ou <strong> clique com o botão direito do mouse </strong> para criar um marcador no local selecionado.
description: Clique com o botão esquerdo do mouse em um marcador para pular, clique com o botão direito do mouse para excluí-lo. <br><br> Pressione <keybinding> para criar um marcador a partir da exibição atual ou <strong>clique com o botão direito do mouse</strong> para criar um marcador no local selecionado.
creationSuccessNotification: Marcador criado.
# Interactive tutorial
interactiveTutorial:
title: Tutorial
hints:
1_1_extractor: Coloque um <strong> extrator </strong> em cima de uma <strong> fonte de círculo </strong> para extraí-lo!
1_2_conveyor: >-
Conecte o extrator com uma <strong> esteira transportadora </strong> até a sua base! <br><br> Dica, <strong>clique e arraste</strong> a esteira com o mouse!
1_3_expand: >-
Este <strong> NÃO </strong> é um jogo inativo! Construa mais extratores e esteiras para concluir o objetivo mais rapidamente.<br><br>Dica, segure <strong> SHIFT </strong> para colocar vários extratores e use <strong> R </strong> para girá-los.
colors:
red: Vermelho
green: Verde
blue: Azul
yellow: Amarelo
purple: Roxo
cyan: Ciano
white: Branco
black: Preto
uncolored: Sem cor
# Shape viewer
shapeViewer:
title: Camadas
empty: Vazio
copyKey: Copiar Chave
# Interactive tutorial
interactiveTutorial:
title: Tutorial
hints:
1_1_extractor: Coloque um <strong>extrator</strong> em cima de uma <strong>fonte de círculo</strong> para extraí-lo!
1_2_conveyor: >-
Conecte o extrator com uma <strong>esteira transportadora</strong> até a sua base!<br><br>Dica, <strong>clique e arraste</strong> a esteira com o mouse!
1_3_expand: >-
Este <strong>NÃO</strong> é um jogo inativo! Construa mais extratores e esteiras para concluir o objetivo mais rapidamente.<br><br>Dica, segure <strong>SHIFT</strong> para colocar vários extratores e use <strong>R</strong> para girá-los.
# All shop upgrades
shopUpgrades:
belt:
name: Esteiras, Distribuidores e Túneis
description: Velocidade x<currentMult> → x<newMult>
miner:
name: Extração
description: Velocidade x<currentMult> → x<newMult>
processors:
name: Corte, Rotação & Montagem
name: Corte, Rotação e Montagem
description: Velocidade x<currentMult> → x<newMult>
painting:
name: Mistura & Pintura
name: Mistura e Pintura
description: Velocidade x<currentMult> → x<newMult>
# Buildings and their name / description
buildings:
hub:
deliver: Entregue
toUnlock: para desbloquear
levelShortcut: LVL
belt:
default:
name: &belt Esteira Transportadora
description: Transporta itens; mantenha pressionado e arraste para colocar vários.
wire:
default:
name: &wire Fio de Energia
description: Permite transportar energia.
miner: # Internal name for the Extractor
default:
name: &miner Extrator
@ -477,10 +486,15 @@ buildings:
cutter:
default:
name: &cutter Cortador
description: Corta as formas verticalmente e produz as duas metades. <strong> Se você usar apenas uma parte, não se esqueça de destruir a outra parte, ou ela irá parar a produção! </strong>
description: Corta as formas verticalmente e produz as duas metades. <strong>Se você usar apenas uma parte, não se esqueça de destruir a outra parte, ou ela irá parar a produção!</strong>
quad:
name: Cortador (Quádruplo)
description: Corta as formas em quatro partes. <strong> Se você usar apenas uma parte, não se esqueça de destruir as outras, ou ela irá parar a produção! </strong>
description: Corta as formas em quatro partes. <strong>Se você usar apenas uma parte, não se esqueça de destruir as outras, ou ela irá parar a produção!</strong>
advanced_processor:
default:
name: &advanced_processor Inversor de Cor
description: Aceita uma cor ou forma e a inverte.
rotater:
default:
@ -490,8 +504,8 @@ buildings:
name: Rotacionador (Anti-horário)
description: Gira as formas no sentido anti-horário em 90 graus.
fl:
name: Rotate (180)
description: Rotates shapes by 180 degrees.
name: Rotacionador (180)
description: Gira as formas em 180 graus.
stacker:
default:
@ -507,16 +521,18 @@ buildings:
default:
name: &painter Pintor
description: &painter_desc Colore a forma inteira na entrada esquerda com a cor da entrada direita.
double:
name: Pintor (Duplo)
description: Colore as duas formas na entrada esquerda com a cor da entrada direita.
quad:
name: Pintor (Quádruplo)
description: Permite colorir cada quadrante da forma com uma cor diferente.
mirrored:
name: *painter
description: *painter_desc
double:
name: Pintor (Duplo)
description: Colore as formas na entrada esquerda com a cor da entrada superior.
quad:
name: Pintor (Quádruplo)
description: Permite colorir cada quadrante da forma com uma cor diferente.
trash:
default:
name: &trash Lixo
@ -525,29 +541,22 @@ buildings:
storage:
name: Estoque
description: Armazena itens em excesso, até uma determinada capacidade. Pode ser usado como uma porta de transbordamento.
hub:
deliver: Entregue
toUnlock: para desbloquear
levelShortcut: LVL
wire:
default:
name: &wire Fio de Energia
description: Permite transportar energia.
advanced_processor:
default:
name: &advanced_processor Inversor de Cor
description: Aceita uma cor ou forma e a inverte.
energy_generator:
deliver: Entregar
# This will be shown before the amount, so for example 'For 123 Energy'
toGenerateEnergy: Para
default:
name: &energy_generator Gerador de Energia
description: Consome formas para gerar energia.
wire_crossings:
default:
name: &wire_crossings Divisor de Fios
description: Divide um fio de energia em dois.
merger:
name: Misturador de Fios
description: Une dois fios de energia em um.
@ -556,11 +565,11 @@ storyRewards:
# Those are the rewards gained from completing the store
reward_cutter_and_trash:
title: Cortando formas
desc: Voce desbloqueou <strong>cortador</strong> - corte de formas pela metade <strong>verticalmente</strong> independentemente de sua orientação!<br><br> Certifique-se de se livrar do lixo, ou então <strong> ele irá parar a produção </strong> - Para esse propósito, eu lhe dei uma lixeira, que destrói tudo o que você coloca nela!
desc: Você desbloqueou <strong>cortador</strong> - corte de formas pela metade <strong>verticalmente</strong> independentemente de sua orientação!<br><br> Certifique-se de se livrar do lixo, ou então <strong>ele irá parar a produção</strong> - Para esse propósito, eu lhe dei uma lixeira, que destrói tudo o que você coloca nela!
reward_rotater:
title: Rotação
desc: O <strong> rotacionador </strong> foi desbloqueado! Gira as formas no sentido horário em 90 graus.
desc: O <strong>rotacionador</strong> foi desbloqueado! Gira as formas no sentido horário em 90 graus.
reward_painter:
title: Pintura
@ -569,11 +578,11 @@ storyRewards:
reward_mixer:
title: Misturando cores
desc: O <strong> misturador </strong> foi desbloqueado - combine duas cores usando <strong>mistura aditiva</strong> com esta construção!
desc: O <strong>misturador</strong> foi desbloqueado - combine duas cores usando <strong>mistura aditiva</strong> com esta construção!
reward_stacker:
title: Empilhador
desc: Agora você pode combinar formas com o <strong>empilhador</strong>! Ambas as entradas são combinadas e, se puderem ser colocadas próximas uma da outra, serão <strong> fundidas </strong>. Caso contrário, a entrada direita é <strong>empilhada em cima</strong> da entrada esquerda!
desc: Agora você pode combinar formas com o <strong>empilhador</strong>! Ambas as entradas são combinadas e, se puderem ser colocadas próximas uma da outra, serão <strong>fundidas</strong>. Caso contrário, a entrada direita é <strong>empilhada em cima</strong> da entrada esquerda!
reward_splitter:
title: Distribuidor
@ -597,7 +606,8 @@ storyRewards:
reward_splitter_compact:
title: Distribuidor compacto
desc: Você desbloqueou uma variante compacta do <strong>Distribuidor</strong> - ele aceita duas entradas e as une em uma!
desc: >-
Você desbloqueou uma variante compacta do <strong>Distribuidor</strong> - ele aceita duas entradas e as une em uma!
reward_cutter_quad:
title: Cortador quádruplo
@ -605,7 +615,7 @@ storyRewards:
reward_painter_double:
title: Pintura dupla
desc: Você desbloqueou uma variante do <strong>pintor</strong> - funciona como o pintor regular, mas processa <strong> duas formas ao mesmo tempo </strong>, consumindo apenas uma cor em vez de duas!
desc: Você desbloqueou uma variante do <strong>pintor</strong> - funciona como o pintor regular, mas processa <strong>duas formas ao mesmo tempo</strong>, consumindo apenas uma cor em vez de duas!
reward_painter_quad:
title: Pintura quádrupla
@ -637,9 +647,9 @@ storyRewards:
settings:
title: opções
categories:
general: General
userInterface: User Interface
advanced: Advanced
general: Geral
userInterface: Interface de Usuário
advanced: Avançado
versionBadges:
dev: Desenvolvedor
@ -653,23 +663,58 @@ settings:
description: >-
Altera o tamanho da fonte do usuário. A interface ainda será dimensionada com base na resolução do dispositivo, mas essa configuração controla a escala do texto.
scales:
super_small: Super pequeno
super_small: Super Pequeno
small: Pequeno
regular: Normal
large: Grande
huge: Gigante
autosaveInterval:
title: Intervalo de gravação automática
description: >-
Controla a frequência com que o jogo salva automaticamente. Você também pode desativá-lo totalmente aqui.
intervals:
one_minute: 1 Minuto
two_minutes: 2 Minutos
five_minutes: 5 Minutos
ten_minutes: 10 Minutos
twenty_minutes: 20 Minutos
disabled: Desativado
scrollWheelSensitivity:
title: Sensibilidade do zoom
description: >-
Altera a sensibilidade do zoom (roda do mouse ou touchpad).
sensitivity:
super_slow: Super lento
super_slow: Super Lento
slow: Lento
regular: Normal
fast: Rápido
super_fast: Super Rápido
movementSpeed:
title: Velocidade da câmera
description: >-
Altera a velocidade com que a câmera se move com o teclado.
speeds:
super_slow: Super Lento
slow: Lento
regular: Normal
fast: Rápido
super_fast: Super Rápido
extremely_fast: Extremamente Rápido
language:
title: Idioma
description: >-
Altera o idioma. Todas as traduções são contribuições de usuários e podem estar incompletas!
enableColorBlindHelper:
title: Modo daltônico.
description: >-
Permite várias ferramentas que permitem jogar se você é daltônico.
fullscreen:
title: Tela Cheia
description: >-
@ -678,7 +723,7 @@ settings:
soundsMuted:
title: Som
description: >-
Se ligado o jogo fica mudo
Se ligado, o jogo fica mudo.
musicMuted:
title: Música
@ -689,7 +734,6 @@ settings:
title: Tema
description: >-
Escolha o tema entre (Claro / Escuro).
themes:
dark: Escuro
light: Claro
@ -709,63 +753,30 @@ settings:
description: >-
Se ativado, oferece dicas e tutoriais enquanto se joga. Além disso, esconde certos elementos da interface até certo ponto, para facilitar o começo do jogo.
language:
title: Idioma
description: >-
Altera o idioma. Todas as traduções são contribuições de usuários e podem estar incompletas!
movementSpeed:
title: Velocidade da câmera
description: Altera a velocidade com que a câmera se move com o teclado.
speeds:
super_slow: Super Lento
slow: Lento
regular: Normal
fast: Rápido
super_fast: Super Rápido
extremely_fast: Extremamente Rápido
enableTunnelSmartplace:
title: Túneis inteligentes
description: >-
Quando colocados, irão remover automaticamente esteiras desnecessárias.
Isso também permite arrastar túneis e túneis em excesso serão removidos.
Quando colocados, irão remover automaticamente esteiras desnecessárias. Isso também permite arrastar túneis e túneis em excesso serão removidos.
vignette:
title: Vignette
title: Vinheta
description: >-
Permite o modo vinheta que escurece os cantos da tela e facilita a
leitura do texto.
Permite o modo vinheta que escurece os cantos da tela e facilita a leitura do texto.
autosaveInterval:
title: Intervalo de gravação automática
description: >-
Controla a frequência com que o jogo salva automaticamente.
Você também pode desativá-lo totalmente aqui.
intervals:
one_minute: 1 Minuto
two_minutes: 2 Minutos
five_minutes: 5 Minutos
ten_minutes: 10 Minutos
twenty_minutes: 20 Minutos
disabled: Desativado
compactBuildingInfo:
title: Informações compactas sobre construções
description: >-
Reduz as caixas de informações dos construções, mostrando apenas suas proporções.
Caso contrário, uma descrição e imagem são mostradas.
disableCutDeleteWarnings:
title: Desativar avisos de recorte / exclusão
description: >-
Desative as caixas de diálogo de aviso exibidas ao cortar / excluir
mais de 100 entidades.
enableColorBlindHelper:
title: Modo daltônico.
description: Permite várias ferramentas que permitem jogar se você é daltônico.
rotationByBuilding:
title: Rotação por tipo de construção
description: >-
Cada tipo de construção lembra a rotação que você definiu pela última vez individualmente.
Isso pode ser mais confortável se você alternar frequentemente entre a colocação de diferentes tipos de construção.
Cada tipo de construção lembra a rotação que você definiu pela última vez individualmente. Isso pode ser mais confortável se você alternar frequentemente entre a colocação de diferentes tipos de construção.
compactBuildingInfo:
title: Informações compactas sobre construções
description: >-
Reduz as caixas de informações dos construções, mostrando apenas suas proporções. Caso contrário, uma descrição e imagem são mostradas.
disableCutDeleteWarnings:
title: Desativar avisos de recorte / exclusão
description: >-
Desative as caixas de diálogo de aviso exibidas ao cortar / excluir mais de 100 entidades.
keybindings:
title: Controles
@ -790,6 +801,7 @@ keybindings:
mapMoveRight: Mover para direita
mapMoveDown: Mover para baixo
mapMoveLeft: Mover para a esquerda
mapMoveFaster: Mover mais rápido
centerMap: Centralizar mapa
mapZoomIn: Aproximar
@ -798,12 +810,12 @@ keybindings:
menuOpenShop: Melhorias
menuOpenStats: Estatísticas
menuClose: Fechar Menu
toggleHud: Ocultar Interface
toggleFPSInfo: Mostrar FPS e Debug Info
switchLayers: Alternar Camadas
exportScreenshot: Exportar Base inteira como Imagem
exportScreenshot: Exportar Base Inteira como Imagem
belt: *belt
splitter: *splitter
underground_belt: *underground_belt
@ -824,41 +836,33 @@ keybindings:
Modifier: Rotação anti-horária
cycleBuildingVariants: Variações
confirmMassDelete: Confirmar exclusão em massa
pasteLastBlueprint: Colar último projeto
cycleBuildings: Trocar de construção
lockBeltDirection: Ativar planejador de correia
switchDirectionLockSide: >-
Planejador: Mudar de lado
massSelectStart: Segure e arraste para começar
massSelectSelectMultiple: Selecionar mais áreas
massSelectCopy: Copiar área
massSelectCut: Cortar área
placementDisableAutoOrientation: Desligar orientação automática
placeMultiple: Permanecer no modo de construção
placeInverse: Inverter orientação de esteira
pasteLastBlueprint: Colar último projeto
massSelectCut: Cortar área
mapMoveFaster: Mover mais rápido
lockBeltDirection: Ativar planejador de correia
switchDirectionLockSide: "Planejador: Mudar de lado"
menuClose: Fechar Menu
about:
title: Sobre o jogo
body: >-
Esse jogo tem código aberto e é desenvolvido por <a href="https://github.com/tobspr"
target="_blank">Tobias Springer</a> (esse sou eu).<br><br>
Esse jogo tem código aberto e é desenvolvido por <a href="https://github.com/tobspr" target="_blank">Tobias Springer</a> (esse sou eu).<br><br>
Se quiser contribuir, confira <a href="<githublink>"
target="_blank">shapez.io no github</a>.<br><br>
Se quiser contribuir, confira <a href="<githublink>" target="_blank">shapez.io no github</a>.<br><br>
O jogo não seria possível sem a comunidade incrível do Discord sobre
os meus jogos - Junte-se à comunidade no <a href="<discordlink>"
target="_blank">servidor do Discord</a>!<br><br>
O jogo não seria possível sem a comunidade incrível do Discord sobre os meus jogos - Junte-se à comunidade no <a href="<discordlink>" target="_blank">servidor do Discord</a>!<br><br>
A trilha sonora foi feita por <a href="https://soundcloud.com/pettersumelius"
target="_blank">Peppsen</a> - Ele é demais.<br><br>
A trilha sonora foi feita por <a href="https://soundcloud.com/pettersumelius" target="_blank">Peppsen</a> - Ele é demais.<br><br>
Finalmente, agradeço muito ao meu melhor amigo
<a href="https://github.com/niklas-dahl" target="_blank">Niklas</a> - Sem nossas sessões de Factorio,
esse jogo nunca teria existido.
Finalmente, agradeço muito ao meu melhor amigo <a href="https://github.com/niklas-dahl" target="_blank">Niklas</a> - Sem nossas sessões de Factorio, esse jogo nunca teria existido.
changelog:
title: Alterações

View File

@ -488,8 +488,8 @@ buildings:
name: Вращатель (Обр.)
description: Поворачивает фигуры против часовой стрелки на 90 градусов.
fl:
name: Rotate (180)
description: Rotates shapes by 180 degrees.
name: Вращатель (180)
description: Вращает фигуры на 180 градусов.
stacker:
default:
@ -635,9 +635,9 @@ storyRewards:
settings:
title: Настройки
categories:
general: General
userInterface: User Interface
advanced: Advanced
general: Основные
userInterface: Интерфейс
advanced: Продвинутые
versionBadges:
dev: Разработчик

View File

@ -176,7 +176,7 @@ mainMenu:
# This is shown when using firefox and other browsers which are not supported.
browserWarning: >-
很抱歉, 本游戏在当前浏览器上可能运行缓慢! 使用chrome或者获取独立版以得到更好的体验。
很抱歉, 本游戏在当前浏览器上可能运行缓慢! 使用 Chrome 或者获取独立版以得到更好的体验。
savegameLevel: 第<x>关
savegameLevelUnknown:
@ -232,7 +232,7 @@ dialogs:
editKeybinding:
title: 更改按键设置
desc: 请按下你想要使用的按键,或者按下ESC键来取消设置。
desc: 请按下你想要使用的按键,或者按下 ESC 键来取消设置。
resetKeybindingsConfirmation:
title: 重置所有按键
@ -244,11 +244,11 @@ dialogs:
featureRestriction:
title: 演示版
desc: 你尝试使用了 <feature> 功能。该功能在演示版中不可用。请考虑购买独立版以获得更好的体验。
desc: 你尝试使用了<feature>功能。该功能在演示版中不可用。请考虑购买独立版以获得更好的体验。
oneSavegameLimit:
title: 存档数量限制
desc: 演示版中只能保存一份存档。 请删除旧存档或者获取独立版!
desc: 演示版中只能保存一份存档。请删除旧存档或者获取独立版!
updateSummary:
title: 更新啦!
@ -314,7 +314,7 @@ ingame:
placeMultiple: 放置多个
reverseOrientation: 反向放置
disableAutoOrientation: 关闭自动定向
toggleHud: 开关HUD
toggleHud: 开关 HUD
placeBuilding: 放置建筑
createMarker: 创建地图标记
delete: 销毁
@ -332,7 +332,7 @@ ingame:
buildingPlacement:
# Buildings can have different variants which are unlocked at later levels,
# and this is the hint shown when there are multiple variants available.
cycleBuildingVariants: <key>键以选择建筑变体.
cycleBuildingVariants: <key> 键以选择建筑变体.
# Shows the hotkey in the ui, e.g. "Hotkey: Q"
hotkeyLabel: >-
@ -391,7 +391,7 @@ ingame:
noShapesProduced: 你还没有生产任何图形。
# Displays the shapes per minute, e.g. '523 / m'
shapesPerMinute: <shapes>个/分钟
shapesPerMinute: <shapes>个 / 分钟
# Settings menu, when you press "ESC"
settingsMenu:
@ -419,7 +419,7 @@ ingame:
waypoints:
waypoints: 地图标记
hub: 基地
description: 左键跳转到地图标记,右键删除地图标记。<br><br>按<keybinding>在当前地点创建地图标记,或者在选定位置上<strong>右键</strong>创建地图标记.
description: 左键跳转到地图标记,右键删除地图标记。<br><br>按 <keybinding> 在当前地点创建地图标记,或者在选定位置上<strong>右键</strong>创建地图标记.
creationSuccessNotification: 成功创建地图标记。
# Interactive tutorial
@ -432,7 +432,7 @@ ingame:
1_3_expand: >-
这<strong>不是</strong>一个挂机游戏!建造更多的开采机和传送带来更快地完成目标。<br><br>
提示:按住<strong>SHIFT</strong>键来放置多个开采机,用<strong>R</strong>键旋转它们。
提示:按住 <strong>SHIFT</strong> 键来放置多个开采机,用 <strong>R</strong> 键旋转它们。
colors:
red: 红色
@ -505,10 +505,10 @@ buildings:
cutter:
default:
name: &cutter 切割机
description: 将图形从上到下切开并输出。 <strong>如果你只需要其中一半,记得把另一半销毁掉,否则切割机会停止工作!</strong>
description: 将图形从上到下切开并输出。<strong>如果你只需要其中一半,记得把另一半销毁掉,否则切割机会停止工作!</strong>
quad:
name: 切割机(四向)
description: 将输入的图形切成四块。 <strong>如果你只需要其中一块,记得把其他的销毁掉,否则切割机会停止工作!</strong>
description: 将输入的图形切成四块。<strong>如果你只需要其中一块,记得把其他的销毁掉,否则切割机会停止工作!</strong>
rotater:
default:
@ -592,7 +592,7 @@ storyRewards:
reward_painter:
title: 上色
desc: >-
恭喜!你解锁了<strong>上色机</strong>。 开采一些颜色 (就像你开采图形一样) 将其在上色机中与图形结合来将图形上色!<br><br>PS: 如果你患有色盲,可以在设置中启用<strong>色盲模式</strong>
恭喜!你解锁了<strong>上色机</strong>。开采一些颜色 (就像你开采图形一样) 将其在上色机中与图形结合来将图形上色!<br><br>PS如果你患有色盲,可以在设置中启用<strong>色盲模式</strong>
reward_mixer:
title: 混合颜色
@ -612,7 +612,7 @@ storyRewards:
reward_rotater_ccw:
title: 逆时针旋转
desc: 恭喜!你解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。这个变体可以逆时针旋转图形。选择旋转机然后按“T”键来选取这个变体。
desc: 恭喜!你解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。这个变体可以逆时针旋转图形。选择旋转机然后按"T"键来选取这个变体。
reward_miner_chainable:
title: 链式开采机
@ -648,13 +648,13 @@ storyRewards:
reward_blueprints:
title: 蓝图
desc: 你现在可以<strong>复制粘贴</strong>你的工厂的一部分了!按住CTRL键并拖动鼠标来选择一块区域然后按C键复制。<br><br>粘贴并<strong>不是免费的</strong>,你需要使用<strong>蓝图图形</strong>来粘贴你的蓝图。蓝图图形是你刚刚交付的图形。
desc: 你现在可以<strong>复制粘贴</strong>你的工厂的一部分了!按住 CTRL 键并拖动鼠标来选择一块区域然后按C键复制。<br><br>粘贴并<strong>不是免费的</strong>,你需要使用<strong>蓝图图形</strong>来粘贴你的蓝图。蓝图图形是你刚刚交付的图形。
# Special reward, which is shown when there is no reward actually
no_reward:
title: 下一关
desc: >-
这一关没有奖励,但是下一关有!<br><br> PS: 你生产过的<strong>所有</strong>图形都会被用来<strong>升级建筑</strong>。
这一关没有奖励,但是下一关有!<br><br> PS你生产过的<strong>所有</strong>图形都会被用来<strong>升级建筑</strong>。
no_reward_freeplay:
title: 下一关
@ -729,21 +729,21 @@ settings:
refreshRate:
title: 模拟频率、刷新频率
description: >-
如果你的显示器是144hz的,请在这里更改刷新频率,这样游戏可以正确地根据你的屏幕进行模拟。但是如果你的电脑性能不佳,提高刷新频率可能降低帧数。
如果你的显示器是 144Hz 的,请在这里更改刷新频率,这样游戏可以正确地根据你的屏幕进行模拟。但是如果你的电脑性能不佳,提高刷新频率可能降低帧数。
# description: >-
# If you have a 144hz monitor, change the refresh rate here so the game will properly simulate at higher refresh rates. This might actually decrease the FPS if your computer is too slow.
alwaysMultiplace:
title: 多重放置
description: >-
开启这个选项之后放下建筑将不会取消建筑选择。等同于一直按下SHIFT键。
开启这个选项之后放下建筑将不会取消建筑选择。等同于一直按下 SHIFT 键。
# description: >-
# If enabled, all buildings will stay selected after placement until you cancel it. This is equivalent to holding SHIFT permanently.
offerHints:
title: 提示与教程
description: >-
是否显示提示、教程以及一些其他的帮助理解游戏的UI元素。
是否显示提示、教程以及一些其他的帮助理解游戏的 UI 元素。
# description: >-
# Whether to offer hints and tutorials while playing. Also hides certain UI elements onto a given level to make it easier to get into the game.
@ -799,7 +799,7 @@ settings:
keybindings:
title: 按键设置
hint: >-
提示:使用CTRL、SHIFT、ALT! 这些建在放置建筑时有不同的效果。
提示:使用 CTRL、SHIFT、ALT这些建在放置建筑时有不同的效果。
# hint: >-
# Tip: Be sure to make use of CTRL, SHIFT and ALT! They enable different placement options.
@ -881,20 +881,20 @@ about:
title: 关于游戏
# title: About this Game
body: >-
本游戏由<a href="https://github.com/tobspr"
本游戏由 <a href="https://github.com/tobspr"
target="_blank">Tobias Springer</a>(我)开发,并且已经开源。<br><br>
如果你想参与开发,请查看<a href="<githublink>"
如果你想参与开发,请查看 <a href="<githublink>"
target="_blank">shapez.io on github</a>。<br><br>
这个游戏的开发少不了热情的Discord社区。请加入我们的<a href="<discordlink>"
这个游戏的开发少不了热情的 Discord 社区。请加入我们的 <a href="<discordlink>"
target="_blank">Discord 服务器</a><br><br>
本游戏的音乐由<a href="https://soundcloud.com/pettersumelius"
target="_blank">Peppsen</a>制作——他是个很棒的伙伴。<br><br>
本游戏的音乐由 <a href="https://soundcloud.com/pettersumelius"
target="_blank">Peppsen</a> 制作——他是个很棒的伙伴。<br><br>
最后,我想感谢我最好的朋友<a
href="https://github.com/niklas-dahl" target="_blank">Niklas</a>——如果没有与他的异星工厂factorio的游戏体验shapez.io将不会存在。
最后,我想感谢我最好的朋友 <a
href="https://github.com/niklas-dahl" target="_blank">Niklas</a> ——如果没有与他的异星工厂factorio的游戏体验shapez.io将不会存在。
changelog:
title: 版本日志