Merge branch 'master' into patch-1
8
.editorconfig
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[{src, translations}/*]
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
charset = utf-8
|
||||||
39
Dockerfile
@ -1,30 +1,31 @@
|
|||||||
FROM node:12 as base
|
FROM node:12 as base
|
||||||
|
|
||||||
|
EXPOSE 3001 3005
|
||||||
|
|
||||||
WORKDIR /shapez.io
|
WORKDIR /shapez.io
|
||||||
|
|
||||||
COPY . .
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg default-jre \
|
||||||
EXPOSE 3005
|
&& apt-get clean \
|
||||||
EXPOSE 3001
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get upgrade -y \
|
|
||||||
&& apt-get dist-upgrade -y \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
ffmpeg \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
FROM base as shape_base
|
COPY package.json yarn.lock ./
|
||||||
|
RUN yarn
|
||||||
|
|
||||||
|
COPY gulp ./gulp
|
||||||
|
WORKDIR /shapez.io/gulp
|
||||||
|
RUN yarn
|
||||||
|
|
||||||
WORKDIR /shapez.io
|
WORKDIR /shapez.io
|
||||||
|
COPY res ./res
|
||||||
RUN yarn
|
COPY src/html ./src/html
|
||||||
|
COPY src/css ./src/css
|
||||||
|
COPY version ./version
|
||||||
|
COPY sync-translations.js ./
|
||||||
|
COPY translations ./translations
|
||||||
|
COPY src/js ./src/js
|
||||||
|
COPY res_raw ./res_raw
|
||||||
|
COPY .git ./.git
|
||||||
|
|
||||||
WORKDIR /shapez.io/gulp
|
WORKDIR /shapez.io/gulp
|
||||||
|
|
||||||
RUN yarn
|
|
||||||
|
|
||||||
WORKDIR /shapez.io/gulp
|
|
||||||
|
|
||||||
ENTRYPOINT ["yarn", "gulp"]
|
ENTRYPOINT ["yarn", "gulp"]
|
||||||
|
|||||||
@ -86,8 +86,16 @@ gulp.task("utils.cleanBuildTempFolder", () => {
|
|||||||
.src(path.join(__dirname, "..", "src", "js", "built-temp"), { read: false, allowEmpty: true })
|
.src(path.join(__dirname, "..", "src", "js", "built-temp"), { read: false, allowEmpty: true })
|
||||||
.pipe($.clean({ force: true }));
|
.pipe($.clean({ force: true }));
|
||||||
});
|
});
|
||||||
|
gulp.task("utils.cleanImageBuildFolder", () => {
|
||||||
|
return gulp
|
||||||
|
.src(path.join(__dirname, "res_built"), { read: false, allowEmpty: true })
|
||||||
|
.pipe($.clean({ force: true }));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task("utils.cleanup", gulp.series("utils.cleanBuildFolder", "utils.cleanBuildTempFolder"));
|
gulp.task(
|
||||||
|
"utils.cleanup",
|
||||||
|
gulp.series("utils.cleanBuildFolder", "utils.cleanImageBuildFolder", "utils.cleanBuildTempFolder")
|
||||||
|
);
|
||||||
|
|
||||||
// Requires no uncomitted files
|
// Requires no uncomitted files
|
||||||
gulp.task("utils.requireCleanWorkingTree", cb => {
|
gulp.task("utils.requireCleanWorkingTree", cb => {
|
||||||
@ -234,12 +242,13 @@ gulp.task(
|
|||||||
"build.standalone.dev",
|
"build.standalone.dev",
|
||||||
gulp.series(
|
gulp.series(
|
||||||
"utils.cleanup",
|
"utils.cleanup",
|
||||||
|
"imgres.buildAtlas",
|
||||||
|
"imgres.atlasToJson",
|
||||||
"imgres.atlas",
|
"imgres.atlas",
|
||||||
"sounds.dev",
|
"sounds.dev",
|
||||||
"imgres.copyImageResources",
|
"imgres.copyImageResources",
|
||||||
"imgres.copyNonImageResources",
|
"imgres.copyNonImageResources",
|
||||||
"translations.fullBuild",
|
"translations.fullBuild",
|
||||||
"js.standalone-dev",
|
|
||||||
"css.dev",
|
"css.dev",
|
||||||
"html.standalone-dev"
|
"html.standalone-dev"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -173,6 +173,8 @@ function gulptasksImageResources($, gulp, buildFolder) {
|
|||||||
gulp.task(
|
gulp.task(
|
||||||
"imgres.allOptimized",
|
"imgres.allOptimized",
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
|
"imgres.buildAtlas",
|
||||||
|
"imgres.atlasToJson",
|
||||||
"imgres.atlasOptimized",
|
"imgres.atlasOptimized",
|
||||||
"imgres.copyNonImageResources",
|
"imgres.copyNonImageResources",
|
||||||
"imgres.copyImageResourcesOptimized"
|
"imgres.copyImageResourcesOptimized"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 132 KiB |
BIN
res/ui/interactive_tutorial.noinline/2_1_place_cutter.gif
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
res/ui/interactive_tutorial.noinline/2_2_place_trash.gif
Normal file
|
After Width: | Height: | Size: 575 KiB |
BIN
res/ui/interactive_tutorial.noinline/2_3_more_cutters.gif
Normal file
|
After Width: | Height: | Size: 776 KiB |
BIN
res/ui/interactive_tutorial.noinline/3_1_rectangles.gif
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
@ -1,36 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
<path style="fill:#FF4B55;" d="M400,0H112C50.144,0,0,50.144,0,112v288c0,61.856,50.144,112,112,112h288 c61.856,0,112-50.144,112-112V112C512,50.144,461.856,0,400,0z"/>
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
<polygon style="fill:#F5F5F5;" points="512,229.517 211.862,229.517 211.862,0 158.897,0 158.897,229.517 0,229.517 0,282.483 158.897,282.483 158.897,512 211.862,512 211.862,282.483 512,282.483 "/>
|
<rect style="fill:#FF4B55;" width="512" height="512"/>
|
||||||
<g>
|
<polygon style="fill:#F5F5F5;" points="512,229.517 211.862,229.517 211.862,0 158.897,0 158.897,229.517 0,229.517 0,282.483
|
||||||
</g>
|
158.897,282.483 158.897,512 211.862,512 211.862,282.483 512,282.483 "/>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 786 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 8.5 KiB |
@ -27,7 +27,7 @@
|
|||||||
@include S(border-radius, $globalBorderRadius);
|
@include S(border-radius, $globalBorderRadius);
|
||||||
|
|
||||||
@include DarkThemeOverride {
|
@include DarkThemeOverride {
|
||||||
background-color: rgba(darken($darkModeGameBackground, 15), 0.4);
|
background-color: rgba(darken($darkModeGameBackground, 15), 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
|
|||||||
@ -17,13 +17,10 @@
|
|||||||
grid-template-rows: 1fr 1fr;
|
grid-template-rows: 1fr 1fr;
|
||||||
@include S(margin-bottom, 4px);
|
@include S(margin-bottom, 4px);
|
||||||
color: #333438;
|
color: #333438;
|
||||||
// text-shadow: #{D(1px)} #{D(1px)} 0 rgba(0, 10, 20, 0.2);
|
|
||||||
|
|
||||||
&.unpinable {
|
&.removable {
|
||||||
> canvas {
|
cursor: pointer;
|
||||||
cursor: pointer;
|
pointer-events: all;
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> canvas {
|
> canvas {
|
||||||
@ -31,16 +28,9 @@
|
|||||||
@include S(height, 25px);
|
@include S(height, 25px);
|
||||||
grid-column: 1 / 2;
|
grid-column: 1 / 2;
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
pointer-events: all;
|
pointer-events: none;
|
||||||
transition: transform 0.1s ease-in-out;
|
|
||||||
transform-origin: D(2px) center;
|
|
||||||
will-change: transform;
|
|
||||||
position: relative;
|
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
&:hover {
|
position: relative;
|
||||||
transform: scale(2);
|
|
||||||
z-index: 21;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .amountLabel,
|
> .amountLabel,
|
||||||
|
|||||||
@ -73,8 +73,8 @@ ingame_HUD_KeybindingOverlay,
|
|||||||
ingame_HUD_Notifications,
|
ingame_HUD_Notifications,
|
||||||
ingame_HUD_DebugInfo,
|
ingame_HUD_DebugInfo,
|
||||||
ingame_HUD_EntityDebugger,
|
ingame_HUD_EntityDebugger,
|
||||||
ingame_HUD_InteractiveTutorial,
|
|
||||||
ingame_HUD_TutorialHints,
|
ingame_HUD_TutorialHints,
|
||||||
|
ingame_HUD_InteractiveTutorial,
|
||||||
ingame_HUD_BuildingsToolbar,
|
ingame_HUD_BuildingsToolbar,
|
||||||
ingame_HUD_wires_toolbar,
|
ingame_HUD_wires_toolbar,
|
||||||
ingame_HUD_BlueprintPlacer,
|
ingame_HUD_BlueprintPlacer,
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { MobileWarningState } from "./states/mobile_warning";
|
|||||||
import { PreloadState } from "./states/preload";
|
import { PreloadState } from "./states/preload";
|
||||||
import { SettingsState } from "./states/settings";
|
import { SettingsState } from "./states/settings";
|
||||||
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
|
import { ShapezGameAnalytics } from "./platform/browser/game_analytics";
|
||||||
|
import { RestrictionManager } from "./core/restriction_manager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
|
* @typedef {import("./platform/game_analytics").GameAnalyticsInterface} GameAnalyticsInterface
|
||||||
@ -70,6 +71,9 @@ export class Application {
|
|||||||
this.inputMgr = new InputDistributor(this);
|
this.inputMgr = new InputDistributor(this);
|
||||||
this.backgroundResourceLoader = new BackgroundResourcesLoader(this);
|
this.backgroundResourceLoader = new BackgroundResourcesLoader(this);
|
||||||
|
|
||||||
|
// Restrictions (Like demo etc)
|
||||||
|
this.restrictionMgr = new RestrictionManager(this);
|
||||||
|
|
||||||
// Platform dependent stuff
|
// Platform dependent stuff
|
||||||
|
|
||||||
/** @type {StorageInterface} */
|
/** @type {StorageInterface} */
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { queryParamOptions } from "./query_parameters";
|
|
||||||
|
|
||||||
export const IS_DEBUG =
|
export const IS_DEBUG =
|
||||||
G_IS_DEV &&
|
G_IS_DEV &&
|
||||||
typeof window !== "undefined" &&
|
typeof window !== "undefined" &&
|
||||||
@ -7,13 +5,10 @@ export const IS_DEBUG =
|
|||||||
(window.location.host.indexOf("localhost:") >= 0 || window.location.host.indexOf("192.168.0.") >= 0) &&
|
(window.location.host.indexOf("localhost:") >= 0 || window.location.host.indexOf("192.168.0.") >= 0) &&
|
||||||
window.location.search.indexOf("nodebug") < 0;
|
window.location.search.indexOf("nodebug") < 0;
|
||||||
|
|
||||||
export const IS_DEMO = queryParamOptions.fullVersion
|
|
||||||
? false
|
|
||||||
: (!G_IS_DEV && !G_IS_STANDALONE) ||
|
|
||||||
(typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0);
|
|
||||||
|
|
||||||
export const SUPPORT_TOUCH = false;
|
export const SUPPORT_TOUCH = false;
|
||||||
|
|
||||||
|
export const IS_MAC = navigator.platform.toLowerCase().indexOf("mac") >= 0;
|
||||||
|
|
||||||
const smoothCanvas = true;
|
const smoothCanvas = true;
|
||||||
|
|
||||||
export const THIRDPARTY_URLS = {
|
export const THIRDPARTY_URLS = {
|
||||||
@ -64,7 +59,7 @@ export const globalConfig = {
|
|||||||
|
|
||||||
undergroundBeltMaxTilesByTier: [5, 9],
|
undergroundBeltMaxTilesByTier: [5, 9],
|
||||||
|
|
||||||
readerAnalyzeIntervalSeconds: G_IS_DEV ? 3 : 10,
|
readerAnalyzeIntervalSeconds: 10,
|
||||||
|
|
||||||
buildingSpeeds: {
|
buildingSpeeds: {
|
||||||
cutter: 1 / 4,
|
cutter: 1 / 4,
|
||||||
@ -137,3 +132,8 @@ if (G_IS_DEV && globalConfig.debug.renderForTrailer) {
|
|||||||
if (globalConfig.debug.fastGameEnter) {
|
if (globalConfig.debug.fastGameEnter) {
|
||||||
globalConfig.debug.noArtificialDelays = true;
|
globalConfig.debug.noArtificialDelays = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (G_IS_DEV && globalConfig.debug.noArtificialDelays) {
|
||||||
|
globalConfig.warmupTimeSecondsFast = 0;
|
||||||
|
globalConfig.warmupTimeSecondsRegular = 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,17 @@ import { getStringForKeyCode } from "../game/key_action_mapper";
|
|||||||
import { createLogger } from "./logging";
|
import { createLogger } from "./logging";
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ***************************************************
|
||||||
|
*
|
||||||
|
* LEGACY CODE WARNING
|
||||||
|
*
|
||||||
|
* This is old code from yorg3.io and needs to be refactored
|
||||||
|
* @TODO
|
||||||
|
*
|
||||||
|
* ***************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
const kbEnter = 13;
|
const kbEnter = 13;
|
||||||
const kbCancel = 27;
|
const kbCancel = 27;
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,17 @@ import { BaseItem } from "../game/base_item";
|
|||||||
import { ClickDetector } from "./click_detector";
|
import { ClickDetector } from "./click_detector";
|
||||||
import { Signal } from "./signal";
|
import { Signal } from "./signal";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ***************************************************
|
||||||
|
*
|
||||||
|
* LEGACY CODE WARNING
|
||||||
|
*
|
||||||
|
* This is old code from yorg3.io and needs to be refactored
|
||||||
|
* @TODO
|
||||||
|
*
|
||||||
|
* ***************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
export class FormElement {
|
export class FormElement {
|
||||||
constructor(id, label) {
|
constructor(id, label) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
|||||||
@ -81,10 +81,6 @@ export class ReadWriteProxy {
|
|||||||
return this.writeAsync();
|
return this.writeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentData() {
|
|
||||||
return this.currentData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} obj
|
* @param {object} obj
|
||||||
|
|||||||
155
src/js/core/restriction_manager.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { Application } from "../application";
|
||||||
|
/* typehints:end */
|
||||||
|
import { IS_MAC } from "./config";
|
||||||
|
import { ExplainedResult } from "./explained_result";
|
||||||
|
import { queryParamOptions } from "./query_parameters";
|
||||||
|
import { ReadWriteProxy } from "./read_write_proxy";
|
||||||
|
|
||||||
|
export class RestrictionManager extends ReadWriteProxy {
|
||||||
|
/**
|
||||||
|
* @param {Application} app
|
||||||
|
*/
|
||||||
|
constructor(app) {
|
||||||
|
super(app, "restriction-flags.bin");
|
||||||
|
|
||||||
|
this.currentData = this.getDefaultData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- RW Proxy Impl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} data
|
||||||
|
*/
|
||||||
|
verify(data) {
|
||||||
|
return ExplainedResult.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
getDefaultData() {
|
||||||
|
return {
|
||||||
|
version: this.getCurrentVersion(),
|
||||||
|
savegameV1119Imported: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
getCurrentVersion() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} data
|
||||||
|
*/
|
||||||
|
migrate(data) {
|
||||||
|
// Todo
|
||||||
|
return ExplainedResult.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
return this.readAsync().then(() => {
|
||||||
|
if (this.currentData.savegameV1119Imported) {
|
||||||
|
console.warn("Levelunlock is granted to current user due to past savegame");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- End RW Proxy Impl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any savegames from the 1.1.19 version
|
||||||
|
*/
|
||||||
|
onHasLegacySavegamesChanged(has119Savegames = false) {
|
||||||
|
if (has119Savegames && !this.currentData.savegameV1119Imported) {
|
||||||
|
this.currentData.savegameV1119Imported = true;
|
||||||
|
console.warn("Current user now has access to all levels due to 1119 savegame");
|
||||||
|
return this.writeAsync();
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the app is currently running as the limited version
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isLimitedVersion() {
|
||||||
|
if (IS_MAC) {
|
||||||
|
// On mac, the full version is always active
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (G_IS_STANDALONE) {
|
||||||
|
// Standalone is never limited
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParamOptions.fullVersion) {
|
||||||
|
// Full version is activated via flag
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (G_IS_DEV) {
|
||||||
|
return typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the app markets the standalone version on steam
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getIsStandaloneMarketingActive() {
|
||||||
|
return this.isLimitedVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if exporting the base as a screenshot is possible
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getIsExportingScreenshotsPossible() {
|
||||||
|
return !this.isLimitedVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum number of supported waypoints
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getMaximumWaypoints() {
|
||||||
|
return this.isLimitedVersion() ? 2 : 1e20;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the user has unlimited savegames
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getHasUnlimitedSavegames() {
|
||||||
|
return !this.isLimitedVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the app has all settings available
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getHasExtendedSettings() {
|
||||||
|
return !this.isLimitedVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if all upgrades are available
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getHasExtendedUpgrades() {
|
||||||
|
return !this.isLimitedVersion() || this.currentData.savegameV1119Imported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if all levels & freeplay is available
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getHasExtendedLevelsAndFreeplay() {
|
||||||
|
return !this.isLimitedVersion() || this.currentData.savegameV1119Imported;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -108,17 +108,6 @@ export class RandomNumberGenerator {
|
|||||||
assert(max > min, "rng: max <= min");
|
assert(max > min, "rng: max <= min");
|
||||||
return Math.floor(this.next() * (max - min) + min);
|
return Math.floor(this.next() * (max - min) + min);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @param {number} min
|
|
||||||
* @param {number} max
|
|
||||||
* @returns {number} Integer in range [min, max]
|
|
||||||
*/
|
|
||||||
nextIntRangeInclusive(min, max) {
|
|
||||||
assert(Number.isFinite(min), "Minimum is no integer");
|
|
||||||
assert(Number.isFinite(max), "Maximum is no integer");
|
|
||||||
assert(max > min, "rng: max <= min");
|
|
||||||
return Math.round(this.next() * (max - min) + min);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} min
|
* @param {number} min
|
||||||
|
|||||||
@ -681,3 +681,72 @@ export function fillInLinkIntoTranslation(translation, link) {
|
|||||||
.replace("<link>", "<a href='" + link + "' target='_blank'>")
|
.replace("<link>", "<a href='" + link + "' target='_blank'>")
|
||||||
.replace("</link>", "</a>");
|
.replace("</link>", "</a>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a file chooser
|
||||||
|
* @param {string} acceptedType
|
||||||
|
*/
|
||||||
|
export function startFileChoose(acceptedType = ".bin") {
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.accept = acceptedType;
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
input.onchange = _ => resolve(input.files[0]);
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const romanLiterals = [
|
||||||
|
"0", // NULL
|
||||||
|
"I",
|
||||||
|
"II",
|
||||||
|
"III",
|
||||||
|
"IV",
|
||||||
|
"V",
|
||||||
|
"VI",
|
||||||
|
"VII",
|
||||||
|
"VIII",
|
||||||
|
"IX",
|
||||||
|
"X",
|
||||||
|
"XI",
|
||||||
|
"XII",
|
||||||
|
"XIII",
|
||||||
|
"XIV",
|
||||||
|
"XV",
|
||||||
|
"XVI",
|
||||||
|
"XVII",
|
||||||
|
"XVIII",
|
||||||
|
"XIX",
|
||||||
|
"XX",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} number
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getRomanNumber(number) {
|
||||||
|
number = Math.max(0, Math.round(number));
|
||||||
|
if (number < romanLiterals.length) {
|
||||||
|
return romanLiterals[number];
|
||||||
|
}
|
||||||
|
return String(number);
|
||||||
|
}
|
||||||
|
|||||||
@ -1111,7 +1111,7 @@ export class BeltPath extends BasicSerializableObject {
|
|||||||
|
|
||||||
isFirstItemProcessed = false;
|
isFirstItemProcessed = false;
|
||||||
this.spacingToFirstItem += clampedProgress;
|
this.spacingToFirstItem += clampedProgress;
|
||||||
if (remainingVelocity < 0.01) {
|
if (remainingVelocity < 1e-7) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import { globalConfig } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { DrawParameters } from "../core/draw_parameters";
|
import { DrawParameters } from "../core/draw_parameters";
|
||||||
import { createLogger } from "../core/logging";
|
|
||||||
import { findNiceIntegerValue } from "../core/utils";
|
import { findNiceIntegerValue } from "../core/utils";
|
||||||
import { Vector } from "../core/vector";
|
import { Vector } from "../core/vector";
|
||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
import { blueprintShape } from "./upgrades";
|
|
||||||
|
|
||||||
const logger = createLogger("blueprint");
|
|
||||||
|
|
||||||
export class Blueprint {
|
export class Blueprint {
|
||||||
/**
|
/**
|
||||||
@ -142,7 +138,7 @@ export class Blueprint {
|
|||||||
* @param {GameRoot} root
|
* @param {GameRoot} root
|
||||||
*/
|
*/
|
||||||
canAfford(root) {
|
canAfford(root) {
|
||||||
return root.hubGoals.getShapesStoredByKey(blueprintShape) >= this.getCost();
|
return root.hubGoals.getShapesStoredByKey(root.gameMode.getBlueprintShapeKey()) >= this.getCost();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { BeltComponent } from "../components/belt";
|
|||||||
import { Entity } from "../entity";
|
import { Entity } from "../entity";
|
||||||
import { MetaBuilding } from "../meta_building";
|
import { MetaBuilding } from "../meta_building";
|
||||||
import { GameRoot } from "../root";
|
import { GameRoot } from "../root";
|
||||||
|
import { THEME } from "../theme";
|
||||||
|
|
||||||
export const arrayBeltVariantToRotation = [enumDirection.top, enumDirection.left, enumDirection.right];
|
export const arrayBeltVariantToRotation = [enumDirection.top, enumDirection.left, enumDirection.right];
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ export class MetaBeltBuilding extends MetaBuilding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSilhouetteColor() {
|
getSilhouetteColor() {
|
||||||
return "#777";
|
return THEME.map.chunkOverview.beltColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlacementSound() {
|
getPlacementSound() {
|
||||||
|
|||||||
@ -110,12 +110,7 @@ export class MetaVirtualProcessorBuilding extends MetaBuilding {
|
|||||||
pinComp.setSlots([
|
pinComp.setSlots([
|
||||||
{
|
{
|
||||||
pos: new Vector(0, 0),
|
pos: new Vector(0, 0),
|
||||||
direction: enumDirection.left,
|
direction: enumDirection.top,
|
||||||
type: enumPinSlotType.logicalEjector,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pos: new Vector(0, 0),
|
|
||||||
direction: enumDirection.right,
|
|
||||||
type: enumPinSlotType.logicalEjector,
|
type: enumPinSlotType.logicalEjector,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -511,7 +511,11 @@ export class Camera extends BasicSerializableObject {
|
|||||||
this.clampZoomLevel();
|
this.clampZoomLevel();
|
||||||
this.desiredZoom = null;
|
this.desiredZoom = null;
|
||||||
|
|
||||||
const mousePosition = this.root.app.mousePosition;
|
let mousePosition = this.root.app.mousePosition;
|
||||||
|
if (!this.root.app.settings.getAllSettings().zoomToCursor) {
|
||||||
|
mousePosition = new Vector(this.root.gameWidth / 2, this.root.gameHeight / 2);
|
||||||
|
}
|
||||||
|
|
||||||
if (mousePosition) {
|
if (mousePosition) {
|
||||||
const worldPos = this.root.camera.screenToWorld(mousePosition);
|
const worldPos = this.root.camera.screenToWorld(mousePosition);
|
||||||
const worldDelta = worldPos.sub(this.center);
|
const worldDelta = worldPos.sub(this.center);
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export class ItemEjectorComponent extends Component {
|
|||||||
static getSchema() {
|
static getSchema() {
|
||||||
// The cachedDestSlot, cachedTargetEntity fields are not serialized.
|
// The cachedDestSlot, cachedTargetEntity fields are not serialized.
|
||||||
return {
|
return {
|
||||||
slots: types.array(
|
slots: types.fixedSizeArray(
|
||||||
types.structured({
|
types.structured({
|
||||||
item: types.nullable(typeItemSingleton),
|
item: types.nullable(typeItemSingleton),
|
||||||
progress: types.float,
|
progress: types.float,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export class WiredPinsComponent extends Component {
|
|||||||
|
|
||||||
static getSchema() {
|
static getSchema() {
|
||||||
return {
|
return {
|
||||||
slots: types.array(
|
slots: types.fixedSizeArray(
|
||||||
types.structured({
|
types.structured({
|
||||||
value: types.nullable(typeItemSingleton),
|
value: types.nullable(typeItemSingleton),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { KeyActionMapper } from "./key_action_mapper";
|
|||||||
import { GameLogic } from "./logic";
|
import { GameLogic } from "./logic";
|
||||||
import { MapView } from "./map_view";
|
import { MapView } from "./map_view";
|
||||||
import { defaultBuildingVariant } from "./meta_building";
|
import { defaultBuildingVariant } from "./meta_building";
|
||||||
|
import { RegularGameMode } from "./modes/regular";
|
||||||
import { ProductionAnalytics } from "./production_analytics";
|
import { ProductionAnalytics } from "./production_analytics";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||||
@ -101,6 +102,9 @@ export class GameCore {
|
|||||||
// Needs to come first
|
// Needs to come first
|
||||||
root.dynamicTickrate = new DynamicTickrate(root);
|
root.dynamicTickrate = new DynamicTickrate(root);
|
||||||
|
|
||||||
|
// Init game mode
|
||||||
|
root.gameMode = new RegularGameMode(root);
|
||||||
|
|
||||||
// Init classes
|
// Init classes
|
||||||
root.camera = new Camera(root);
|
root.camera = new Camera(root);
|
||||||
root.map = new MapView(root);
|
root.map = new MapView(root);
|
||||||
|
|||||||
71
src/js/game/game_mode.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { enumHubGoalRewards } from "./tutorial_goals";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
import { GameRoot } from "./root";
|
||||||
|
|
||||||
|
/** @typedef {{
|
||||||
|
* shape: string,
|
||||||
|
* amount: number
|
||||||
|
* }} UpgradeRequirement */
|
||||||
|
|
||||||
|
/** @typedef {{
|
||||||
|
* required: Array<UpgradeRequirement>
|
||||||
|
* improvement?: number,
|
||||||
|
* excludePrevious?: boolean
|
||||||
|
* }} TierRequirement */
|
||||||
|
|
||||||
|
/** @typedef {Array<TierRequirement>} UpgradeTiers */
|
||||||
|
|
||||||
|
/** @typedef {{
|
||||||
|
* shape: string,
|
||||||
|
* required: number,
|
||||||
|
* reward: enumHubGoalRewards,
|
||||||
|
* throughputOnly?: boolean
|
||||||
|
* }} LevelDefinition */
|
||||||
|
|
||||||
|
export class GameMode {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {GameRoot} root
|
||||||
|
*/
|
||||||
|
constructor(root) {
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return all available upgrades
|
||||||
|
* @returns {Object<string, UpgradeTiers>}
|
||||||
|
*/
|
||||||
|
getUpgrades() {
|
||||||
|
abstract;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the blueprint shape key
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getBlueprintShapeKey() {
|
||||||
|
abstract;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the goals for all levels including their reward
|
||||||
|
* @returns {Array<LevelDefinition>}
|
||||||
|
*/
|
||||||
|
getLevelDefinitions() {
|
||||||
|
abstract;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return whether free play is available or if the game stops
|
||||||
|
* after the predefined levels
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getIsFreeplayAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
import { globalConfig, IS_DEMO } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
import { RandomNumberGenerator } from "../core/rng";
|
import { RandomNumberGenerator } from "../core/rng";
|
||||||
import { clamp, findNiceIntegerValue, randomChoice, randomInt } from "../core/utils";
|
import { clamp } from "../core/utils";
|
||||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||||
import { enumColors } from "./colors";
|
import { enumColors } from "./colors";
|
||||||
import { enumItemProcessorTypes } from "./components/item_processor";
|
import { enumItemProcessorTypes } from "./components/item_processor";
|
||||||
import { enumAnalyticsDataSource } from "./production_analytics";
|
import { enumAnalyticsDataSource } from "./production_analytics";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
import { enumSubShape, ShapeDefinition } from "./shape_definition";
|
||||||
import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals";
|
import { enumHubGoalRewards } from "./tutorial_goals";
|
||||||
import { UPGRADES } from "./upgrades";
|
|
||||||
|
|
||||||
export class HubGoals extends BasicSerializableObject {
|
export class HubGoals extends BasicSerializableObject {
|
||||||
static getId() {
|
static getId() {
|
||||||
@ -23,27 +22,36 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(data) {
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} data
|
||||||
|
* @param {GameRoot} root
|
||||||
|
*/
|
||||||
|
deserialize(data, root) {
|
||||||
const errorCode = super.deserialize(data);
|
const errorCode = super.deserialize(data);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_DEMO) {
|
const levels = root.gameMode.getLevelDefinitions();
|
||||||
this.level = Math.min(this.level, tutorialGoals.length);
|
|
||||||
|
// If freeplay is not available, clamp the level
|
||||||
|
if (!root.gameMode.getIsFreeplayAvailable()) {
|
||||||
|
this.level = Math.min(this.level, levels.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute gained rewards
|
// Compute gained rewards
|
||||||
for (let i = 0; i < this.level - 1; ++i) {
|
for (let i = 0; i < this.level - 1; ++i) {
|
||||||
if (i < tutorialGoals.length) {
|
if (i < levels.length) {
|
||||||
const reward = tutorialGoals[i].reward;
|
const reward = levels[i].reward;
|
||||||
this.gainedRewards[reward] = (this.gainedRewards[reward] || 0) + 1;
|
this.gainedRewards[reward] = (this.gainedRewards[reward] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute upgrade improvements
|
// Compute upgrade improvements
|
||||||
for (const upgradeId in UPGRADES) {
|
const upgrades = this.root.gameMode.getUpgrades();
|
||||||
const tiers = UPGRADES[upgradeId];
|
for (const upgradeId in upgrades) {
|
||||||
|
const tiers = upgrades[upgradeId];
|
||||||
const level = this.upgradeLevels[upgradeId] || 0;
|
const level = this.upgradeLevels[upgradeId] || 0;
|
||||||
let totalImprovement = 1;
|
let totalImprovement = 1;
|
||||||
for (let i = 0; i < level; ++i) {
|
for (let i = 0; i < level; ++i) {
|
||||||
@ -84,17 +92,16 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
*/
|
*/
|
||||||
this.upgradeLevels = {};
|
this.upgradeLevels = {};
|
||||||
|
|
||||||
// Reset levels
|
|
||||||
for (const key in UPGRADES) {
|
|
||||||
this.upgradeLevels[key] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the improvements for all upgrades
|
* Stores the improvements for all upgrades
|
||||||
* @type {Object<string, number>}
|
* @type {Object<string, number>}
|
||||||
*/
|
*/
|
||||||
this.upgradeImprovements = {};
|
this.upgradeImprovements = {};
|
||||||
for (const key in UPGRADES) {
|
|
||||||
|
// Reset levels first
|
||||||
|
const upgrades = this.root.gameMode.getUpgrades();
|
||||||
|
for (const key in upgrades) {
|
||||||
|
this.upgradeLevels[key] = 0;
|
||||||
this.upgradeImprovements[key] = 1;
|
this.upgradeImprovements[key] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +127,10 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isEndOfDemoReached() {
|
isEndOfDemoReached() {
|
||||||
return IS_DEMO && this.level >= tutorialGoals.length;
|
return (
|
||||||
|
!this.root.gameMode.getIsFreeplayAvailable() &&
|
||||||
|
this.level >= this.root.gameMode.getLevelDefinitions().length
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,8 +225,9 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
*/
|
*/
|
||||||
computeNextGoal() {
|
computeNextGoal() {
|
||||||
const storyIndex = this.level - 1;
|
const storyIndex = this.level - 1;
|
||||||
if (storyIndex < tutorialGoals.length) {
|
const levels = this.root.gameMode.getLevelDefinitions();
|
||||||
const { shape, required, reward, throughputOnly } = tutorialGoals[storyIndex];
|
if (storyIndex < levels.length) {
|
||||||
|
const { shape, required, reward, throughputOnly } = levels[storyIndex];
|
||||||
this.currentGoal = {
|
this.currentGoal = {
|
||||||
/** @type {ShapeDefinition} */
|
/** @type {ShapeDefinition} */
|
||||||
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape),
|
definition: this.root.shapeDefinitionMgr.getShapeFromShortKey(shape),
|
||||||
@ -254,7 +265,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
* Returns whether we are playing in free-play
|
* Returns whether we are playing in free-play
|
||||||
*/
|
*/
|
||||||
isFreePlay() {
|
isFreePlay() {
|
||||||
return this.level >= tutorialGoals.length;
|
return this.level >= this.root.gameMode.getLevelDefinitions().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -262,7 +273,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
* @param {string} upgradeId
|
* @param {string} upgradeId
|
||||||
*/
|
*/
|
||||||
canUnlockUpgrade(upgradeId) {
|
canUnlockUpgrade(upgradeId) {
|
||||||
const tiers = UPGRADES[upgradeId];
|
const tiers = this.root.gameMode.getUpgrades()[upgradeId];
|
||||||
const currentLevel = this.getUpgradeLevel(upgradeId);
|
const currentLevel = this.getUpgradeLevel(upgradeId);
|
||||||
|
|
||||||
if (currentLevel >= tiers.length) {
|
if (currentLevel >= tiers.length) {
|
||||||
@ -270,11 +281,6 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_DEMO && currentLevel >= 4) {
|
|
||||||
// DEMO
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.upgradesNoCost) {
|
if (G_IS_DEV && globalConfig.debug.upgradesNoCost) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -296,7 +302,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
*/
|
*/
|
||||||
getAvailableUpgradeCount() {
|
getAvailableUpgradeCount() {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const upgradeId in UPGRADES) {
|
for (const upgradeId in this.root.gameMode.getUpgrades()) {
|
||||||
if (this.canUnlockUpgrade(upgradeId)) {
|
if (this.canUnlockUpgrade(upgradeId)) {
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
@ -314,7 +320,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const upgradeTiers = UPGRADES[upgradeId];
|
const upgradeTiers = this.root.gameMode.getUpgrades()[upgradeId];
|
||||||
const currentLevel = this.getUpgradeLevel(upgradeId);
|
const currentLevel = this.getUpgradeLevel(upgradeId);
|
||||||
|
|
||||||
const tierData = upgradeTiers[currentLevel];
|
const tierData = upgradeTiers[currentLevel];
|
||||||
@ -363,7 +369,7 @@ export class HubGoals extends BasicSerializableObject {
|
|||||||
if (allowUncolored) {
|
if (allowUncolored) {
|
||||||
universalColors.push(enumColors.uncolored);
|
universalColors.push(enumColors.uncolored);
|
||||||
}
|
}
|
||||||
const index = rng.nextIntRangeInclusive(0, colorWheel.length - 3);
|
const index = rng.nextIntRange(0, colorWheel.length - 2);
|
||||||
const pickedColors = colorWheel.slice(index, index + 3);
|
const pickedColors = colorWheel.slice(index, index + 3);
|
||||||
pickedColors.push(rng.choice(universalColors));
|
pickedColors.push(rng.choice(universalColors));
|
||||||
return pickedColors;
|
return pickedColors;
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
|
|||||||
import { HUDUnlockNotification } from "./parts/unlock_notification";
|
import { HUDUnlockNotification } from "./parts/unlock_notification";
|
||||||
import { HUDGameMenu } from "./parts/game_menu";
|
import { HUDGameMenu } from "./parts/game_menu";
|
||||||
import { HUDShop } from "./parts/shop";
|
import { HUDShop } from "./parts/shop";
|
||||||
import { IS_MOBILE, globalConfig, IS_DEMO } from "../../core/config";
|
import { IS_MOBILE, globalConfig } from "../../core/config";
|
||||||
import { HUDMassSelector } from "./parts/mass_selector";
|
import { HUDMassSelector } from "./parts/mass_selector";
|
||||||
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
|
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
|
||||||
import { HUDStatistics } from "./parts/statistics";
|
import { HUDStatistics } from "./parts/statistics";
|
||||||
@ -45,7 +45,6 @@ import { HUDLeverToggle } from "./parts/lever_toggle";
|
|||||||
import { HUDLayerPreview } from "./parts/layer_preview";
|
import { HUDLayerPreview } from "./parts/layer_preview";
|
||||||
import { HUDMinerHighlight } from "./parts/miner_highlight";
|
import { HUDMinerHighlight } from "./parts/miner_highlight";
|
||||||
import { HUDBetaOverlay } from "./parts/beta_overlay";
|
import { HUDBetaOverlay } from "./parts/beta_overlay";
|
||||||
import { HUDPerformanceWarning } from "./parts/performance_warning";
|
|
||||||
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
import { HUDStandaloneAdvantages } from "./parts/standalone_advantages";
|
||||||
import { HUDCatMemes } from "./parts/cat_memes";
|
import { HUDCatMemes } from "./parts/cat_memes";
|
||||||
|
|
||||||
@ -88,7 +87,6 @@ export class GameHUD {
|
|||||||
layerPreview: new HUDLayerPreview(this.root),
|
layerPreview: new HUDLayerPreview(this.root),
|
||||||
|
|
||||||
minerHighlight: new HUDMinerHighlight(this.root),
|
minerHighlight: new HUDMinerHighlight(this.root),
|
||||||
performanceWarning: new HUDPerformanceWarning(this.root),
|
|
||||||
|
|
||||||
// Typing hints
|
// Typing hints
|
||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
@ -116,7 +114,7 @@ export class GameHUD {
|
|||||||
this.parts.entityDebugger = new HUDEntityDebugger(this.root);
|
this.parts.entityDebugger = new HUDEntityDebugger(this.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_DEMO) {
|
if (this.root.app.restrictionMgr.getIsStandaloneMarketingActive()) {
|
||||||
this.parts.watermark = new HUDWatermark(this.root);
|
this.parts.watermark = new HUDWatermark(this.root);
|
||||||
this.parts.standaloneAdvantages = new HUDStandaloneAdvantages(this.root);
|
this.parts.standaloneAdvantages = new HUDStandaloneAdvantages(this.root);
|
||||||
this.parts.catMemes = new HUDCatMemes(this.root);
|
this.parts.catMemes = new HUDCatMemes(this.root);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export class HUDBetaOverlay extends BaseHUDPart {
|
|||||||
parent,
|
parent,
|
||||||
"ingame_HUD_BetaOverlay",
|
"ingame_HUD_BetaOverlay",
|
||||||
[],
|
[],
|
||||||
"<h2>UNSTABLE BETA VERSION</h2><span>Steam Release: 9th October 2020!</span>"
|
"<h2>UNSTABLE BETA VERSION</h2><span>Unfinalized & potential buggy content!</span>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,202 +1,203 @@
|
|||||||
import { DrawParameters } from "../../../core/draw_parameters";
|
import { DrawParameters } from "../../../core/draw_parameters";
|
||||||
import { STOP_PROPAGATION } from "../../../core/signal";
|
import { STOP_PROPAGATION } from "../../../core/signal";
|
||||||
import { TrackedState } from "../../../core/tracked_state";
|
import { TrackedState } from "../../../core/tracked_state";
|
||||||
import { makeDiv } from "../../../core/utils";
|
import { makeDiv } from "../../../core/utils";
|
||||||
import { Vector } from "../../../core/vector";
|
import { Vector } from "../../../core/vector";
|
||||||
import { T } from "../../../translations";
|
import { SOUNDS } from "../../../platform/sound";
|
||||||
import { enumMouseButton } from "../../camera";
|
import { T } from "../../../translations";
|
||||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
import { Blueprint } from "../../blueprint";
|
||||||
import { blueprintShape } from "../../upgrades";
|
import { enumMouseButton } from "../../camera";
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { Blueprint } from "../../blueprint";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
import { SOUNDS } from "../../../platform/sound";
|
|
||||||
|
export class HUDBlueprintPlacer extends BaseHUDPart {
|
||||||
export class HUDBlueprintPlacer extends BaseHUDPart {
|
createElements(parent) {
|
||||||
createElements(parent) {
|
const blueprintCostShape = this.root.shapeDefinitionMgr.getShapeFromShortKey(
|
||||||
const blueprintCostShape = this.root.shapeDefinitionMgr.getShapeFromShortKey(blueprintShape);
|
this.root.gameMode.getBlueprintShapeKey()
|
||||||
const blueprintCostShapeCanvas = blueprintCostShape.generateAsCanvas(80);
|
);
|
||||||
|
const blueprintCostShapeCanvas = blueprintCostShape.generateAsCanvas(80);
|
||||||
this.costDisplayParent = makeDiv(parent, "ingame_HUD_BlueprintPlacer", [], ``);
|
|
||||||
|
this.costDisplayParent = makeDiv(parent, "ingame_HUD_BlueprintPlacer", [], ``);
|
||||||
makeDiv(this.costDisplayParent, null, ["label"], T.ingame.blueprintPlacer.cost);
|
|
||||||
const costContainer = makeDiv(this.costDisplayParent, null, ["costContainer"], "");
|
makeDiv(this.costDisplayParent, null, ["label"], T.ingame.blueprintPlacer.cost);
|
||||||
this.costDisplayText = makeDiv(costContainer, null, ["costText"], "");
|
const costContainer = makeDiv(this.costDisplayParent, null, ["costContainer"], "");
|
||||||
costContainer.appendChild(blueprintCostShapeCanvas);
|
this.costDisplayText = makeDiv(costContainer, null, ["costText"], "");
|
||||||
}
|
costContainer.appendChild(blueprintCostShapeCanvas);
|
||||||
|
}
|
||||||
initialize() {
|
|
||||||
this.root.hud.signals.buildingsSelectedForCopy.add(this.createBlueprintFromBuildings, this);
|
initialize() {
|
||||||
|
this.root.hud.signals.buildingsSelectedForCopy.add(this.createBlueprintFromBuildings, this);
|
||||||
/** @type {TypedTrackedState<Blueprint?>} */
|
|
||||||
this.currentBlueprint = new TrackedState(this.onBlueprintChanged, this);
|
/** @type {TypedTrackedState<Blueprint?>} */
|
||||||
/** @type {Blueprint?} */
|
this.currentBlueprint = new TrackedState(this.onBlueprintChanged, this);
|
||||||
this.lastBlueprintUsed = null;
|
/** @type {Blueprint?} */
|
||||||
|
this.lastBlueprintUsed = null;
|
||||||
const keyActionMapper = this.root.keyMapper;
|
|
||||||
keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
|
const keyActionMapper = this.root.keyMapper;
|
||||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.abortPlacement, this);
|
keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this);
|
||||||
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.abortPlacement, this);
|
||||||
keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this);
|
keyActionMapper.getBinding(KEYMAPPINGS.placement.rotateWhilePlacing).add(this.rotateBlueprint, this);
|
||||||
|
keyActionMapper.getBinding(KEYMAPPINGS.massSelect.pasteLastBlueprint).add(this.pasteBlueprint, this);
|
||||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
|
||||||
this.root.camera.movePreHandler.add(this.onMouseMove, this);
|
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||||
|
this.root.camera.movePreHandler.add(this.onMouseMove, this);
|
||||||
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this);
|
|
||||||
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.abortPlacement, this);
|
||||||
|
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
||||||
this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent);
|
|
||||||
this.trackedCanAfford = new TrackedState(this.onCanAffordChanged, this);
|
this.domAttach = new DynamicDomAttach(this.root, this.costDisplayParent);
|
||||||
}
|
this.trackedCanAfford = new TrackedState(this.onCanAffordChanged, this);
|
||||||
|
}
|
||||||
abortPlacement() {
|
|
||||||
if (this.currentBlueprint.get()) {
|
abortPlacement() {
|
||||||
this.currentBlueprint.set(null);
|
if (this.currentBlueprint.get()) {
|
||||||
|
this.currentBlueprint.set(null);
|
||||||
return STOP_PROPAGATION;
|
|
||||||
}
|
return STOP_PROPAGATION;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Called when the layer was changed
|
/**
|
||||||
* @param {Layer} layer
|
* Called when the layer was changed
|
||||||
*/
|
* @param {Layer} layer
|
||||||
onEditModeChanged(layer) {
|
*/
|
||||||
// Check if the layer of the blueprint differs and thus we have to deselect it
|
onEditModeChanged(layer) {
|
||||||
const blueprint = this.currentBlueprint.get();
|
// Check if the layer of the blueprint differs and thus we have to deselect it
|
||||||
if (blueprint) {
|
const blueprint = this.currentBlueprint.get();
|
||||||
if (blueprint.layer !== layer) {
|
if (blueprint) {
|
||||||
this.currentBlueprint.set(null);
|
if (blueprint.layer !== layer) {
|
||||||
}
|
this.currentBlueprint.set(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Called when the blueprint is now affordable or not
|
/**
|
||||||
* @param {boolean} canAfford
|
* Called when the blueprint is now affordable or not
|
||||||
*/
|
* @param {boolean} canAfford
|
||||||
onCanAffordChanged(canAfford) {
|
*/
|
||||||
this.costDisplayParent.classList.toggle("canAfford", canAfford);
|
onCanAffordChanged(canAfford) {
|
||||||
}
|
this.costDisplayParent.classList.toggle("canAfford", canAfford);
|
||||||
|
}
|
||||||
update() {
|
|
||||||
const currentBlueprint = this.currentBlueprint.get();
|
update() {
|
||||||
this.domAttach.update(currentBlueprint && currentBlueprint.getCost() > 0);
|
const currentBlueprint = this.currentBlueprint.get();
|
||||||
this.trackedCanAfford.set(currentBlueprint && currentBlueprint.canAfford(this.root));
|
this.domAttach.update(currentBlueprint && currentBlueprint.getCost() > 0);
|
||||||
}
|
this.trackedCanAfford.set(currentBlueprint && currentBlueprint.canAfford(this.root));
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Called when the blueprint was changed
|
/**
|
||||||
* @param {Blueprint} blueprint
|
* Called when the blueprint was changed
|
||||||
*/
|
* @param {Blueprint} blueprint
|
||||||
onBlueprintChanged(blueprint) {
|
*/
|
||||||
if (blueprint) {
|
onBlueprintChanged(blueprint) {
|
||||||
this.lastBlueprintUsed = blueprint;
|
if (blueprint) {
|
||||||
this.costDisplayText.innerText = "" + blueprint.getCost();
|
this.lastBlueprintUsed = blueprint;
|
||||||
}
|
this.costDisplayText.innerText = "" + blueprint.getCost();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* mouse down pre handler
|
/**
|
||||||
* @param {Vector} pos
|
* mouse down pre handler
|
||||||
* @param {enumMouseButton} button
|
* @param {Vector} pos
|
||||||
*/
|
* @param {enumMouseButton} button
|
||||||
onMouseDown(pos, button) {
|
*/
|
||||||
if (button === enumMouseButton.right) {
|
onMouseDown(pos, button) {
|
||||||
if (this.currentBlueprint.get()) {
|
if (button === enumMouseButton.right) {
|
||||||
this.abortPlacement();
|
if (this.currentBlueprint.get()) {
|
||||||
return STOP_PROPAGATION;
|
this.abortPlacement();
|
||||||
}
|
return STOP_PROPAGATION;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const blueprint = this.currentBlueprint.get();
|
|
||||||
if (!blueprint) {
|
const blueprint = this.currentBlueprint.get();
|
||||||
return;
|
if (!blueprint) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
if (!blueprint.canAfford(this.root)) {
|
|
||||||
this.root.soundProxy.playUiError();
|
if (!blueprint.canAfford(this.root)) {
|
||||||
return;
|
this.root.soundProxy.playUiError();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
const worldPos = this.root.camera.screenToWorld(pos);
|
|
||||||
const tile = worldPos.toTileSpace();
|
const worldPos = this.root.camera.screenToWorld(pos);
|
||||||
if (blueprint.tryPlace(this.root, tile)) {
|
const tile = worldPos.toTileSpace();
|
||||||
const cost = blueprint.getCost();
|
if (blueprint.tryPlace(this.root, tile)) {
|
||||||
this.root.hubGoals.takeShapeByKey(blueprintShape, cost);
|
const cost = blueprint.getCost();
|
||||||
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
|
this.root.hubGoals.takeShapeByKey(this.root.gameMode.getBlueprintShapeKey(), cost);
|
||||||
}
|
this.root.soundProxy.playUi(SOUNDS.placeBuilding);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Mose move handler
|
/**
|
||||||
*/
|
* Mose move handler
|
||||||
onMouseMove() {
|
*/
|
||||||
// Prevent movement while blueprint is selected
|
onMouseMove() {
|
||||||
if (this.currentBlueprint.get()) {
|
// Prevent movement while blueprint is selected
|
||||||
return STOP_PROPAGATION;
|
if (this.currentBlueprint.get()) {
|
||||||
}
|
return STOP_PROPAGATION;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Called when an array of bulidings was selected
|
/**
|
||||||
* @param {Array<number>} uids
|
* Called when an array of bulidings was selected
|
||||||
*/
|
* @param {Array<number>} uids
|
||||||
createBlueprintFromBuildings(uids) {
|
*/
|
||||||
if (uids.length === 0) {
|
createBlueprintFromBuildings(uids) {
|
||||||
return;
|
if (uids.length === 0) {
|
||||||
}
|
return;
|
||||||
this.currentBlueprint.set(Blueprint.fromUids(this.root, uids));
|
}
|
||||||
}
|
this.currentBlueprint.set(Blueprint.fromUids(this.root, uids));
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Attempts to rotate the current blueprint
|
/**
|
||||||
*/
|
* Attempts to rotate the current blueprint
|
||||||
rotateBlueprint() {
|
*/
|
||||||
if (this.currentBlueprint.get()) {
|
rotateBlueprint() {
|
||||||
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
|
if (this.currentBlueprint.get()) {
|
||||||
this.currentBlueprint.get().rotateCcw();
|
if (this.root.keyMapper.getBinding(KEYMAPPINGS.placement.rotateInverseModifier).pressed) {
|
||||||
} else {
|
this.currentBlueprint.get().rotateCcw();
|
||||||
this.currentBlueprint.get().rotateCw();
|
} else {
|
||||||
}
|
this.currentBlueprint.get().rotateCw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Attempts to paste the last blueprint
|
/**
|
||||||
*/
|
* Attempts to paste the last blueprint
|
||||||
pasteBlueprint() {
|
*/
|
||||||
if (this.lastBlueprintUsed !== null) {
|
pasteBlueprint() {
|
||||||
if (this.lastBlueprintUsed.layer !== this.root.currentLayer) {
|
if (this.lastBlueprintUsed !== null) {
|
||||||
// Not compatible
|
if (this.lastBlueprintUsed.layer !== this.root.currentLayer) {
|
||||||
this.root.soundProxy.playUiError();
|
// Not compatible
|
||||||
return;
|
this.root.soundProxy.playUiError();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
this.root.hud.signals.pasteBlueprintRequested.dispatch();
|
|
||||||
this.currentBlueprint.set(this.lastBlueprintUsed);
|
this.root.hud.signals.pasteBlueprintRequested.dispatch();
|
||||||
} else {
|
this.currentBlueprint.set(this.lastBlueprintUsed);
|
||||||
this.root.soundProxy.playUiError();
|
} else {
|
||||||
}
|
this.root.soundProxy.playUiError();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
*
|
/**
|
||||||
* @param {DrawParameters} parameters
|
*
|
||||||
*/
|
* @param {DrawParameters} parameters
|
||||||
draw(parameters) {
|
*/
|
||||||
const blueprint = this.currentBlueprint.get();
|
draw(parameters) {
|
||||||
if (!blueprint) {
|
const blueprint = this.currentBlueprint.get();
|
||||||
return;
|
if (!blueprint) {
|
||||||
}
|
return;
|
||||||
const mousePosition = this.root.app.mousePosition;
|
}
|
||||||
if (!mousePosition) {
|
const mousePosition = this.root.app.mousePosition;
|
||||||
// Not on screen
|
if (!mousePosition) {
|
||||||
return;
|
// Not on screen
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
const worldPos = this.root.camera.screenToWorld(mousePosition);
|
|
||||||
const tile = worldPos.toTileSpace();
|
const worldPos = this.root.camera.screenToWorld(mousePosition);
|
||||||
blueprint.draw(parameters, tile);
|
const tile = worldPos.toTileSpace();
|
||||||
}
|
blueprint.draw(parameters, tile);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
|
|||||||
import { TrackedState } from "../../../core/tracked_state";
|
import { TrackedState } from "../../../core/tracked_state";
|
||||||
import { cachebust } from "../../../core/cachebust";
|
import { cachebust } from "../../../core/cachebust";
|
||||||
import { T } from "../../../translations";
|
import { T } from "../../../translations";
|
||||||
|
import { enumItemProcessorTypes, ItemProcessorComponent } from "../../components/item_processor";
|
||||||
|
import { ShapeItem } from "../../items/shape_item";
|
||||||
|
|
||||||
const tutorialsByLevel = [
|
const tutorialsByLevel = [
|
||||||
// Level 1
|
// Level 1
|
||||||
@ -30,6 +32,68 @@ const tutorialsByLevel = [
|
|||||||
condition: () => true,
|
condition: () => true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
// Level 2
|
||||||
|
[
|
||||||
|
// 2.1 place a cutter
|
||||||
|
{
|
||||||
|
id: "2_1_place_cutter",
|
||||||
|
condition: /** @param {GameRoot} root */ root => {
|
||||||
|
return (
|
||||||
|
root.entityMgr
|
||||||
|
.getAllWithComponent(ItemProcessorComponent)
|
||||||
|
.filter(e => e.components.ItemProcessor.type === enumItemProcessorTypes.cutter)
|
||||||
|
.length === 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 2.2 place trash
|
||||||
|
{
|
||||||
|
id: "2_2_place_trash",
|
||||||
|
condition: /** @param {GameRoot} root */ root => {
|
||||||
|
return (
|
||||||
|
root.entityMgr
|
||||||
|
.getAllWithComponent(ItemProcessorComponent)
|
||||||
|
.filter(e => e.components.ItemProcessor.type === enumItemProcessorTypes.trash)
|
||||||
|
.length === 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 2.3 place more cutters
|
||||||
|
{
|
||||||
|
id: "2_3_more_cutters",
|
||||||
|
condition: /** @param {GameRoot} root */ root => {
|
||||||
|
return (
|
||||||
|
root.entityMgr
|
||||||
|
.getAllWithComponent(ItemProcessorComponent)
|
||||||
|
.filter(e => e.components.ItemProcessor.type === enumItemProcessorTypes.cutter)
|
||||||
|
.length < 3
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// Level 2
|
||||||
|
[
|
||||||
|
// 3.1. rectangles
|
||||||
|
{
|
||||||
|
id: "3_1_rectangles",
|
||||||
|
condition: /** @param {GameRoot} root */ root => {
|
||||||
|
return (
|
||||||
|
// 4 miners placed above rectangles and 10 delivered
|
||||||
|
root.hubGoals.getCurrentGoalDelivered() < 10 ||
|
||||||
|
root.entityMgr.getAllWithComponent(MinerComponent).filter(entity => {
|
||||||
|
const tile = entity.components.StaticMapEntity.origin;
|
||||||
|
const below = root.map.getLowerLayerContentXY(tile.x, tile.y);
|
||||||
|
if (below && below.getItemType() === "shape") {
|
||||||
|
const shape = /** @type {ShapeItem} */ (below).definition.getHash();
|
||||||
|
return shape === "RuRuRuRu";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).length < 4
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
export class HUDInteractiveTutorial extends BaseHUDPart {
|
export class HUDInteractiveTutorial extends BaseHUDPart {
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import { T } from "../../../translations";
|
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
|
||||||
|
|
||||||
export class HUDPerformanceWarning extends BaseHUDPart {
|
|
||||||
initialize() {
|
|
||||||
this.warningShown = false;
|
|
||||||
this.root.signals.entityManuallyPlaced.add(this.checkAfterPlace, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAfterPlace() {
|
|
||||||
if (!this.warningShown && this.root.entityMgr.entities.length > 10000) {
|
|
||||||
this.root.hud.parts.dialogs.showInfo(T.dialogs.entityWarning.title, T.dialogs.entityWarning.desc);
|
|
||||||
this.warningShown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,11 @@
|
|||||||
import { ClickDetector } from "../../../core/click_detector";
|
import { ClickDetector } from "../../../core/click_detector";
|
||||||
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
|
|
||||||
import { ShapeDefinition } from "../../shape_definition";
|
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
|
||||||
import { blueprintShape, UPGRADES } from "../../upgrades";
|
|
||||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
|
||||||
import { enumAnalyticsDataSource } from "../../production_analytics";
|
|
||||||
import { T } from "../../../translations";
|
|
||||||
import { globalConfig } from "../../../core/config";
|
import { globalConfig } from "../../../core/config";
|
||||||
|
import { arrayDeleteValue, formatBigNumber, makeDiv } from "../../../core/utils";
|
||||||
|
import { T } from "../../../translations";
|
||||||
|
import { enumAnalyticsDataSource } from "../../production_analytics";
|
||||||
|
import { ShapeDefinition } from "../../shape_definition";
|
||||||
|
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the pinned shapes on the left side of the screen
|
* Manages the pinned shapes on the left side of the screen
|
||||||
@ -82,7 +81,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
updateShapesAfterUpgrade() {
|
updateShapesAfterUpgrade() {
|
||||||
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
||||||
const key = this.pinnedShapes[i];
|
const key = this.pinnedShapes[i];
|
||||||
if (key === blueprintShape) {
|
if (key === this.root.gameMode.getBlueprintShapeKey()) {
|
||||||
// Ignore blueprint shapes
|
// Ignore blueprint shapes
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -107,13 +106,14 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
|
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
|
||||||
return this.root.hubGoals.currentGoal.required;
|
return this.root.hubGoals.currentGoal.required;
|
||||||
}
|
}
|
||||||
if (key === blueprintShape) {
|
if (key === this.root.gameMode.getBlueprintShapeKey()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this shape is required for any upgrade
|
// Check if this shape is required for any upgrade
|
||||||
for (const upgradeId in UPGRADES) {
|
const upgrades = this.root.gameMode.getUpgrades();
|
||||||
const upgradeTiers = UPGRADES[upgradeId];
|
for (const upgradeId in upgrades) {
|
||||||
|
const upgradeTiers = upgrades[upgradeId];
|
||||||
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
||||||
const tierHandle = upgradeTiers[currentTier];
|
const tierHandle = upgradeTiers[currentTier];
|
||||||
|
|
||||||
@ -138,7 +138,10 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
* @param {string} key
|
* @param {string} key
|
||||||
*/
|
*/
|
||||||
isShapePinned(key) {
|
isShapePinned(key) {
|
||||||
if (key === this.root.hubGoals.currentGoal.definition.getHash() || key === blueprintShape) {
|
if (
|
||||||
|
key === this.root.hubGoals.currentGoal.definition.getHash() ||
|
||||||
|
key === this.root.gameMode.getBlueprintShapeKey()
|
||||||
|
) {
|
||||||
// This is a "special" shape which is always pinned
|
// This is a "special" shape which is always pinned
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -178,7 +181,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
// Pin blueprint shape as well
|
// Pin blueprint shape as well
|
||||||
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
|
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
|
||||||
this.internalPinShape({
|
this.internalPinShape({
|
||||||
key: blueprintShape,
|
key: this.root.gameMode.getBlueprintShapeKey(),
|
||||||
canUnpin: false,
|
canUnpin: false,
|
||||||
className: "blueprint",
|
className: "blueprint",
|
||||||
});
|
});
|
||||||
@ -214,11 +217,11 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
|
|
||||||
let detector = null;
|
let detector = null;
|
||||||
if (canUnpin) {
|
if (canUnpin) {
|
||||||
element.classList.add("unpinable");
|
element.classList.add("removable");
|
||||||
detector = new ClickDetector(element, {
|
detector = new ClickDetector(element, {
|
||||||
consumeEvents: true,
|
consumeEvents: true,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
targetOnly: true,
|
targetOnly: false,
|
||||||
});
|
});
|
||||||
detector.click.add(() => this.unpinShape(key));
|
detector.click.add(() => this.unpinShape(key));
|
||||||
} else {
|
} else {
|
||||||
@ -291,6 +294,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
* @param {string} key
|
* @param {string} key
|
||||||
*/
|
*/
|
||||||
unpinShape(key) {
|
unpinShape(key) {
|
||||||
|
console.log("unpin", key);
|
||||||
arrayDeleteValue(this.pinnedShapes, key);
|
arrayDeleteValue(this.pinnedShapes, key);
|
||||||
this.rerenderFull();
|
this.rerenderFull();
|
||||||
}
|
}
|
||||||
@ -306,7 +310,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === blueprintShape) {
|
if (key === this.root.gameMode.getBlueprintShapeKey()) {
|
||||||
// Can not pin the blueprint shape
|
// Can not pin the blueprint shape
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { BaseHUDPart } from "../base_hud_part";
|
|
||||||
import { makeDiv } from "../../../core/utils";
|
import { makeDiv } from "../../../core/utils";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
import { blueprintShape, UPGRADES } from "../../upgrades";
|
|
||||||
import { enumNotificationType } from "./notifications";
|
import { enumNotificationType } from "./notifications";
|
||||||
import { tutorialGoals } from "../../tutorial_goals";
|
|
||||||
|
|
||||||
export class HUDSandboxController extends BaseHUDPart {
|
export class HUDSandboxController extends BaseHUDPart {
|
||||||
createElements(parent) {
|
createElements(parent) {
|
||||||
@ -75,10 +73,11 @@ export class HUDSandboxController extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
giveBlueprints() {
|
giveBlueprints() {
|
||||||
if (!this.root.hubGoals.storedShapes[blueprintShape]) {
|
const shape = this.root.gameMode.getBlueprintShapeKey();
|
||||||
this.root.hubGoals.storedShapes[blueprintShape] = 0;
|
if (!this.root.hubGoals.storedShapes[shape]) {
|
||||||
|
this.root.hubGoals.storedShapes[shape] = 0;
|
||||||
}
|
}
|
||||||
this.root.hubGoals.storedShapes[blueprintShape] += 1e9;
|
this.root.hubGoals.storedShapes[shape] += 1e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
maxOutAll() {
|
maxOutAll() {
|
||||||
@ -89,7 +88,7 @@ export class HUDSandboxController extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
modifyUpgrade(id, amount) {
|
modifyUpgrade(id, amount) {
|
||||||
const upgradeTiers = UPGRADES[id];
|
const upgradeTiers = this.root.gameMode.getUpgrades()[id];
|
||||||
const maxLevel = upgradeTiers.length;
|
const maxLevel = upgradeTiers.length;
|
||||||
|
|
||||||
this.root.hubGoals.upgradeLevels[id] = Math.max(
|
this.root.hubGoals.upgradeLevels[id] = Math.max(
|
||||||
@ -122,9 +121,10 @@ export class HUDSandboxController extends BaseHUDPart {
|
|||||||
|
|
||||||
// Compute gained rewards
|
// Compute gained rewards
|
||||||
hubGoals.gainedRewards = {};
|
hubGoals.gainedRewards = {};
|
||||||
|
const levels = this.root.gameMode.getLevelDefinitions();
|
||||||
for (let i = 0; i < hubGoals.level - 1; ++i) {
|
for (let i = 0; i < hubGoals.level - 1; ++i) {
|
||||||
if (i < tutorialGoals.length) {
|
if (i < levels.length) {
|
||||||
const reward = tutorialGoals[i].reward;
|
const reward = levels[i].reward;
|
||||||
hubGoals.gainedRewards[reward] = (hubGoals.gainedRewards[reward] || 0) + 1;
|
hubGoals.gainedRewards[reward] = (hubGoals.gainedRewards[reward] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { BaseHUDPart } from "../base_hud_part";
|
|
||||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
|
||||||
import { IS_DEMO, globalConfig } from "../../../core/config";
|
|
||||||
import { T } from "../../../translations";
|
|
||||||
import { createLogger } from "../../../core/logging";
|
|
||||||
import { StaticMapEntityComponent } from "../../components/static_map_entity";
|
|
||||||
import { Vector } from "../../../core/vector";
|
|
||||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||||
|
import { globalConfig } from "../../../core/config";
|
||||||
import { DrawParameters } from "../../../core/draw_parameters";
|
import { DrawParameters } from "../../../core/draw_parameters";
|
||||||
|
import { createLogger } from "../../../core/logging";
|
||||||
import { Rectangle } from "../../../core/rectangle";
|
import { Rectangle } from "../../../core/rectangle";
|
||||||
|
import { Vector } from "../../../core/vector";
|
||||||
|
import { T } from "../../../translations";
|
||||||
|
import { StaticMapEntityComponent } from "../../components/static_map_entity";
|
||||||
|
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
const logger = createLogger("screenshot_exporter");
|
const logger = createLogger("screenshot_exporter");
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export class HUDScreenshotExporter extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startExport() {
|
startExport() {
|
||||||
if (IS_DEMO) {
|
if (!this.root.app.restrictionMgr.getIsExportingScreenshotsPossible()) {
|
||||||
this.root.hud.parts.dialogs.showFeatureRestrictionInfo(T.demo.features.exportingBase);
|
this.root.hud.parts.dialogs.showFeatureRestrictionInfo(T.demo.features.exportingBase);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ export class HUDScreenshotExporter extends BaseHUDPart {
|
|||||||
const parameters = new DrawParameters({
|
const parameters = new DrawParameters({
|
||||||
context,
|
context,
|
||||||
visibleRect,
|
visibleRect,
|
||||||
desiredAtlasScale: chunkScale,
|
desiredAtlasScale: 0.25,
|
||||||
root: this.root,
|
root: this.root,
|
||||||
zoomLevel: chunkScale,
|
zoomLevel: chunkScale,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { ClickDetector } from "../../../core/click_detector";
|
import { ClickDetector } from "../../../core/click_detector";
|
||||||
import { InputReceiver } from "../../../core/input_receiver";
|
import { InputReceiver } from "../../../core/input_receiver";
|
||||||
import { formatBigNumber, makeDiv } from "../../../core/utils";
|
import { formatBigNumber, getRomanNumber, makeDiv } from "../../../core/utils";
|
||||||
import { T } from "../../../translations";
|
import { T } from "../../../translations";
|
||||||
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
|
import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
import { UPGRADES } from "../../upgrades";
|
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ export class HUDShop extends BaseHUDPart {
|
|||||||
this.upgradeToElements = {};
|
this.upgradeToElements = {};
|
||||||
|
|
||||||
// Upgrades
|
// Upgrades
|
||||||
for (const upgradeId in UPGRADES) {
|
for (const upgradeId in this.root.gameMode.getUpgrades()) {
|
||||||
const handle = {};
|
const handle = {};
|
||||||
handle.requireIndexToElement = [];
|
handle.requireIndexToElement = [];
|
||||||
|
|
||||||
@ -59,7 +58,7 @@ export class HUDShop extends BaseHUDPart {
|
|||||||
rerenderFull() {
|
rerenderFull() {
|
||||||
for (const upgradeId in this.upgradeToElements) {
|
for (const upgradeId in this.upgradeToElements) {
|
||||||
const handle = this.upgradeToElements[upgradeId];
|
const handle = this.upgradeToElements[upgradeId];
|
||||||
const upgradeTiers = UPGRADES[upgradeId];
|
const upgradeTiers = this.root.gameMode.getUpgrades()[upgradeId];
|
||||||
|
|
||||||
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
||||||
const currentTierMultiplier = this.root.hubGoals.upgradeImprovements[upgradeId];
|
const currentTierMultiplier = this.root.hubGoals.upgradeImprovements[upgradeId];
|
||||||
@ -68,7 +67,7 @@ export class HUDShop extends BaseHUDPart {
|
|||||||
// Set tier
|
// Set tier
|
||||||
handle.elemTierLabel.innerText = T.ingame.shop.tier.replace(
|
handle.elemTierLabel.innerText = T.ingame.shop.tier.replace(
|
||||||
"<x>",
|
"<x>",
|
||||||
"" + T.ingame.shop.tierLabels[currentTier]
|
getRomanNumber(currentTier + 1)
|
||||||
);
|
);
|
||||||
|
|
||||||
handle.elemTierLabel.setAttribute("data-tier", currentTier);
|
handle.elemTierLabel.setAttribute("data-tier", currentTier);
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { globalConfig } from "../../../core/config";
|
import { globalConfig } from "../../../core/config";
|
||||||
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
import { gMetaBuildingRegistry } from "../../../core/global_registries";
|
||||||
|
import { InputReceiver } from "../../../core/input_receiver";
|
||||||
import { makeDiv } from "../../../core/utils";
|
import { makeDiv } from "../../../core/utils";
|
||||||
import { SOUNDS } from "../../../platform/sound";
|
import { SOUNDS } from "../../../platform/sound";
|
||||||
import { T } from "../../../translations";
|
import { T } from "../../../translations";
|
||||||
import { defaultBuildingVariant } from "../../meta_building";
|
import { defaultBuildingVariant } from "../../meta_building";
|
||||||
import { enumHubGoalRewards, tutorialGoals } from "../../tutorial_goals";
|
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||||
|
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
|
||||||
import { BaseHUDPart } from "../base_hud_part";
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||||
import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings";
|
|
||||||
import { InputReceiver } from "../../../core/input_receiver";
|
|
||||||
import { enumNotificationType } from "./notifications";
|
import { enumNotificationType } from "./notifications";
|
||||||
|
|
||||||
export class HUDUnlockNotification extends BaseHUDPart {
|
export class HUDUnlockNotification extends BaseHUDPart {
|
||||||
@ -53,7 +53,9 @@ export class HUDUnlockNotification extends BaseHUDPart {
|
|||||||
showForLevel(level, reward) {
|
showForLevel(level, reward) {
|
||||||
this.root.soundProxy.playUi(SOUNDS.levelComplete);
|
this.root.soundProxy.playUi(SOUNDS.levelComplete);
|
||||||
|
|
||||||
if (level > tutorialGoals.length) {
|
const levels = this.root.gameMode.getLevelDefinitions();
|
||||||
|
// Don't use getIsFreeplay() because we want the freeplay level up to show
|
||||||
|
if (level > levels.length) {
|
||||||
this.root.hud.signals.notification.dispatch(
|
this.root.hud.signals.notification.dispatch(
|
||||||
T.ingame.notifications.freeplayLevelComplete.replace("<level>", String(level)),
|
T.ingame.notifications.freeplayLevelComplete.replace("<level>", String(level)),
|
||||||
enumNotificationType.success
|
enumNotificationType.success
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
import { makeOffscreenBuffer } from "../../../core/buffer_utils";
|
||||||
import { globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../../../core/config";
|
import { globalConfig, THIRDPARTY_URLS } from "../../../core/config";
|
||||||
import { DrawParameters } from "../../../core/draw_parameters";
|
import { DrawParameters } from "../../../core/draw_parameters";
|
||||||
import { Loader } from "../../../core/loader";
|
import { Loader } from "../../../core/loader";
|
||||||
import { DialogWithForm } from "../../../core/modal_dialog_elements";
|
import { DialogWithForm } from "../../../core/modal_dialog_elements";
|
||||||
@ -302,7 +302,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
// Show info that you can have only N markers in the demo,
|
// Show info that you can have only N markers in the demo,
|
||||||
// actually show this *after* entering the name so you want the
|
// actually show this *after* entering the name so you want the
|
||||||
// standalone even more (I'm evil :P)
|
// standalone even more (I'm evil :P)
|
||||||
if (IS_DEMO && this.waypoints.length > 2) {
|
if (this.waypoints.length > this.root.app.restrictionMgr.getMaximumWaypoints()) {
|
||||||
this.root.hud.parts.dialogs.showFeatureRestrictionInfo(
|
this.root.hud.parts.dialogs.showFeatureRestrictionInfo(
|
||||||
"",
|
"",
|
||||||
T.dialogs.markerDemoLimit.desc
|
T.dialogs.markerDemoLimit.desc
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { MetaComparatorBuilding } from "../../buildings/comparator";
|
|||||||
import { MetaReaderBuilding } from "../../buildings/reader";
|
import { MetaReaderBuilding } from "../../buildings/reader";
|
||||||
import { MetaFilterBuilding } from "../../buildings/filter";
|
import { MetaFilterBuilding } from "../../buildings/filter";
|
||||||
import { MetaDisplayBuilding } from "../../buildings/display";
|
import { MetaDisplayBuilding } from "../../buildings/display";
|
||||||
|
import { MetaStorageBuilding } from "../../buildings/storage";
|
||||||
|
|
||||||
export class HUDWiresToolbar extends HUDBaseToolbar {
|
export class HUDWiresToolbar extends HUDBaseToolbar {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
@ -26,6 +27,7 @@ export class HUDWiresToolbar extends HUDBaseToolbar {
|
|||||||
MetaTransistorBuilding,
|
MetaTransistorBuilding,
|
||||||
],
|
],
|
||||||
secondaryBuildings: [
|
secondaryBuildings: [
|
||||||
|
MetaStorageBuilding,
|
||||||
MetaReaderBuilding,
|
MetaReaderBuilding,
|
||||||
MetaLeverBuilding,
|
MetaLeverBuilding,
|
||||||
MetaFilterBuilding,
|
MetaFilterBuilding,
|
||||||
|
|||||||
@ -122,6 +122,7 @@ export const KEYCODE_RMB = 3;
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getStringForKeyCode(code) {
|
export function getStringForKeyCode(code) {
|
||||||
|
// @todo: Refactor into dictionary
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case KEYCODE_LMB:
|
case KEYCODE_LMB:
|
||||||
return "LMB";
|
return "LMB";
|
||||||
|
|||||||
@ -104,15 +104,17 @@ export class MapChunkView extends MapChunk {
|
|||||||
});
|
});
|
||||||
|
|
||||||
parameters.context.imageSmoothingEnabled = true;
|
parameters.context.imageSmoothingEnabled = true;
|
||||||
|
const resourcesScale = this.root.app.settings.getAllSettings().mapResourcesScale;
|
||||||
|
|
||||||
// Draw patch items
|
// Draw patch items
|
||||||
if (this.root.currentLayer === "regular") {
|
if (this.root.currentLayer === "regular" && resourcesScale > 0.05) {
|
||||||
|
const diameter = (70 / Math.pow(parameters.zoomLevel, 0.35)) * (0.2 + 2 * resourcesScale);
|
||||||
|
|
||||||
for (let i = 0; i < this.patches.length; ++i) {
|
for (let i = 0; i < this.patches.length; ++i) {
|
||||||
const patch = this.patches[i];
|
const patch = this.patches[i];
|
||||||
if (patch.item.getItemType() === "shape") {
|
if (patch.item.getItemType() === "shape") {
|
||||||
const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
|
const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
|
||||||
const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
|
const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
|
||||||
const diameter = 80 / Math.pow(parameters.zoomLevel, 0.35);
|
|
||||||
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
480
src/js/game/modes/regular.js
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
import { findNiceIntegerValue } from "../../core/utils";
|
||||||
|
import { GameMode } from "../game_mode";
|
||||||
|
import { ShapeDefinition } from "../shape_definition";
|
||||||
|
import { enumHubGoalRewards } from "../tutorial_goals";
|
||||||
|
|
||||||
|
const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
||||||
|
const finalGameShape = "RuCw--Cw:----Ru--";
|
||||||
|
const preparementShape = "CpRpCp--:SwSwSwSw";
|
||||||
|
const blueprintShape = "CbCbCbRb:CwCwCwCw";
|
||||||
|
|
||||||
|
// Tiers need % of the previous tier as requirement too
|
||||||
|
const tierGrowth = 2.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates all upgrades
|
||||||
|
* @returns {Object<string, import("../game_mode").UpgradeTiers>} */
|
||||||
|
function generateUpgrades(limitedVersion = false) {
|
||||||
|
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
||||||
|
const numEndgameUpgrades = limitedVersion ? 0 : 1000 - fixedImprovements.length - 1;
|
||||||
|
|
||||||
|
function generateInfiniteUnlocks() {
|
||||||
|
return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
|
||||||
|
required: [
|
||||||
|
{ shape: preparementShape, amount: 30000 + i * 10000 },
|
||||||
|
{ shape: finalGameShape, amount: 20000 + i * 5000 },
|
||||||
|
{ shape: rocketShape, amount: 20000 + i * 5000 },
|
||||||
|
],
|
||||||
|
excludePrevious: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in endgame upgrades
|
||||||
|
for (let i = 0; i < numEndgameUpgrades; ++i) {
|
||||||
|
if (i < 20) {
|
||||||
|
fixedImprovements.push(0.1);
|
||||||
|
} else if (i < 50) {
|
||||||
|
fixedImprovements.push(0.05);
|
||||||
|
} else if (i < 100) {
|
||||||
|
fixedImprovements.push(0.025);
|
||||||
|
} else {
|
||||||
|
fixedImprovements.push(0.0125);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgrades = {
|
||||||
|
belt: [
|
||||||
|
{
|
||||||
|
required: [{ shape: "CuCuCuCu", amount: 30 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "--CuCu--", amount: 500 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "CpCpCpCp", amount: 1000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: preparementShape, amount: 25000 }],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [
|
||||||
|
{ shape: preparementShape, amount: 25000 },
|
||||||
|
{ shape: finalGameShape, amount: 50000 },
|
||||||
|
],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
...generateInfiniteUnlocks(),
|
||||||
|
],
|
||||||
|
|
||||||
|
miner: [
|
||||||
|
{
|
||||||
|
required: [{ shape: "RuRuRuRu", amount: 300 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "Cu------", amount: 800 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "ScScScSc", amount: 3500 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: preparementShape, amount: 25000 }],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [
|
||||||
|
{ shape: preparementShape, amount: 25000 },
|
||||||
|
{ shape: finalGameShape, amount: 50000 },
|
||||||
|
],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
...generateInfiniteUnlocks(),
|
||||||
|
],
|
||||||
|
|
||||||
|
processors: [
|
||||||
|
{
|
||||||
|
required: [{ shape: "SuSuSuSu", amount: 500 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "RuRu----", amount: 600 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "CgScScCg", amount: 3500 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: preparementShape, amount: 25000 }],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [
|
||||||
|
{ shape: preparementShape, amount: 25000 },
|
||||||
|
{ shape: finalGameShape, amount: 50000 },
|
||||||
|
],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
...generateInfiniteUnlocks(),
|
||||||
|
],
|
||||||
|
|
||||||
|
painting: [
|
||||||
|
{
|
||||||
|
required: [{ shape: "RbRb----", amount: 600 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "WrWrWrWr", amount: 3800 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [{ shape: preparementShape, amount: 25000 }],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: [
|
||||||
|
{ shape: preparementShape, amount: 25000 },
|
||||||
|
{ shape: finalGameShape, amount: 50000 },
|
||||||
|
],
|
||||||
|
excludePrevious: true,
|
||||||
|
},
|
||||||
|
...generateInfiniteUnlocks(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatically generate tier levels
|
||||||
|
for (const upgradeId in upgrades) {
|
||||||
|
const upgradeTiers = upgrades[upgradeId];
|
||||||
|
|
||||||
|
let currentTierRequirements = [];
|
||||||
|
for (let i = 0; i < upgradeTiers.length; ++i) {
|
||||||
|
const tierHandle = upgradeTiers[i];
|
||||||
|
tierHandle.improvement = fixedImprovements[i];
|
||||||
|
const originalRequired = tierHandle.required.slice();
|
||||||
|
|
||||||
|
for (let k = currentTierRequirements.length - 1; k >= 0; --k) {
|
||||||
|
const oldTierRequirement = currentTierRequirements[k];
|
||||||
|
if (!tierHandle.excludePrevious) {
|
||||||
|
tierHandle.required.unshift({
|
||||||
|
shape: oldTierRequirement.shape,
|
||||||
|
amount: oldTierRequirement.amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentTierRequirements.push(
|
||||||
|
...originalRequired.map(req => ({
|
||||||
|
amount: req.amount,
|
||||||
|
shape: req.shape,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
currentTierRequirements.forEach(tier => {
|
||||||
|
tier.amount = findNiceIntegerValue(tier.amount * tierGrowth);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALIDATE
|
||||||
|
if (G_IS_DEV) {
|
||||||
|
for (const upgradeId in upgrades) {
|
||||||
|
upgrades[upgradeId].forEach(tier => {
|
||||||
|
tier.required.forEach(({ shape }) => {
|
||||||
|
try {
|
||||||
|
ShapeDefinition.fromShortKey(shape);
|
||||||
|
} catch (ex) {
|
||||||
|
throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return upgrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the level definitions
|
||||||
|
* @param {boolean} limitedVersion
|
||||||
|
*/
|
||||||
|
export function generateLevelDefinitions(limitedVersion = false) {
|
||||||
|
const levelDefinitions = [
|
||||||
|
// 1
|
||||||
|
// Circle
|
||||||
|
{
|
||||||
|
shape: "CuCuCuCu", // belts t1
|
||||||
|
required: 30,
|
||||||
|
reward: enumHubGoalRewards.reward_cutter_and_trash,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2
|
||||||
|
// Cutter
|
||||||
|
{
|
||||||
|
shape: "----CuCu", //
|
||||||
|
required: 40,
|
||||||
|
reward: enumHubGoalRewards.no_reward,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 3
|
||||||
|
// Rectangle
|
||||||
|
{
|
||||||
|
shape: "RuRuRuRu", // miners t1
|
||||||
|
required: 70,
|
||||||
|
reward: enumHubGoalRewards.reward_balancer,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 4
|
||||||
|
{
|
||||||
|
shape: "RuRu----", // processors t2
|
||||||
|
required: 70,
|
||||||
|
reward: enumHubGoalRewards.reward_rotater,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 5
|
||||||
|
// Rotater
|
||||||
|
{
|
||||||
|
shape: "Cu----Cu", // belts t2
|
||||||
|
required: 170,
|
||||||
|
reward: enumHubGoalRewards.reward_tunnel,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 6
|
||||||
|
{
|
||||||
|
shape: "Cu------", // miners t2
|
||||||
|
required: 270,
|
||||||
|
reward: enumHubGoalRewards.reward_painter,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 7
|
||||||
|
// Painter
|
||||||
|
{
|
||||||
|
shape: "CrCrCrCr", // unused
|
||||||
|
required: 300,
|
||||||
|
reward: enumHubGoalRewards.reward_rotater_ccw,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 8
|
||||||
|
{
|
||||||
|
shape: "RbRb----", // painter t2
|
||||||
|
required: 480,
|
||||||
|
reward: enumHubGoalRewards.reward_mixer,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 9
|
||||||
|
// Mixing (purple)
|
||||||
|
{
|
||||||
|
shape: "CpCpCpCp", // belts t3
|
||||||
|
required: 600,
|
||||||
|
reward: enumHubGoalRewards.reward_merger,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 10
|
||||||
|
// STACKER: Star shape + cyan
|
||||||
|
{
|
||||||
|
shape: "ScScScSc", // miners t3
|
||||||
|
required: 800,
|
||||||
|
reward: enumHubGoalRewards.reward_stacker,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 11
|
||||||
|
// Chainable miner
|
||||||
|
{
|
||||||
|
shape: "CgScScCg", // processors t3
|
||||||
|
required: 1000,
|
||||||
|
reward: enumHubGoalRewards.reward_miner_chainable,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 12
|
||||||
|
// Blueprints
|
||||||
|
{
|
||||||
|
shape: "CbCbCbRb:CwCwCwCw",
|
||||||
|
required: 1000,
|
||||||
|
reward: enumHubGoalRewards.reward_blueprints,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 13
|
||||||
|
// Tunnel Tier 2
|
||||||
|
{
|
||||||
|
shape: "RpRpRpRp:CwCwCwCw", // painting t3
|
||||||
|
required: 3800,
|
||||||
|
reward: enumHubGoalRewards.reward_underground_belt_tier_2,
|
||||||
|
},
|
||||||
|
|
||||||
|
// DEMO STOPS HERE
|
||||||
|
...(limitedVersion
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
shape: "RpRpRpRp:CwCwCwCw",
|
||||||
|
required: 0,
|
||||||
|
reward: enumHubGoalRewards.reward_demo_end,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
// 14
|
||||||
|
// Belt reader
|
||||||
|
{
|
||||||
|
shape: "--Cg----:--Cr----", // unused
|
||||||
|
required: 16, // Per second!
|
||||||
|
reward: enumHubGoalRewards.reward_belt_reader,
|
||||||
|
throughputOnly: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 15
|
||||||
|
// Storage
|
||||||
|
{
|
||||||
|
shape: "SrSrSrSr:CyCyCyCy", // unused
|
||||||
|
required: 10000,
|
||||||
|
reward: enumHubGoalRewards.reward_storage,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 16
|
||||||
|
// Quad Cutter
|
||||||
|
{
|
||||||
|
shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
|
||||||
|
required: 6000,
|
||||||
|
reward: enumHubGoalRewards.reward_cutter_quad,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 17
|
||||||
|
// Double painter
|
||||||
|
{
|
||||||
|
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
|
||||||
|
required: 20000,
|
||||||
|
reward: enumHubGoalRewards.reward_painter_double,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 18
|
||||||
|
// Rotater (180deg)
|
||||||
|
{
|
||||||
|
shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
|
||||||
|
required: 20000,
|
||||||
|
reward: enumHubGoalRewards.reward_rotater_180,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 19
|
||||||
|
// Compact splitter
|
||||||
|
{
|
||||||
|
shape: "CpRpCp--:SwSwSwSw",
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_splitter,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 20
|
||||||
|
// WIRES
|
||||||
|
{
|
||||||
|
shape: finalGameShape,
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_wires_painter_and_levers,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 21
|
||||||
|
// Filter
|
||||||
|
{
|
||||||
|
shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr",
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_filter,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 22
|
||||||
|
// Constant signal
|
||||||
|
{
|
||||||
|
shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_constant_signal,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 23
|
||||||
|
// Display
|
||||||
|
{
|
||||||
|
shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_display,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 24 Logic gates
|
||||||
|
{
|
||||||
|
shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_logic_gates,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 25 Virtual Processing
|
||||||
|
{
|
||||||
|
shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
|
||||||
|
required: 25000,
|
||||||
|
reward: enumHubGoalRewards.reward_virtual_processing,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 26 Freeplay
|
||||||
|
{
|
||||||
|
shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
|
||||||
|
required: 50000,
|
||||||
|
reward: enumHubGoalRewards.reward_freeplay,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (G_IS_DEV) {
|
||||||
|
levelDefinitions.forEach(({ shape }) => {
|
||||||
|
try {
|
||||||
|
ShapeDefinition.fromShortKey(shape);
|
||||||
|
} catch (ex) {
|
||||||
|
throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return levelDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullVersionUpgrades = generateUpgrades(false);
|
||||||
|
const demoVersionUpgrades = generateUpgrades(true);
|
||||||
|
|
||||||
|
const fullVersionLevels = generateLevelDefinitions(false);
|
||||||
|
const demoVersionLevels = generateLevelDefinitions(true);
|
||||||
|
|
||||||
|
export class RegularGameMode extends GameMode {
|
||||||
|
constructor(root) {
|
||||||
|
super(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpgrades() {
|
||||||
|
return this.root.app.restrictionMgr.getHasExtendedUpgrades()
|
||||||
|
? fullVersionUpgrades
|
||||||
|
: demoVersionUpgrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsFreeplayAvailable() {
|
||||||
|
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlueprintShapeKey() {
|
||||||
|
return blueprintShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLevelDefinitions() {
|
||||||
|
return this.root.app.restrictionMgr.getHasExtendedLevelsAndFreeplay()
|
||||||
|
? fullVersionLevels
|
||||||
|
: demoVersionLevels;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,221 +1,225 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import { Signal } from "../core/signal";
|
import { Signal } from "../core/signal";
|
||||||
import { RandomNumberGenerator } from "../core/rng";
|
import { RandomNumberGenerator } from "../core/rng";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
|
|
||||||
// Type hints
|
// Type hints
|
||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
import { GameTime } from "./time/game_time";
|
import { GameTime } from "./time/game_time";
|
||||||
import { EntityManager } from "./entity_manager";
|
import { EntityManager } from "./entity_manager";
|
||||||
import { GameSystemManager } from "./game_system_manager";
|
import { GameSystemManager } from "./game_system_manager";
|
||||||
import { GameHUD } from "./hud/hud";
|
import { GameHUD } from "./hud/hud";
|
||||||
import { MapView } from "./map_view";
|
import { MapView } from "./map_view";
|
||||||
import { Camera } from "./camera";
|
import { Camera } from "./camera";
|
||||||
import { InGameState } from "../states/ingame";
|
import { InGameState } from "../states/ingame";
|
||||||
import { AutomaticSave } from "./automatic_save";
|
import { AutomaticSave } from "./automatic_save";
|
||||||
import { Application } from "../application";
|
import { Application } from "../application";
|
||||||
import { SoundProxy } from "./sound_proxy";
|
import { SoundProxy } from "./sound_proxy";
|
||||||
import { Savegame } from "../savegame/savegame";
|
import { Savegame } from "../savegame/savegame";
|
||||||
import { GameLogic } from "./logic";
|
import { GameLogic } from "./logic";
|
||||||
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
import { ShapeDefinitionManager } from "./shape_definition_manager";
|
||||||
import { HubGoals } from "./hub_goals";
|
import { HubGoals } from "./hub_goals";
|
||||||
import { BufferMaintainer } from "../core/buffer_maintainer";
|
import { BufferMaintainer } from "../core/buffer_maintainer";
|
||||||
import { ProductionAnalytics } from "./production_analytics";
|
import { ProductionAnalytics } from "./production_analytics";
|
||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
import { ShapeDefinition } from "./shape_definition";
|
import { ShapeDefinition } from "./shape_definition";
|
||||||
import { BaseItem } from "./base_item";
|
import { BaseItem } from "./base_item";
|
||||||
import { DynamicTickrate } from "./dynamic_tickrate";
|
import { DynamicTickrate } from "./dynamic_tickrate";
|
||||||
import { KeyActionMapper } from "./key_action_mapper";
|
import { KeyActionMapper } from "./key_action_mapper";
|
||||||
import { Vector } from "../core/vector";
|
import { Vector } from "../core/vector";
|
||||||
/* typehints:end */
|
import { GameMode } from "./game_mode";
|
||||||
|
/* typehints:end */
|
||||||
const logger = createLogger("game/root");
|
|
||||||
|
const logger = createLogger("game/root");
|
||||||
/** @type {Array<Layer>} */
|
|
||||||
export const layers = ["regular", "wires"];
|
/** @type {Array<Layer>} */
|
||||||
|
export const layers = ["regular", "wires"];
|
||||||
/**
|
|
||||||
* The game root is basically the whole game state at a given point,
|
/**
|
||||||
* combining all important classes. We don't have globals, but this
|
* The game root is basically the whole game state at a given point,
|
||||||
* class is passed to almost all game classes.
|
* combining all important classes. We don't have globals, but this
|
||||||
*/
|
* class is passed to almost all game classes.
|
||||||
export class GameRoot {
|
*/
|
||||||
/**
|
export class GameRoot {
|
||||||
* Constructs a new game root
|
/**
|
||||||
* @param {Application} app
|
* Constructs a new game root
|
||||||
*/
|
* @param {Application} app
|
||||||
constructor(app) {
|
*/
|
||||||
this.app = app;
|
constructor(app) {
|
||||||
|
this.app = app;
|
||||||
/** @type {Savegame} */
|
|
||||||
this.savegame = null;
|
/** @type {Savegame} */
|
||||||
|
this.savegame = null;
|
||||||
/** @type {InGameState} */
|
|
||||||
this.gameState = null;
|
/** @type {InGameState} */
|
||||||
|
this.gameState = null;
|
||||||
/** @type {KeyActionMapper} */
|
|
||||||
this.keyMapper = null;
|
/** @type {KeyActionMapper} */
|
||||||
|
this.keyMapper = null;
|
||||||
// Store game dimensions
|
|
||||||
this.gameWidth = 500;
|
// Store game dimensions
|
||||||
this.gameHeight = 500;
|
this.gameWidth = 500;
|
||||||
|
this.gameHeight = 500;
|
||||||
// Stores whether the current session is a fresh game (true), or was continued (false)
|
|
||||||
/** @type {boolean} */
|
// Stores whether the current session is a fresh game (true), or was continued (false)
|
||||||
this.gameIsFresh = true;
|
/** @type {boolean} */
|
||||||
|
this.gameIsFresh = true;
|
||||||
// Stores whether the logic is already initialized
|
|
||||||
/** @type {boolean} */
|
// Stores whether the logic is already initialized
|
||||||
this.logicInitialized = false;
|
/** @type {boolean} */
|
||||||
|
this.logicInitialized = false;
|
||||||
// Stores whether the game is already initialized, that is, all systems etc have been created
|
|
||||||
/** @type {boolean} */
|
// Stores whether the game is already initialized, that is, all systems etc have been created
|
||||||
this.gameInitialized = false;
|
/** @type {boolean} */
|
||||||
|
this.gameInitialized = false;
|
||||||
/**
|
|
||||||
* Whether a bulk operation is running
|
/**
|
||||||
*/
|
* Whether a bulk operation is running
|
||||||
this.bulkOperationRunning = false;
|
*/
|
||||||
|
this.bulkOperationRunning = false;
|
||||||
//////// Other properties ///////
|
|
||||||
|
//////// Other properties ///////
|
||||||
/** @type {Camera} */
|
|
||||||
this.camera = null;
|
/** @type {Camera} */
|
||||||
|
this.camera = null;
|
||||||
/** @type {HTMLCanvasElement} */
|
|
||||||
this.canvas = null;
|
/** @type {HTMLCanvasElement} */
|
||||||
|
this.canvas = null;
|
||||||
/** @type {CanvasRenderingContext2D} */
|
|
||||||
this.context = null;
|
/** @type {CanvasRenderingContext2D} */
|
||||||
|
this.context = null;
|
||||||
/** @type {MapView} */
|
|
||||||
this.map = null;
|
/** @type {MapView} */
|
||||||
|
this.map = null;
|
||||||
/** @type {GameLogic} */
|
|
||||||
this.logic = null;
|
/** @type {GameLogic} */
|
||||||
|
this.logic = null;
|
||||||
/** @type {EntityManager} */
|
|
||||||
this.entityMgr = null;
|
/** @type {EntityManager} */
|
||||||
|
this.entityMgr = null;
|
||||||
/** @type {GameHUD} */
|
|
||||||
this.hud = null;
|
/** @type {GameHUD} */
|
||||||
|
this.hud = null;
|
||||||
/** @type {GameSystemManager} */
|
|
||||||
this.systemMgr = null;
|
/** @type {GameSystemManager} */
|
||||||
|
this.systemMgr = null;
|
||||||
/** @type {GameTime} */
|
|
||||||
this.time = null;
|
/** @type {GameTime} */
|
||||||
|
this.time = null;
|
||||||
/** @type {HubGoals} */
|
|
||||||
this.hubGoals = null;
|
/** @type {HubGoals} */
|
||||||
|
this.hubGoals = null;
|
||||||
/** @type {BufferMaintainer} */
|
|
||||||
this.buffers = null;
|
/** @type {BufferMaintainer} */
|
||||||
|
this.buffers = null;
|
||||||
/** @type {AutomaticSave} */
|
|
||||||
this.automaticSave = null;
|
/** @type {AutomaticSave} */
|
||||||
|
this.automaticSave = null;
|
||||||
/** @type {SoundProxy} */
|
|
||||||
this.soundProxy = null;
|
/** @type {SoundProxy} */
|
||||||
|
this.soundProxy = null;
|
||||||
/** @type {ShapeDefinitionManager} */
|
|
||||||
this.shapeDefinitionMgr = null;
|
/** @type {ShapeDefinitionManager} */
|
||||||
|
this.shapeDefinitionMgr = null;
|
||||||
/** @type {ProductionAnalytics} */
|
|
||||||
this.productionAnalytics = null;
|
/** @type {ProductionAnalytics} */
|
||||||
|
this.productionAnalytics = null;
|
||||||
/** @type {DynamicTickrate} */
|
|
||||||
this.dynamicTickrate = null;
|
/** @type {DynamicTickrate} */
|
||||||
|
this.dynamicTickrate = null;
|
||||||
/** @type {Layer} */
|
|
||||||
this.currentLayer = "regular";
|
/** @type {Layer} */
|
||||||
|
this.currentLayer = "regular";
|
||||||
this.signals = {
|
|
||||||
// Entities
|
/** @type {GameMode} */
|
||||||
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
this.gameMode = null;
|
||||||
entityAdded: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
|
||||||
entityChanged: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
this.signals = {
|
||||||
entityGotNewComponent: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
// Entities
|
||||||
entityComponentRemoved: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
entityManuallyPlaced: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
entityQueuedForDestroy: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
entityAdded: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
entityDestroyed: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
entityChanged: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
|
entityGotNewComponent: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
// Global
|
entityComponentRemoved: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
resized: /** @type {TypedSignal<[number, number]>} */ (new Signal()),
|
entityQueuedForDestroy: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
readyToRender: /** @type {TypedSignal<[]>} */ (new Signal()),
|
entityDestroyed: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
aboutToDestruct: /** @type {TypedSignal<[]>} */ new Signal(),
|
|
||||||
|
// Global
|
||||||
// Game Hooks
|
resized: /** @type {TypedSignal<[number, number]>} */ (new Signal()),
|
||||||
gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved
|
readyToRender: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
|
aboutToDestruct: /** @type {TypedSignal<[]>} */ new Signal(),
|
||||||
|
|
||||||
gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
|
// Game Hooks
|
||||||
|
gameSaved: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got saved
|
||||||
storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()),
|
gameRestored: /** @type {TypedSignal<[]>} */ (new Signal()), // Game got restored
|
||||||
upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()),
|
|
||||||
|
gameFrameStarted: /** @type {TypedSignal<[]>} */ (new Signal()), // New frame
|
||||||
// Called right after game is initialized
|
|
||||||
postLoadHook: /** @type {TypedSignal<[]>} */ (new Signal()),
|
storyGoalCompleted: /** @type {TypedSignal<[number, string]>} */ (new Signal()),
|
||||||
|
upgradePurchased: /** @type {TypedSignal<[string]>} */ (new Signal()),
|
||||||
shapeDelivered: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
|
|
||||||
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
// Called right after game is initialized
|
||||||
|
postLoadHook: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
|
||||||
|
shapeDelivered: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
|
||||||
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
itemProduced: /** @type {TypedSignal<[BaseItem]>} */ (new Signal()),
|
||||||
|
|
||||||
// Called to check if an entity can be placed, second parameter is an additional offset.
|
bulkOperationFinished: /** @type {TypedSignal<[]>} */ (new Signal()),
|
||||||
// Use to introduce additional placement checks
|
|
||||||
prePlacementCheck: /** @type {TypedSignal<[Entity, Vector]>} */ (new Signal()),
|
editModeChanged: /** @type {TypedSignal<[Layer]>} */ (new Signal()),
|
||||||
|
|
||||||
// Called before actually placing an entity, use to perform additional logic
|
// Called to check if an entity can be placed, second parameter is an additional offset.
|
||||||
// for freeing space before actually placing.
|
// Use to introduce additional placement checks
|
||||||
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
prePlacementCheck: /** @type {TypedSignal<[Entity, Vector]>} */ (new Signal()),
|
||||||
};
|
|
||||||
|
// Called before actually placing an entity, use to perform additional logic
|
||||||
// RNG's
|
// for freeing space before actually placing.
|
||||||
/** @type {Object.<string, Object.<string, RandomNumberGenerator>>} */
|
freeEntityAreaBeforeBuild: /** @type {TypedSignal<[Entity]>} */ (new Signal()),
|
||||||
this.rngs = {};
|
};
|
||||||
|
|
||||||
// Work queue
|
// RNG's
|
||||||
this.queue = {
|
/** @type {Object.<string, Object.<string, RandomNumberGenerator>>} */
|
||||||
requireRedraw: false,
|
this.rngs = {};
|
||||||
};
|
|
||||||
}
|
// Work queue
|
||||||
|
this.queue = {
|
||||||
/**
|
requireRedraw: false,
|
||||||
* Destructs the game root
|
};
|
||||||
*/
|
}
|
||||||
destruct() {
|
|
||||||
logger.log("destructing root");
|
/**
|
||||||
this.signals.aboutToDestruct.dispatch();
|
* Destructs the game root
|
||||||
|
*/
|
||||||
this.reset();
|
destruct() {
|
||||||
}
|
logger.log("destructing root");
|
||||||
|
this.signals.aboutToDestruct.dispatch();
|
||||||
/**
|
|
||||||
* Resets the whole root and removes all properties
|
this.reset();
|
||||||
*/
|
}
|
||||||
reset() {
|
|
||||||
if (this.signals) {
|
/**
|
||||||
// Destruct all signals
|
* Resets the whole root and removes all properties
|
||||||
for (let i = 0; i < this.signals.length; ++i) {
|
*/
|
||||||
this.signals[i].removeAll();
|
reset() {
|
||||||
}
|
if (this.signals) {
|
||||||
}
|
// Destruct all signals
|
||||||
|
for (let i = 0; i < this.signals.length; ++i) {
|
||||||
if (this.hud) {
|
this.signals[i].removeAll();
|
||||||
this.hud.cleanup();
|
}
|
||||||
}
|
}
|
||||||
if (this.camera) {
|
|
||||||
this.camera.cleanup();
|
if (this.hud) {
|
||||||
}
|
this.hud.cleanup();
|
||||||
|
}
|
||||||
// Finally free all properties
|
if (this.camera) {
|
||||||
for (let prop in this) {
|
this.camera.cleanup();
|
||||||
if (this.hasOwnProperty(prop)) {
|
}
|
||||||
delete this[prop];
|
|
||||||
}
|
// Finally free all properties
|
||||||
}
|
for (let prop in this) {
|
||||||
}
|
if (this.hasOwnProperty(prop)) {
|
||||||
}
|
delete this[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { GameSystemWithFilter } from "../game_system_with_filter";
|
|||||||
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
import { BOOL_FALSE_SINGLETON, BOOL_TRUE_SINGLETON } from "../items/boolean_item";
|
||||||
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
import { COLOR_ITEM_SINGLETONS } from "../items/color_item";
|
||||||
import { ShapeDefinition } from "../shape_definition";
|
import { ShapeDefinition } from "../shape_definition";
|
||||||
import { blueprintShape } from "../upgrades";
|
|
||||||
|
|
||||||
export class ConstantSignalSystem extends GameSystemWithFilter {
|
export class ConstantSignalSystem extends GameSystemWithFilter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
@ -61,7 +60,9 @@ export class ConstantSignalSystem extends GameSystemWithFilter {
|
|||||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
|
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(
|
||||||
this.root.hubGoals.currentGoal.definition
|
this.root.hubGoals.currentGoal.definition
|
||||||
),
|
),
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(blueprintShape),
|
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(
|
||||||
|
this.root.gameMode.getBlueprintShapeKey()
|
||||||
|
),
|
||||||
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
|
...this.root.hud.parts.pinnedShapes.pinnedShapes.map(key =>
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
|
this.root.shapeDefinitionMgr.getShapeItemFromShortKey(key)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { globalConfig, IS_DEMO } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
import { smoothenDpi } from "../../core/dpi_manager";
|
import { smoothenDpi } from "../../core/dpi_manager";
|
||||||
import { DrawParameters } from "../../core/draw_parameters";
|
import { DrawParameters } from "../../core/draw_parameters";
|
||||||
import { drawSpriteClipped } from "../../core/draw_utils";
|
import { drawSpriteClipped } from "../../core/draw_utils";
|
||||||
|
|||||||
@ -154,22 +154,18 @@ export class LogicGateSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<BaseItem|null>} parameters
|
* @param {Array<BaseItem|null>} parameters
|
||||||
* @returns {[BaseItem, BaseItem]}
|
* @returns {BaseItem}
|
||||||
*/
|
*/
|
||||||
compute_ROTATE(parameters) {
|
compute_ROTATE(parameters) {
|
||||||
const item = parameters[0];
|
const item = parameters[0];
|
||||||
if (!item || item.getItemType() !== "shape") {
|
if (!item || item.getItemType() !== "shape") {
|
||||||
// Not a shape
|
// Not a shape
|
||||||
return [null, null];
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const definition = /** @type {ShapeItem} */ (item).definition;
|
const definition = /** @type {ShapeItem} */ (item).definition;
|
||||||
const rotatedDefinitionCCW = this.root.shapeDefinitionMgr.shapeActionRotateCCW(definition);
|
|
||||||
const rotatedDefinitionCW = this.root.shapeDefinitionMgr.shapeActionRotateCW(definition);
|
const rotatedDefinitionCW = this.root.shapeDefinitionMgr.shapeActionRotateCW(definition);
|
||||||
return [
|
return this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinitionCW);
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinitionCCW),
|
|
||||||
this.root.shapeDefinitionMgr.getShapeItemFromDefinition(rotatedDefinitionCW),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -32,7 +32,8 @@
|
|||||||
},
|
},
|
||||||
"chunkOverview": {
|
"chunkOverview": {
|
||||||
"empty": "#444856",
|
"empty": "#444856",
|
||||||
"filled": "#646b7d"
|
"filled": "#646b7d",
|
||||||
|
"beltColor": "#9096a3"
|
||||||
},
|
},
|
||||||
|
|
||||||
"wires": {
|
"wires": {
|
||||||
|
|||||||
@ -33,7 +33,8 @@
|
|||||||
|
|
||||||
"chunkOverview": {
|
"chunkOverview": {
|
||||||
"empty": "#a6afbb",
|
"empty": "#a6afbb",
|
||||||
"filled": "#c5ccd6"
|
"filled": "#c5ccd6",
|
||||||
|
"beltColor": "#777"
|
||||||
},
|
},
|
||||||
|
|
||||||
"wires": {
|
"wires": {
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
import { IS_DEMO } from "../core/config";
|
|
||||||
import { ShapeDefinition } from "./shape_definition";
|
|
||||||
import { finalGameShape } from "./upgrades";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't forget to also update tutorial_goals_mappings.js as well as the translations!
|
* Don't forget to also update tutorial_goals_mappings.js as well as the translations!
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
@ -40,229 +36,3 @@ export const enumHubGoalRewards = {
|
|||||||
no_reward: "no_reward",
|
no_reward: "no_reward",
|
||||||
no_reward_freeplay: "no_reward_freeplay",
|
no_reward_freeplay: "no_reward_freeplay",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tutorialGoals = [
|
|
||||||
// 1
|
|
||||||
// Circle
|
|
||||||
{
|
|
||||||
shape: "CuCuCuCu", // belts t1
|
|
||||||
required: 30,
|
|
||||||
reward: enumHubGoalRewards.reward_cutter_and_trash,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 2
|
|
||||||
// Cutter
|
|
||||||
{
|
|
||||||
shape: "----CuCu", //
|
|
||||||
required: 40,
|
|
||||||
reward: enumHubGoalRewards.no_reward,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 3
|
|
||||||
// Rectangle
|
|
||||||
{
|
|
||||||
shape: "RuRuRuRu", // miners t1
|
|
||||||
required: 70,
|
|
||||||
reward: enumHubGoalRewards.reward_balancer,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 4
|
|
||||||
{
|
|
||||||
shape: "RuRu----", // processors t2
|
|
||||||
required: 70,
|
|
||||||
reward: enumHubGoalRewards.reward_rotater,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 5
|
|
||||||
// Rotater
|
|
||||||
{
|
|
||||||
shape: "Cu----Cu", // belts t2
|
|
||||||
required: 170,
|
|
||||||
reward: enumHubGoalRewards.reward_tunnel,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 6
|
|
||||||
{
|
|
||||||
shape: "Cu------", // miners t2
|
|
||||||
required: 270,
|
|
||||||
reward: enumHubGoalRewards.reward_painter,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 7
|
|
||||||
// Painter
|
|
||||||
{
|
|
||||||
shape: "CrCrCrCr", // unused
|
|
||||||
required: 300,
|
|
||||||
reward: enumHubGoalRewards.reward_rotater_ccw,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 8
|
|
||||||
{
|
|
||||||
shape: "RbRb----", // painter t2
|
|
||||||
required: 480,
|
|
||||||
reward: enumHubGoalRewards.reward_mixer,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 9
|
|
||||||
// Mixing (purple)
|
|
||||||
{
|
|
||||||
shape: "CpCpCpCp", // belts t3
|
|
||||||
required: 600,
|
|
||||||
reward: enumHubGoalRewards.reward_merger,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 10
|
|
||||||
// STACKER: Star shape + cyan
|
|
||||||
{
|
|
||||||
shape: "ScScScSc", // miners t3
|
|
||||||
required: 800,
|
|
||||||
reward: enumHubGoalRewards.reward_stacker,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 11
|
|
||||||
// Chainable miner
|
|
||||||
{
|
|
||||||
shape: "CgScScCg", // processors t3
|
|
||||||
required: 1000,
|
|
||||||
reward: enumHubGoalRewards.reward_miner_chainable,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 12
|
|
||||||
// Blueprints
|
|
||||||
{
|
|
||||||
shape: "CbCbCbRb:CwCwCwCw",
|
|
||||||
required: 1000,
|
|
||||||
reward: enumHubGoalRewards.reward_blueprints,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 13
|
|
||||||
// Tunnel Tier 2
|
|
||||||
{
|
|
||||||
shape: "RpRpRpRp:CwCwCwCw", // painting t3
|
|
||||||
required: 3800,
|
|
||||||
reward: enumHubGoalRewards.reward_underground_belt_tier_2,
|
|
||||||
},
|
|
||||||
|
|
||||||
// DEMO STOPS HERE
|
|
||||||
...(IS_DEMO
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
shape: "RpRpRpRp:CwCwCwCw",
|
|
||||||
required: 0,
|
|
||||||
reward: enumHubGoalRewards.reward_demo_end,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
// 14
|
|
||||||
// Belt reader
|
|
||||||
{
|
|
||||||
shape: "--Cg----:--Cr----", // unused
|
|
||||||
required: 16, // Per second!
|
|
||||||
reward: enumHubGoalRewards.reward_belt_reader,
|
|
||||||
throughputOnly: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 15
|
|
||||||
// Storage
|
|
||||||
{
|
|
||||||
shape: "SrSrSrSr:CyCyCyCy", // unused
|
|
||||||
required: 10000,
|
|
||||||
reward: enumHubGoalRewards.reward_storage,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 16
|
|
||||||
// Quad Cutter
|
|
||||||
{
|
|
||||||
shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", // belts t4 (two variants)
|
|
||||||
required: 6000,
|
|
||||||
reward: enumHubGoalRewards.reward_cutter_quad,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 17
|
|
||||||
// Double painter
|
|
||||||
{
|
|
||||||
shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", // miner t4 (two variants)
|
|
||||||
required: 20000,
|
|
||||||
reward: enumHubGoalRewards.reward_painter_double,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 18
|
|
||||||
// Rotater (180deg)
|
|
||||||
{
|
|
||||||
shape: "Sg----Sg:CgCgCgCg:--CyCy--", // unused
|
|
||||||
required: 20000,
|
|
||||||
reward: enumHubGoalRewards.reward_rotater_180,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 19
|
|
||||||
// Compact splitter
|
|
||||||
{
|
|
||||||
shape: "CpRpCp--:SwSwSwSw",
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_splitter,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 20
|
|
||||||
// WIRES
|
|
||||||
{
|
|
||||||
shape: finalGameShape,
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_wires_painter_and_levers,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 21
|
|
||||||
// Filter
|
|
||||||
{
|
|
||||||
shape: "CrCwCrCw:CwCrCwCr:CrCwCrCw:CwCrCwCr",
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_filter,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 22
|
|
||||||
// Constant signal
|
|
||||||
{
|
|
||||||
shape: "Cg----Cr:Cw----Cw:Sy------:Cy----Cy",
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_constant_signal,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 23
|
|
||||||
// Display
|
|
||||||
{
|
|
||||||
shape: "CcSyCcSy:SyCcSyCc:CcSyCcSy",
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_display,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 24 Logic gates
|
|
||||||
{
|
|
||||||
shape: "CcRcCcRc:RwCwRwCw:Sr--Sw--:CyCyCyCy",
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_logic_gates,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 25 Virtual Processing
|
|
||||||
{
|
|
||||||
shape: "Rg--Rg--:CwRwCwRw:--Rg--Rg",
|
|
||||||
required: 25000,
|
|
||||||
reward: enumHubGoalRewards.reward_virtual_processing,
|
|
||||||
},
|
|
||||||
|
|
||||||
// 26 Freeplay
|
|
||||||
{
|
|
||||||
shape: "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw",
|
|
||||||
required: 50000,
|
|
||||||
reward: enumHubGoalRewards.reward_freeplay,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (G_IS_DEV) {
|
|
||||||
tutorialGoals.forEach(({ shape }) => {
|
|
||||||
try {
|
|
||||||
ShapeDefinition.fromShortKey(shape);
|
|
||||||
} catch (ex) {
|
|
||||||
throw new Error("Invalid tutorial goal: '" + ex + "' for shape" + shape);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,212 +0,0 @@
|
|||||||
import { IS_DEMO } from "../core/config";
|
|
||||||
import { findNiceIntegerValue } from "../core/utils";
|
|
||||||
import { ShapeDefinition } from "./shape_definition";
|
|
||||||
|
|
||||||
export const preparementShape = "CpRpCp--:SwSwSwSw";
|
|
||||||
export const finalGameShape = "RuCw--Cw:----Ru--";
|
|
||||||
export const rocketShape = "CbCuCbCu:Sr------:--CrSrCr:CwCwCwCw";
|
|
||||||
export const blueprintShape = "CbCbCbRb:CwCwCwCw";
|
|
||||||
|
|
||||||
const fixedImprovements = [0.5, 0.5, 1, 1, 2, 1, 1];
|
|
||||||
|
|
||||||
const numEndgameUpgrades = !IS_DEMO ? 20 - fixedImprovements.length - 1 : 0;
|
|
||||||
|
|
||||||
function generateEndgameUpgrades() {
|
|
||||||
return new Array(numEndgameUpgrades).fill(null).map((_, i) => ({
|
|
||||||
required: [
|
|
||||||
{ shape: preparementShape, amount: 30000 + i * 10000 },
|
|
||||||
{ shape: finalGameShape, amount: 20000 + i * 5000 },
|
|
||||||
{ shape: rocketShape, amount: 20000 + i * 5000 },
|
|
||||||
],
|
|
||||||
excludePrevious: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < numEndgameUpgrades; ++i) {
|
|
||||||
fixedImprovements.push(0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @typedef {{
|
|
||||||
* shape: string,
|
|
||||||
* amount: number
|
|
||||||
* }} UpgradeRequirement */
|
|
||||||
|
|
||||||
/** @typedef {{
|
|
||||||
* required: Array<UpgradeRequirement>
|
|
||||||
* improvement?: number,
|
|
||||||
* excludePrevious?: boolean
|
|
||||||
* }} TierRequirement */
|
|
||||||
|
|
||||||
/** @typedef {Array<TierRequirement>} UpgradeTiers */
|
|
||||||
|
|
||||||
/** @type {Object<string, UpgradeTiers>} */
|
|
||||||
export const UPGRADES = {
|
|
||||||
belt: [
|
|
||||||
{
|
|
||||||
required: [{ shape: "CuCuCuCu", amount: 60 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "--CuCu--", amount: 500 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "CpCpCpCp", amount: 1000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "SrSrSrSr:CyCyCyCy", amount: 6000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "SrSrSrSr:CyCyCyCy:SwSwSwSw", amount: 25000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: preparementShape, amount: 25000 }],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [
|
|
||||||
{ shape: preparementShape, amount: 25000 },
|
|
||||||
{ shape: finalGameShape, amount: 50000 },
|
|
||||||
],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
...generateEndgameUpgrades(),
|
|
||||||
],
|
|
||||||
|
|
||||||
miner: [
|
|
||||||
{
|
|
||||||
required: [{ shape: "RuRuRuRu", amount: 300 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "Cu------", amount: 800 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "ScScScSc", amount: 3500 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "CwCwCwCw:WbWbWbWb", amount: 23000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "CbRbRbCb:CwCwCwCw:WbWbWbWb", amount: 50000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: preparementShape, amount: 25000 }],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [
|
|
||||||
{ shape: preparementShape, amount: 25000 },
|
|
||||||
{ shape: finalGameShape, amount: 50000 },
|
|
||||||
],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
...generateEndgameUpgrades(),
|
|
||||||
],
|
|
||||||
|
|
||||||
processors: [
|
|
||||||
{
|
|
||||||
required: [{ shape: "SuSuSuSu", amount: 500 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "RuRu----", amount: 600 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "CgScScCg", amount: 3500 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "CwCrCwCr:SgSgSgSg", amount: 25000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "WrRgWrRg:CwCrCwCr:SgSgSgSg", amount: 50000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: preparementShape, amount: 25000 }],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [
|
|
||||||
{ shape: preparementShape, amount: 25000 },
|
|
||||||
{ shape: finalGameShape, amount: 50000 },
|
|
||||||
],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
...generateEndgameUpgrades(),
|
|
||||||
],
|
|
||||||
|
|
||||||
painting: [
|
|
||||||
{
|
|
||||||
required: [{ shape: "RbRb----", amount: 600 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "WrWrWrWr", amount: 3800 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "RpRpRpRp:CwCwCwCw", amount: 6500 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp", amount: 25000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: "WpWpWpWp:CwCwCwCw:WpWpWpWp:CwCwCwCw", amount: 50000 }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [{ shape: preparementShape, amount: 25000 }],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
required: [
|
|
||||||
{ shape: preparementShape, amount: 25000 },
|
|
||||||
{ shape: finalGameShape, amount: 50000 },
|
|
||||||
],
|
|
||||||
excludePrevious: true,
|
|
||||||
},
|
|
||||||
...generateEndgameUpgrades(),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tiers need % of the previous tier as requirement too
|
|
||||||
const tierGrowth = 2.5;
|
|
||||||
|
|
||||||
// Automatically generate tier levels
|
|
||||||
for (const upgradeId in UPGRADES) {
|
|
||||||
const upgradeTiers = UPGRADES[upgradeId];
|
|
||||||
|
|
||||||
let currentTierRequirements = [];
|
|
||||||
for (let i = 0; i < upgradeTiers.length; ++i) {
|
|
||||||
const tierHandle = upgradeTiers[i];
|
|
||||||
tierHandle.improvement = fixedImprovements[i];
|
|
||||||
const originalRequired = tierHandle.required.slice();
|
|
||||||
|
|
||||||
for (let k = currentTierRequirements.length - 1; k >= 0; --k) {
|
|
||||||
const oldTierRequirement = currentTierRequirements[k];
|
|
||||||
if (!tierHandle.excludePrevious) {
|
|
||||||
tierHandle.required.unshift({
|
|
||||||
shape: oldTierRequirement.shape,
|
|
||||||
amount: oldTierRequirement.amount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentTierRequirements.push(
|
|
||||||
...originalRequired.map(req => ({
|
|
||||||
amount: req.amount,
|
|
||||||
shape: req.shape,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
currentTierRequirements.forEach(tier => {
|
|
||||||
tier.amount = findNiceIntegerValue(tier.amount * tierGrowth);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VALIDATE
|
|
||||||
if (G_IS_DEV) {
|
|
||||||
for (const upgradeId in UPGRADES) {
|
|
||||||
UPGRADES[upgradeId].forEach(tier => {
|
|
||||||
tier.required.forEach(({ shape }) => {
|
|
||||||
try {
|
|
||||||
ShapeDefinition.fromShortKey(shape);
|
|
||||||
} catch (ex) {
|
|
||||||
throw new Error("Invalid upgrade goal: '" + ex + "' for shape" + shape);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,113 +1,120 @@
|
|||||||
/**
|
/**
|
||||||
* @type {Object<string, {name: string, data: any, code: string, region: string}>}
|
* @type {Object<string, {name: string, data: any, code: string, region: string}>}
|
||||||
*/
|
*/
|
||||||
export const LANGUAGES = {
|
export const LANGUAGES = {
|
||||||
"en": {
|
"en": {
|
||||||
name: "English",
|
name: "English",
|
||||||
data: null,
|
data: null,
|
||||||
code: "en",
|
code: "en",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"de": {
|
"de": {
|
||||||
name: "Deutsch",
|
name: "Deutsch",
|
||||||
data: require("./built-temp/base-de.json"),
|
data: require("./built-temp/base-de.json"),
|
||||||
code: "de",
|
code: "de",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
name: "Français",
|
name: "Français",
|
||||||
data: require("./built-temp/base-fr.json"),
|
data: require("./built-temp/base-fr.json"),
|
||||||
code: "fr",
|
code: "fr",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"ja": {
|
"ja": {
|
||||||
name: "日本語",
|
name: "日本語",
|
||||||
data: require("./built-temp/base-ja.json"),
|
data: require("./built-temp/base-ja.json"),
|
||||||
code: "ja",
|
code: "ja",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"pt-PT": {
|
"pt-PT": {
|
||||||
name: "Português (Portugal)",
|
name: "Português (Portugal)",
|
||||||
data: require("./built-temp/base-pt-PT.json"),
|
data: require("./built-temp/base-pt-PT.json"),
|
||||||
code: "pt",
|
code: "pt",
|
||||||
region: "PT",
|
region: "PT",
|
||||||
},
|
},
|
||||||
"pt-BR": {
|
"pt-BR": {
|
||||||
name: "Português (Brasil)",
|
name: "Português (Brasil)",
|
||||||
data: require("./built-temp/base-pt-BR.json"),
|
data: require("./built-temp/base-pt-BR.json"),
|
||||||
code: "pt",
|
code: "pt",
|
||||||
region: "BR",
|
region: "BR",
|
||||||
},
|
},
|
||||||
"ru": {
|
"ru": {
|
||||||
name: "Русский",
|
name: "Русский",
|
||||||
data: require("./built-temp/base-ru.json"),
|
data: require("./built-temp/base-ru.json"),
|
||||||
code: "ru",
|
code: "ru",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"cs": {
|
"cs": {
|
||||||
name: "Čeština",
|
name: "Čeština",
|
||||||
data: require("./built-temp/base-cz.json"),
|
data: require("./built-temp/base-cz.json"),
|
||||||
code: "cs",
|
code: "cs",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"es-419": {
|
"es-419": {
|
||||||
name: "Español",
|
name: "Español",
|
||||||
data: require("./built-temp/base-es.json"),
|
data: require("./built-temp/base-es.json"),
|
||||||
code: "es",
|
code: "es",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"pl": {
|
"pl": {
|
||||||
name: "Polski",
|
name: "Polski",
|
||||||
data: require("./built-temp/base-pl.json"),
|
data: require("./built-temp/base-pl.json"),
|
||||||
code: "pl",
|
code: "pl",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"kor": {
|
"kor": {
|
||||||
name: "한국어",
|
name: "한국어",
|
||||||
data: require("./built-temp/base-kor.json"),
|
data: require("./built-temp/base-kor.json"),
|
||||||
code: "kor",
|
code: "kor",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"nl": {
|
"nl": {
|
||||||
name: "Nederlands",
|
name: "Nederlands",
|
||||||
data: require("./built-temp/base-nl.json"),
|
data: require("./built-temp/base-nl.json"),
|
||||||
code: "nl",
|
code: "nl",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
"no": {
|
"no": {
|
||||||
name: "Norsk",
|
name: "Norsk",
|
||||||
data: require("./built-temp/base-no.json"),
|
data: require("./built-temp/base-no.json"),
|
||||||
code: "no",
|
code: "no",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
"tr": {
|
"tr": {
|
||||||
name: "Türkçe",
|
name: "Türkçe",
|
||||||
data: require("./built-temp/base-tr.json"),
|
data: require("./built-temp/base-tr.json"),
|
||||||
code: "tr",
|
code: "tr",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
"zh-CN": {
|
"zh-CN": {
|
||||||
// simplified
|
// simplified
|
||||||
name: "中文简体",
|
name: "中文简体",
|
||||||
data: require("./built-temp/base-zh-CN.json"),
|
data: require("./built-temp/base-zh-CN.json"),
|
||||||
code: "zh",
|
code: "zh",
|
||||||
region: "CN",
|
region: "CN",
|
||||||
},
|
},
|
||||||
|
|
||||||
"zh-TW": {
|
"zh-TW": {
|
||||||
// traditional
|
// traditional
|
||||||
name: "中文繁體",
|
name: "中文繁體",
|
||||||
data: require("./built-temp/base-zh-TW.json"),
|
data: require("./built-temp/base-zh-TW.json"),
|
||||||
code: "zh",
|
code: "zh",
|
||||||
region: "TW",
|
region: "TW",
|
||||||
},
|
},
|
||||||
|
|
||||||
"sv": {
|
"sv": {
|
||||||
name: "Svenska",
|
name: "Svenska",
|
||||||
data: require("./built-temp/base-sv.json"),
|
data: require("./built-temp/base-sv.json"),
|
||||||
code: "sv",
|
code: "sv",
|
||||||
region: "",
|
region: "",
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
"da": {
|
||||||
|
name: "Dansk",
|
||||||
|
data: require("./built-temp/base-da.json"),
|
||||||
|
code: "da",
|
||||||
|
region: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
import { globalConfig } from "../../core/config";
|
import { globalConfig } from "../../core/config";
|
||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
|
import { queryParamOptions } from "../../core/query_parameters";
|
||||||
|
import { BeltComponent } from "../../game/components/belt";
|
||||||
|
import { StaticMapEntityComponent } from "../../game/components/static_map_entity";
|
||||||
import { GameRoot } from "../../game/root";
|
import { GameRoot } from "../../game/root";
|
||||||
import { InGameState } from "../../states/ingame";
|
import { InGameState } from "../../states/ingame";
|
||||||
import { GameAnalyticsInterface } from "../game_analytics";
|
import { GameAnalyticsInterface } from "../game_analytics";
|
||||||
import { FILE_NOT_FOUND } from "../storage";
|
import { FILE_NOT_FOUND } from "../storage";
|
||||||
import { blueprintShape, UPGRADES } from "../../game/upgrades";
|
|
||||||
import { tutorialGoals } from "../../game/tutorial_goals";
|
|
||||||
import { BeltComponent } from "../../game/components/belt";
|
|
||||||
import { StaticMapEntityComponent } from "../../game/components/static_map_entity";
|
|
||||||
import { queryParamOptions } from "../../core/query_parameters";
|
|
||||||
|
|
||||||
const logger = createLogger("game_analytics");
|
const logger = createLogger("game_analytics");
|
||||||
|
|
||||||
@ -190,23 +188,26 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the shape is interesting
|
* Returns true if the shape is interesting
|
||||||
|
* @param {GameRoot} root
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
*/
|
*/
|
||||||
isInterestingShape(key) {
|
isInterestingShape(root, key) {
|
||||||
if (key === blueprintShape) {
|
if (key === root.gameMode.getBlueprintShapeKey()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if its a story goal
|
// Check if its a story goal
|
||||||
for (let i = 0; i < tutorialGoals.length; ++i) {
|
const levels = root.gameMode.getLevelDefinitions();
|
||||||
if (key === tutorialGoals[i].shape) {
|
for (let i = 0; i < levels.length; ++i) {
|
||||||
|
if (key === levels[i].shape) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if its required to unlock an upgrade
|
// Check if its required to unlock an upgrade
|
||||||
for (const upgradeKey in UPGRADES) {
|
const upgrades = root.gameMode.getUpgrades();
|
||||||
const upgradeTiers = UPGRADES[upgradeKey];
|
for (const upgradeKey in upgrades) {
|
||||||
|
const upgradeTiers = upgrades[upgradeKey];
|
||||||
for (let i = 0; i < upgradeTiers.length; ++i) {
|
for (let i = 0; i < upgradeTiers.length; ++i) {
|
||||||
const tier = upgradeTiers[i];
|
const tier = upgradeTiers[i];
|
||||||
const required = tier.required;
|
const required = tier.required;
|
||||||
@ -226,7 +227,9 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
|||||||
* @param {GameRoot} root
|
* @param {GameRoot} root
|
||||||
*/
|
*/
|
||||||
generateGameDump(root) {
|
generateGameDump(root) {
|
||||||
const shapeIds = Object.keys(root.hubGoals.storedShapes).filter(this.isInterestingShape.bind(this));
|
const shapeIds = Object.keys(root.hubGoals.storedShapes).filter(key =>
|
||||||
|
this.isInterestingShape(root, key)
|
||||||
|
);
|
||||||
let shapes = {};
|
let shapes = {};
|
||||||
for (let i = 0; i < shapeIds.length; ++i) {
|
for (let i = 0; i < shapeIds.length; ++i) {
|
||||||
shapes[shapeIds[i]] = root.hubGoals.storedShapes[shapeIds[i]];
|
shapes[shapeIds[i]] = root.hubGoals.storedShapes[shapeIds[i]];
|
||||||
|
|||||||
@ -1,214 +1,202 @@
|
|||||||
import { globalConfig, IS_DEMO, IS_MOBILE } from "../../core/config";
|
import { globalConfig, IS_MOBILE } from "../../core/config";
|
||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
import { queryParamOptions } from "../../core/query_parameters";
|
import { queryParamOptions } from "../../core/query_parameters";
|
||||||
import { clamp } from "../../core/utils";
|
import { clamp } from "../../core/utils";
|
||||||
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
import { GamedistributionAdProvider } from "../ad_providers/gamedistribution";
|
||||||
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
import { NoAdProvider } from "../ad_providers/no_ad_provider";
|
||||||
import { PlatformWrapperInterface } from "../wrapper";
|
import { PlatformWrapperInterface } from "../wrapper";
|
||||||
import { StorageImplBrowser } from "./storage";
|
import { StorageImplBrowser } from "./storage";
|
||||||
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
import { StorageImplBrowserIndexedDB } from "./storage_indexed_db";
|
||||||
|
|
||||||
const logger = createLogger("platform/browser");
|
const logger = createLogger("platform/browser");
|
||||||
|
|
||||||
export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
||||||
initialize() {
|
initialize() {
|
||||||
this.recaptchaTokenCallback = null;
|
this.recaptchaTokenCallback = null;
|
||||||
|
|
||||||
this.embedProvider = {
|
this.embedProvider = {
|
||||||
id: "shapezio-website",
|
id: "shapezio-website",
|
||||||
adProvider: NoAdProvider,
|
adProvider: NoAdProvider,
|
||||||
iframed: false,
|
iframed: false,
|
||||||
externalLinks: true,
|
externalLinks: true,
|
||||||
iogLink: true,
|
iogLink: true,
|
||||||
unlimitedSavegames: IS_DEMO ? false : true,
|
};
|
||||||
showDemoBadge: IS_DEMO,
|
|
||||||
};
|
if (!G_IS_STANDALONE && queryParamOptions.embedProvider) {
|
||||||
|
const providerId = queryParamOptions.embedProvider;
|
||||||
if (!G_IS_STANDALONE && queryParamOptions.embedProvider) {
|
this.embedProvider.iframed = true;
|
||||||
const providerId = queryParamOptions.embedProvider;
|
this.embedProvider.iogLink = false;
|
||||||
this.embedProvider.iframed = true;
|
|
||||||
this.embedProvider.iogLink = false;
|
switch (providerId) {
|
||||||
|
case "armorgames": {
|
||||||
switch (providerId) {
|
this.embedProvider.id = "armorgames";
|
||||||
case "armorgames": {
|
break;
|
||||||
this.embedProvider.id = "armorgames";
|
}
|
||||||
break;
|
|
||||||
}
|
case "iogames.space": {
|
||||||
|
this.embedProvider.id = "iogames.space";
|
||||||
case "iogames.space": {
|
this.embedProvider.iogLink = true;
|
||||||
this.embedProvider.id = "iogames.space";
|
break;
|
||||||
this.embedProvider.iogLink = true;
|
}
|
||||||
this.embedProvider.unlimitedSavegames = true;
|
|
||||||
this.embedProvider.showDemoBadge = false;
|
case "miniclip": {
|
||||||
break;
|
this.embedProvider.id = "miniclip";
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
case "miniclip": {
|
|
||||||
this.embedProvider.id = "miniclip";
|
case "gamedistribution": {
|
||||||
break;
|
this.embedProvider.id = "gamedistribution";
|
||||||
}
|
this.embedProvider.externalLinks = false;
|
||||||
|
this.embedProvider.adProvider = GamedistributionAdProvider;
|
||||||
case "gamedistribution": {
|
break;
|
||||||
this.embedProvider.id = "gamedistribution";
|
}
|
||||||
this.embedProvider.externalLinks = false;
|
|
||||||
this.embedProvider.adProvider = GamedistributionAdProvider;
|
case "kongregate": {
|
||||||
break;
|
this.embedProvider.id = "kongregate";
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
case "kongregate": {
|
|
||||||
this.embedProvider.id = "kongregate";
|
case "crazygames": {
|
||||||
break;
|
this.embedProvider.id = "crazygames";
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
case "crazygames": {
|
|
||||||
this.embedProvider.id = "crazygames";
|
default: {
|
||||||
break;
|
logger.error("Got unsupported embed provider:", providerId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
default: {
|
}
|
||||||
logger.error("Got unsupported embed provider:", providerId);
|
|
||||||
}
|
logger.log("Embed provider:", this.embedProvider.id);
|
||||||
}
|
|
||||||
}
|
return this.detectStorageImplementation()
|
||||||
|
.then(() => this.initializeAdProvider())
|
||||||
logger.log("Embed provider:", this.embedProvider.id);
|
.then(() => super.initialize());
|
||||||
|
}
|
||||||
return this.detectStorageImplementation()
|
|
||||||
.then(() => this.initializeAdProvider())
|
detectStorageImplementation() {
|
||||||
.then(() => super.initialize());
|
return new Promise(resolve => {
|
||||||
}
|
logger.log("Detecting storage");
|
||||||
|
|
||||||
detectStorageImplementation() {
|
if (!window.indexedDB) {
|
||||||
return new Promise(resolve => {
|
logger.log("Indexed DB not supported");
|
||||||
logger.log("Detecting storage");
|
this.app.storage = new StorageImplBrowser(this.app);
|
||||||
|
resolve();
|
||||||
if (!window.indexedDB) {
|
return;
|
||||||
logger.log("Indexed DB not supported");
|
}
|
||||||
this.app.storage = new StorageImplBrowser(this.app);
|
|
||||||
resolve();
|
// Try accessing the indexedb
|
||||||
return;
|
let request;
|
||||||
}
|
try {
|
||||||
|
request = window.indexedDB.open("indexeddb_feature_detection", 1);
|
||||||
// Try accessing the indexedb
|
} catch (ex) {
|
||||||
let request;
|
logger.warn("Error while opening indexed db:", ex);
|
||||||
try {
|
this.app.storage = new StorageImplBrowser(this.app);
|
||||||
request = window.indexedDB.open("indexeddb_feature_detection", 1);
|
resolve();
|
||||||
} catch (ex) {
|
return;
|
||||||
logger.warn("Error while opening indexed db:", ex);
|
}
|
||||||
this.app.storage = new StorageImplBrowser(this.app);
|
request.onerror = err => {
|
||||||
resolve();
|
logger.log("Indexed DB can *not* be accessed: ", err);
|
||||||
return;
|
logger.log("Using fallback to local storage");
|
||||||
}
|
this.app.storage = new StorageImplBrowser(this.app);
|
||||||
request.onerror = err => {
|
resolve();
|
||||||
logger.log("Indexed DB can *not* be accessed: ", err);
|
};
|
||||||
logger.log("Using fallback to local storage");
|
request.onsuccess = () => {
|
||||||
this.app.storage = new StorageImplBrowser(this.app);
|
logger.log("Indexed DB *can* be accessed");
|
||||||
resolve();
|
this.app.storage = new StorageImplBrowserIndexedDB(this.app);
|
||||||
};
|
resolve();
|
||||||
request.onsuccess = () => {
|
};
|
||||||
logger.log("Indexed DB *can* be accessed");
|
});
|
||||||
this.app.storage = new StorageImplBrowserIndexedDB(this.app);
|
}
|
||||||
resolve();
|
|
||||||
};
|
getId() {
|
||||||
});
|
return "browser@" + this.embedProvider.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHasUnlimitedSavegames() {
|
getUiScale() {
|
||||||
return this.embedProvider.unlimitedSavegames;
|
if (IS_MOBILE) {
|
||||||
}
|
return 1;
|
||||||
|
}
|
||||||
getShowDemoBadges() {
|
|
||||||
return this.embedProvider.showDemoBadge;
|
const avgDims = Math.min(this.app.screenWidth, this.app.screenHeight);
|
||||||
}
|
return clamp((avgDims / 1000.0) * 1.9, 0.1, 10);
|
||||||
|
}
|
||||||
getId() {
|
|
||||||
return "browser@" + this.embedProvider.id;
|
getSupportsRestart() {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
getUiScale() {
|
|
||||||
if (IS_MOBILE) {
|
getTouchPanStrength() {
|
||||||
return 1;
|
return IS_MOBILE ? 1 : 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
const avgDims = Math.min(this.app.screenWidth, this.app.screenHeight);
|
openExternalLink(url, force = false) {
|
||||||
return clamp((avgDims / 1000.0) * 1.9, 0.1, 10);
|
logger.log("Opening external:", url);
|
||||||
}
|
if (force || this.embedProvider.externalLinks) {
|
||||||
|
window.open(url);
|
||||||
getSupportsRestart() {
|
} else {
|
||||||
return true;
|
// Do nothing
|
||||||
}
|
alert(
|
||||||
|
"This platform does not allow opening external links. You can play on https://shapez.io directly to open them.\n\nClicked Link: " +
|
||||||
getTouchPanStrength() {
|
url
|
||||||
return IS_MOBILE ? 1 : 0.5;
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
openExternalLink(url, force = false) {
|
|
||||||
logger.log("Opening external:", url);
|
performRestart() {
|
||||||
if (force || this.embedProvider.externalLinks) {
|
logger.log("Performing restart");
|
||||||
window.open(url);
|
window.location.reload(true);
|
||||||
} else {
|
}
|
||||||
// Do nothing
|
|
||||||
alert(
|
/**
|
||||||
"This platform does not allow opening external links. You can play on https://shapez.io directly to open them.\n\nClicked Link: " +
|
* Detects if there is an adblocker installed
|
||||||
url
|
* @returns {Promise<boolean>}
|
||||||
);
|
*/
|
||||||
}
|
detectAdblock() {
|
||||||
}
|
return Promise.race([
|
||||||
|
new Promise(resolve => {
|
||||||
performRestart() {
|
// If the request wasn't blocked within a very short period of time, this means
|
||||||
logger.log("Performing restart");
|
// the adblocker is not active and the request was actually made -> ignore it then
|
||||||
window.location.reload(true);
|
setTimeout(() => resolve(false), 30);
|
||||||
}
|
}),
|
||||||
|
new Promise(resolve => {
|
||||||
/**
|
fetch("https://googleads.g.doubleclick.net/pagead/id", {
|
||||||
* Detects if there is an adblocker installed
|
method: "HEAD",
|
||||||
* @returns {Promise<boolean>}
|
mode: "no-cors",
|
||||||
*/
|
})
|
||||||
detectAdblock() {
|
.then(res => {
|
||||||
return Promise.race([
|
resolve(false);
|
||||||
new Promise(resolve => {
|
})
|
||||||
// If the request wasn't blocked within a very short period of time, this means
|
.catch(err => {
|
||||||
// the adblocker is not active and the request was actually made -> ignore it then
|
resolve(true);
|
||||||
setTimeout(() => resolve(false), 30);
|
});
|
||||||
}),
|
}),
|
||||||
new Promise(resolve => {
|
]);
|
||||||
fetch("https://googleads.g.doubleclick.net/pagead/id", {
|
}
|
||||||
method: "HEAD",
|
|
||||||
mode: "no-cors",
|
initializeAdProvider() {
|
||||||
})
|
if (G_IS_DEV && !globalConfig.debug.testAds) {
|
||||||
.then(res => {
|
logger.log("Ads disabled in local environment");
|
||||||
resolve(false);
|
return Promise.resolve();
|
||||||
})
|
}
|
||||||
.catch(err => {
|
|
||||||
resolve(true);
|
// First, detect adblocker
|
||||||
});
|
return this.detectAdblock().then(hasAdblocker => {
|
||||||
}),
|
if (hasAdblocker) {
|
||||||
]);
|
logger.log("Adblock detected");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
initializeAdProvider() {
|
|
||||||
if (G_IS_DEV && !globalConfig.debug.testAds) {
|
const adProvider = this.embedProvider.adProvider;
|
||||||
logger.log("Ads disabled in local environment");
|
this.app.adProvider = new adProvider(this.app);
|
||||||
return Promise.resolve();
|
return this.app.adProvider.initialize().catch(err => {
|
||||||
}
|
logger.error("Failed to initialize ad provider, disabling ads:", err);
|
||||||
|
this.app.adProvider = new NoAdProvider(this.app);
|
||||||
// First, detect adblocker
|
});
|
||||||
return this.detectAdblock().then(hasAdblocker => {
|
});
|
||||||
if (hasAdblocker) {
|
}
|
||||||
logger.log("Adblock detected");
|
|
||||||
return;
|
exitApp() {
|
||||||
}
|
// Can not exit app
|
||||||
|
}
|
||||||
const adProvider = this.embedProvider.adProvider;
|
}
|
||||||
this.app.adProvider = new adProvider(this.app);
|
|
||||||
return this.app.adProvider.initialize().catch(err => {
|
|
||||||
logger.error("Failed to initialize ad provider, disabling ads:", err);
|
|
||||||
this.app.adProvider = new NoAdProvider(this.app);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
exitApp() {
|
|
||||||
// Can not exit app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,65 +1,57 @@
|
|||||||
import { PlatformWrapperImplBrowser } from "../browser/wrapper";
|
import { PlatformWrapperImplBrowser } from "../browser/wrapper";
|
||||||
import { getIPCRenderer } from "../../core/utils";
|
import { getIPCRenderer } from "../../core/utils";
|
||||||
import { createLogger } from "../../core/logging";
|
import { createLogger } from "../../core/logging";
|
||||||
import { StorageImplElectron } from "./storage";
|
import { StorageImplElectron } from "./storage";
|
||||||
import { PlatformWrapperInterface } from "../wrapper";
|
import { PlatformWrapperInterface } from "../wrapper";
|
||||||
|
|
||||||
const logger = createLogger("electron-wrapper");
|
const logger = createLogger("electron-wrapper");
|
||||||
|
|
||||||
export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
export class PlatformWrapperImplElectron extends PlatformWrapperImplBrowser {
|
||||||
initialize() {
|
initialize() {
|
||||||
this.app.storage = new StorageImplElectron(this);
|
this.app.storage = new StorageImplElectron(this);
|
||||||
return PlatformWrapperInterface.prototype.initialize.call(this);
|
return PlatformWrapperInterface.prototype.initialize.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
return "electron";
|
return "electron";
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportsRestart() {
|
getSupportsRestart() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
openExternalLink(url) {
|
openExternalLink(url) {
|
||||||
logger.log(this, "Opening external:", url);
|
logger.log(this, "Opening external:", url);
|
||||||
window.open(url, "about:blank");
|
window.open(url, "about:blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportsAds() {
|
getSupportsAds() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHasUnlimitedSavegames() {
|
performRestart() {
|
||||||
return true;
|
logger.log(this, "Performing restart");
|
||||||
}
|
window.location.reload(true);
|
||||||
|
}
|
||||||
getShowDemoBadges() {
|
|
||||||
return false;
|
initializeAdProvider() {
|
||||||
}
|
return Promise.resolve();
|
||||||
|
}
|
||||||
performRestart() {
|
|
||||||
logger.log(this, "Performing restart");
|
getSupportsFullscreen() {
|
||||||
window.location.reload(true);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeAdProvider() {
|
setFullscreen(flag) {
|
||||||
return Promise.resolve();
|
getIPCRenderer().send("set-fullscreen", flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportsFullscreen() {
|
getSupportsAppExit() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullscreen(flag) {
|
exitApp() {
|
||||||
getIPCRenderer().send("set-fullscreen", flag);
|
logger.log(this, "Sending app exit signal");
|
||||||
}
|
getIPCRenderer().send("exit-app");
|
||||||
|
}
|
||||||
getSupportsAppExit() {
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
exitApp() {
|
|
||||||
logger.log(this, "Sending app exit signal");
|
|
||||||
getIPCRenderer().send("exit-app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { GameRoot } from "../game/root";
|
|||||||
|
|
||||||
import { newEmptyMap, clamp } from "../core/utils";
|
import { newEmptyMap, clamp } from "../core/utils";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import { globalConfig, IS_DEMO } from "../core/config";
|
import { globalConfig } from "../core/config";
|
||||||
|
|
||||||
const logger = createLogger("sound");
|
const logger = createLogger("sound");
|
||||||
|
|
||||||
@ -29,7 +29,9 @@ export const SOUNDS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MUSIC = {
|
export const MUSIC = {
|
||||||
theme: IS_DEMO ? "theme-short" : "theme-full",
|
// The theme always depends on the standalone only, even if running the full
|
||||||
|
// version in the browser
|
||||||
|
theme: G_IS_STANDALONE ? "theme-full" : "theme-short",
|
||||||
menu: "menu",
|
menu: "menu",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,142 +1,131 @@
|
|||||||
/* typehints:start */
|
/* typehints:start */
|
||||||
import { Application } from "../application";
|
import { Application } from "../application";
|
||||||
/* typehints:end */
|
/* typehints:end */
|
||||||
|
|
||||||
import { IS_MOBILE } from "../core/config";
|
import { IS_MOBILE } from "../core/config";
|
||||||
|
|
||||||
export class PlatformWrapperInterface {
|
export class PlatformWrapperInterface {
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
/** @type {Application} */
|
/** @type {Application} */
|
||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {string} */
|
/** @returns {string} */
|
||||||
getId() {
|
getId() {
|
||||||
abstract;
|
abstract;
|
||||||
return "unknown-platform";
|
return "unknown-platform";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the UI scale, called on every resize
|
* Returns the UI scale, called on every resize
|
||||||
* @returns {number} */
|
* @returns {number} */
|
||||||
getUiScale() {
|
getUiScale() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} */
|
/** @returns {boolean} */
|
||||||
getSupportsRestart() {
|
getSupportsRestart() {
|
||||||
abstract;
|
abstract;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the user has unlimited savegames
|
* Returns the strength of touch pans with the mouse
|
||||||
*/
|
*/
|
||||||
getHasUnlimitedSavegames() {
|
getTouchPanStrength() {
|
||||||
return true;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShowDemoBadges() {
|
/** @returns {Promise<void>} */
|
||||||
return false;
|
initialize() {
|
||||||
}
|
document.documentElement.classList.add("p-" + this.getId());
|
||||||
|
return Promise.resolve();
|
||||||
/**
|
}
|
||||||
* Returns the strength of touch pans with the mouse
|
|
||||||
*/
|
/**
|
||||||
getTouchPanStrength() {
|
* Should initialize the apps ad provider in case supported
|
||||||
return 1;
|
* @returns {Promise<void>}
|
||||||
}
|
*/
|
||||||
|
initializeAdProvider() {
|
||||||
/** @returns {Promise<void>} */
|
return Promise.resolve();
|
||||||
initialize() {
|
}
|
||||||
document.documentElement.classList.add("p-" + this.getId());
|
|
||||||
return Promise.resolve();
|
/**
|
||||||
}
|
* Should return the minimum supported zoom level
|
||||||
|
* @returns {number}
|
||||||
/**
|
*/
|
||||||
* Should initialize the apps ad provider in case supported
|
getMinimumZoom() {
|
||||||
* @returns {Promise<void>}
|
return 0.1 * this.getScreenScale();
|
||||||
*/
|
}
|
||||||
initializeAdProvider() {
|
|
||||||
return Promise.resolve();
|
/**
|
||||||
}
|
* Should return the maximum supported zoom level
|
||||||
|
* @returns {number}
|
||||||
/**
|
*/
|
||||||
* Should return the minimum supported zoom level
|
getMaximumZoom() {
|
||||||
* @returns {number}
|
return 3.5 * this.getScreenScale();
|
||||||
*/
|
}
|
||||||
getMinimumZoom() {
|
|
||||||
return 0.1 * this.getScreenScale();
|
getScreenScale() {
|
||||||
}
|
return Math.min(window.innerWidth, window.innerHeight) / 1024.0;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Should return the maximum supported zoom level
|
/**
|
||||||
* @returns {number}
|
* Should return if this platform supports ads at all
|
||||||
*/
|
*/
|
||||||
getMaximumZoom() {
|
getSupportsAds() {
|
||||||
return 3.5 * this.getScreenScale();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScreenScale() {
|
/**
|
||||||
return Math.min(window.innerWidth, window.innerHeight) / 1024.0;
|
* Attempt to open an external url
|
||||||
}
|
* @param {string} url
|
||||||
|
* @param {boolean=} force Whether to always open the url even if not allowed
|
||||||
/**
|
*/
|
||||||
* Should return if this platform supports ads at all
|
openExternalLink(url, force = false) {
|
||||||
*/
|
abstract;
|
||||||
getSupportsAds() {
|
}
|
||||||
return false;
|
|
||||||
}
|
/**
|
||||||
|
* Attempt to restart the app
|
||||||
/**
|
*/
|
||||||
* Attempt to open an external url
|
performRestart() {
|
||||||
* @param {string} url
|
abstract;
|
||||||
* @param {boolean=} force Whether to always open the url even if not allowed
|
}
|
||||||
*/
|
|
||||||
openExternalLink(url, force = false) {
|
/**
|
||||||
abstract;
|
* Returns whether this platform supports a toggleable fullscreen
|
||||||
}
|
*/
|
||||||
|
getSupportsFullscreen() {
|
||||||
/**
|
return false;
|
||||||
* Attempt to restart the app
|
}
|
||||||
*/
|
|
||||||
performRestart() {
|
/**
|
||||||
abstract;
|
* Should set the apps fullscreen state to the desired state
|
||||||
}
|
* @param {boolean} flag
|
||||||
|
*/
|
||||||
/**
|
setFullscreen(flag) {
|
||||||
* Returns whether this platform supports a toggleable fullscreen
|
abstract;
|
||||||
*/
|
}
|
||||||
getSupportsFullscreen() {
|
|
||||||
return false;
|
/**
|
||||||
}
|
* Returns whether this platform supports quitting the app
|
||||||
|
*/
|
||||||
/**
|
getSupportsAppExit() {
|
||||||
* Should set the apps fullscreen state to the desired state
|
return false;
|
||||||
* @param {boolean} flag
|
}
|
||||||
*/
|
|
||||||
setFullscreen(flag) {
|
/**
|
||||||
abstract;
|
* Attempts to quit the app
|
||||||
}
|
*/
|
||||||
|
exitApp() {
|
||||||
/**
|
abstract;
|
||||||
* Returns whether this platform supports quitting the app
|
}
|
||||||
*/
|
|
||||||
getSupportsAppExit() {
|
/**
|
||||||
return false;
|
* Whether this platform supports a keyboard
|
||||||
}
|
*/
|
||||||
|
getSupportsKeyboard() {
|
||||||
/**
|
return !IS_MOBILE;
|
||||||
* Attempts to quit the app
|
}
|
||||||
*/
|
}
|
||||||
exitApp() {
|
|
||||||
abstract;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this platform supports a keyboard
|
|
||||||
*/
|
|
||||||
getSupportsKeyboard() {
|
|
||||||
return !IS_MOBILE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -6,8 +6,7 @@ import { ReadWriteProxy } from "../core/read_write_proxy";
|
|||||||
import { BoolSetting, EnumSetting, RangeSetting, BaseSetting } from "./setting_types";
|
import { BoolSetting, EnumSetting, RangeSetting, BaseSetting } from "./setting_types";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import { ExplainedResult } from "../core/explained_result";
|
import { ExplainedResult } from "../core/explained_result";
|
||||||
import { THEMES, THEME, applyGameTheme } from "../game/theme";
|
import { THEMES, applyGameTheme } from "../game/theme";
|
||||||
import { IS_DEMO } from "../core/config";
|
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
import { LANGUAGES } from "../languages";
|
import { LANGUAGES } from "../languages";
|
||||||
|
|
||||||
@ -187,7 +186,9 @@ export const allApplicationSettings = [
|
|||||||
app.platformWrapper.setFullscreen(value);
|
app.platformWrapper.setFullscreen(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
!IS_DEMO
|
/**
|
||||||
|
* @param {Application} app
|
||||||
|
*/ app => app.restrictionMgr.getHasExtendedSettings()
|
||||||
),
|
),
|
||||||
|
|
||||||
new BoolSetting(
|
new BoolSetting(
|
||||||
@ -215,7 +216,9 @@ export const allApplicationSettings = [
|
|||||||
applyGameTheme(id);
|
applyGameTheme(id);
|
||||||
document.documentElement.setAttribute("data-theme", id);
|
document.documentElement.setAttribute("data-theme", id);
|
||||||
},
|
},
|
||||||
enabled: !IS_DEMO,
|
enabledCb: /**
|
||||||
|
* @param {Application} app
|
||||||
|
*/ app => app.restrictionMgr.getHasExtendedSettings(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new EnumSetting("autosaveInterval", {
|
new EnumSetting("autosaveInterval", {
|
||||||
@ -255,6 +258,7 @@ export const allApplicationSettings = [
|
|||||||
|
|
||||||
new BoolSetting("enableMousePan", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("enableMousePan", enumCategories.advanced, (app, value) => {}),
|
||||||
new BoolSetting("alwaysMultiplace", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("alwaysMultiplace", enumCategories.advanced, (app, value) => {}),
|
||||||
|
new BoolSetting("zoomToCursor", enumCategories.advanced, (app, value) => {}),
|
||||||
new BoolSetting("clearCursorOnDeleteWhilePlacing", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("clearCursorOnDeleteWhilePlacing", enumCategories.advanced, (app, value) => {}),
|
||||||
new BoolSetting("enableTunnelSmartplace", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("enableTunnelSmartplace", enumCategories.advanced, (app, value) => {}),
|
||||||
new BoolSetting("vignette", enumCategories.userInterface, (app, value) => {}),
|
new BoolSetting("vignette", enumCategories.userInterface, (app, value) => {}),
|
||||||
@ -263,6 +267,7 @@ export const allApplicationSettings = [
|
|||||||
new BoolSetting("rotationByBuilding", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("rotationByBuilding", enumCategories.advanced, (app, value) => {}),
|
||||||
new BoolSetting("displayChunkBorders", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("displayChunkBorders", enumCategories.advanced, (app, value) => {}),
|
||||||
new BoolSetting("pickMinerOnPatch", enumCategories.advanced, (app, value) => {}),
|
new BoolSetting("pickMinerOnPatch", enumCategories.advanced, (app, value) => {}),
|
||||||
|
new RangeSetting("mapResourcesScale", enumCategories.advanced, () => null),
|
||||||
|
|
||||||
new EnumSetting("refreshRate", {
|
new EnumSetting("refreshRate", {
|
||||||
options: refreshRateOptions,
|
options: refreshRateOptions,
|
||||||
@ -271,7 +276,9 @@ export const allApplicationSettings = [
|
|||||||
category: enumCategories.performance,
|
category: enumCategories.performance,
|
||||||
restartRequired: false,
|
restartRequired: false,
|
||||||
changeCb: (app, id) => {},
|
changeCb: (app, id) => {},
|
||||||
enabled: !IS_DEMO,
|
enabledCb: /**
|
||||||
|
* @param {Application} app
|
||||||
|
*/ app => app.restrictionMgr.getHasExtendedSettings(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new BoolSetting("lowQualityMapResources", enumCategories.performance, (app, value) => {}),
|
new BoolSetting("lowQualityMapResources", enumCategories.performance, (app, value) => {}),
|
||||||
@ -317,6 +324,8 @@ class SettingsStorage {
|
|||||||
this.disableTileGrid = false;
|
this.disableTileGrid = false;
|
||||||
this.lowQualityTextures = false;
|
this.lowQualityTextures = false;
|
||||||
this.simplifiedBelts = false;
|
this.simplifiedBelts = false;
|
||||||
|
this.zoomToCursor = true;
|
||||||
|
this.mapResourcesScale = 0.5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Object.<string, number>}
|
* @type {Object.<string, number>}
|
||||||
@ -355,7 +364,7 @@ export class ApplicationSettings extends ReadWriteProxy {
|
|||||||
* @returns {SettingsStorage}
|
* @returns {SettingsStorage}
|
||||||
*/
|
*/
|
||||||
getAllSettings() {
|
getAllSettings() {
|
||||||
return this.getCurrentData().settings;
|
return this.currentData.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -527,7 +536,7 @@ export class ApplicationSettings extends ReadWriteProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getCurrentVersion() {
|
getCurrentVersion() {
|
||||||
return 28;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {{settings: SettingsStorage, version: number}} data */
|
/** @param {{settings: SettingsStorage, version: number}} data */
|
||||||
@ -660,6 +669,16 @@ export class ApplicationSettings extends ReadWriteProxy {
|
|||||||
data.version = 28;
|
data.version = 28;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.version < 29) {
|
||||||
|
data.settings.zoomToCursor = true;
|
||||||
|
data.version = 29;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.version < 30) {
|
||||||
|
data.settings.mapResourcesScale = 0.5;
|
||||||
|
data.version = 30;
|
||||||
|
}
|
||||||
|
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,19 +7,30 @@ import { T } from "../translations";
|
|||||||
|
|
||||||
const logger = createLogger("setting_types");
|
const logger = createLogger("setting_types");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ***************************************************
|
||||||
|
*
|
||||||
|
* LEGACY CODE WARNING
|
||||||
|
*
|
||||||
|
* This is old code from yorg3.io and needs to be refactored
|
||||||
|
* @TODO
|
||||||
|
*
|
||||||
|
* ***************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
export class BaseSetting {
|
export class BaseSetting {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {string} categoryId
|
* @param {string} categoryId
|
||||||
* @param {function(Application, any):void} changeCb
|
* @param {function(Application, any):void} changeCb
|
||||||
* @param {boolean} enabled
|
* @param {function(Application) : boolean=} enabledCb
|
||||||
*/
|
*/
|
||||||
constructor(id, categoryId, changeCb, enabled) {
|
constructor(id, categoryId, changeCb, enabledCb = null) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.categoryId = categoryId;
|
this.categoryId = categoryId;
|
||||||
this.changeCb = changeCb;
|
this.changeCb = changeCb;
|
||||||
this.enabled = enabled;
|
this.enabledCb = enabledCb;
|
||||||
|
|
||||||
/** @type {Application} */
|
/** @type {Application} */
|
||||||
this.app = null;
|
this.app = null;
|
||||||
@ -39,6 +50,7 @@ export class BaseSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Binds all parameters
|
||||||
* @param {Application} app
|
* @param {Application} app
|
||||||
* @param {HTMLElement} element
|
* @param {HTMLElement} element
|
||||||
* @param {any} dialogs
|
* @param {any} dialogs
|
||||||
@ -49,19 +61,37 @@ export class BaseSetting {
|
|||||||
this.dialogs = dialogs;
|
this.dialogs = dialogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHtml() {
|
/**
|
||||||
|
* Returns the HTML for this setting
|
||||||
|
* @param {Application} app
|
||||||
|
*/
|
||||||
|
getHtml(app) {
|
||||||
abstract;
|
abstract;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this setting is enabled and available
|
||||||
|
* @param {Application} app
|
||||||
|
*/
|
||||||
|
getIsAvailable(app) {
|
||||||
|
return this.enabledCb ? this.enabledCb(app) : true;
|
||||||
|
}
|
||||||
|
|
||||||
syncValueToElement() {
|
syncValueToElement() {
|
||||||
abstract;
|
abstract;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to modify the setting
|
||||||
|
*/
|
||||||
modify() {
|
modify() {
|
||||||
abstract;
|
abstract;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the dialog that a restart is required
|
||||||
|
*/
|
||||||
showRestartRequiredDialog() {
|
showRestartRequiredDialog() {
|
||||||
const { restart } = this.dialogs.showInfo(
|
const { restart } = this.dialogs.showInfo(
|
||||||
T.dialogs.restartRequired.title,
|
T.dialogs.restartRequired.title,
|
||||||
@ -74,6 +104,7 @@ export class BaseSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Validates the set value
|
||||||
* @param {any} value
|
* @param {any} value
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
@ -96,10 +127,10 @@ export class EnumSetting extends BaseSetting {
|
|||||||
iconPrefix = null,
|
iconPrefix = null,
|
||||||
changeCb = null,
|
changeCb = null,
|
||||||
magicValue = null,
|
magicValue = null,
|
||||||
enabled = true,
|
enabledCb = null,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
super(id, category, changeCb, enabled);
|
super(id, category, changeCb, enabledCb);
|
||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.valueGetter = valueGetter;
|
this.valueGetter = valueGetter;
|
||||||
@ -110,10 +141,14 @@ export class EnumSetting extends BaseSetting {
|
|||||||
this.magicValue = magicValue;
|
this.magicValue = magicValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHtml() {
|
/**
|
||||||
|
* @param {Application} app
|
||||||
|
*/
|
||||||
|
getHtml(app) {
|
||||||
|
const available = this.getIsAvailable(app);
|
||||||
return `
|
return `
|
||||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
<div class="setting cardbox ${available ? "enabled" : "disabled"}">
|
||||||
${this.enabled ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
|
${available ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>${T.settings.labels[this.id].title}</label>
|
<label>${T.settings.labels[this.id].title}</label>
|
||||||
<div class="value enum" data-setting="${this.id}"></div>
|
<div class="value enum" data-setting="${this.id}"></div>
|
||||||
@ -180,14 +215,18 @@ export class EnumSetting extends BaseSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BoolSetting extends BaseSetting {
|
export class BoolSetting extends BaseSetting {
|
||||||
constructor(id, category, changeCb = null, enabled = true) {
|
constructor(id, category, changeCb = null, enabledCb = null) {
|
||||||
super(id, category, changeCb, enabled);
|
super(id, category, changeCb, enabledCb);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHtml() {
|
/**
|
||||||
|
* @param {Application} app
|
||||||
|
*/
|
||||||
|
getHtml(app) {
|
||||||
|
const available = this.getIsAvailable(app);
|
||||||
return `
|
return `
|
||||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
<div class="setting cardbox ${available ? "enabled" : "disabled"}">
|
||||||
${this.enabled ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
|
${available ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>${T.settings.labels[this.id].title}</label>
|
<label>${T.settings.labels[this.id].title}</label>
|
||||||
@ -226,13 +265,13 @@ export class RangeSetting extends BaseSetting {
|
|||||||
id,
|
id,
|
||||||
category,
|
category,
|
||||||
changeCb = null,
|
changeCb = null,
|
||||||
enabled = true,
|
|
||||||
defaultValue = 1.0,
|
defaultValue = 1.0,
|
||||||
minValue = 0,
|
minValue = 0,
|
||||||
maxValue = 1.0,
|
maxValue = 1.0,
|
||||||
stepSize = 0.0001
|
stepSize = 0.0001,
|
||||||
|
enabledCb = null
|
||||||
) {
|
) {
|
||||||
super(id, category, changeCb, enabled);
|
super(id, category, changeCb, enabledCb);
|
||||||
|
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
this.minValue = minValue;
|
this.minValue = minValue;
|
||||||
@ -240,10 +279,14 @@ export class RangeSetting extends BaseSetting {
|
|||||||
this.stepSize = stepSize;
|
this.stepSize = stepSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHtml() {
|
/**
|
||||||
|
* @param {Application} app
|
||||||
|
*/
|
||||||
|
getHtml(app) {
|
||||||
|
const available = this.getIsAvailable(app);
|
||||||
return `
|
return `
|
||||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
<div class="setting cardbox ${available ? "enabled" : "disabled"}">
|
||||||
${this.enabled ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
|
${available ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>${T.settings.labels[this.id].title}</label>
|
<label>${T.settings.labels[this.id].title}</label>
|
||||||
|
|||||||
@ -40,13 +40,6 @@ export class SavegameManager extends ReadWriteProxy {
|
|||||||
return 1002;
|
return 1002;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {SavegamesData}
|
|
||||||
*/
|
|
||||||
getCurrentData() {
|
|
||||||
return super.getCurrentData();
|
|
||||||
}
|
|
||||||
|
|
||||||
verify(data) {
|
verify(data) {
|
||||||
// TODO / FIXME!!!!
|
// TODO / FIXME!!!!
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
@ -96,6 +89,14 @@ export class SavegameManager extends ReadWriteProxy {
|
|||||||
return new Savegame(this.app, { internalId, metaDataRef: metadata });
|
return new Savegame(this.app, { internalId, metaDataRef: metadata });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if this manager has any savegame of a 1.1.19 version, which
|
||||||
|
* enables all levels
|
||||||
|
*/
|
||||||
|
getHasAnyLegacySavegames() {
|
||||||
|
return this.currentData.savegames.some(savegame => savegame.version === 1005 || savegame.level > 14);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a savegame
|
* Deletes a savegame
|
||||||
* @param {SavegameMetadata} game
|
* @param {SavegameMetadata} game
|
||||||
@ -149,7 +150,9 @@ export class SavegameManager extends ReadWriteProxy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.currentData.savegames.push(metaData);
|
this.currentData.savegames.push(metaData);
|
||||||
this.sortSavegames();
|
|
||||||
|
// Notice: This is async and happening in the background
|
||||||
|
this.updateAfterSavegamesChanged();
|
||||||
|
|
||||||
return new Savegame(this.app, {
|
return new Savegame(this.app, {
|
||||||
internalId: id,
|
internalId: id,
|
||||||
@ -157,8 +160,16 @@ export class SavegameManager extends ReadWriteProxy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to import a savegame
|
||||||
|
* @param {object} data
|
||||||
|
*/
|
||||||
importSavegame(data) {
|
importSavegame(data) {
|
||||||
const savegame = this.createNewSavegame();
|
const savegame = this.createNewSavegame();
|
||||||
|
|
||||||
|
// Track legacy savegames
|
||||||
|
const isOldSavegame = data.version < 1006;
|
||||||
|
|
||||||
const migrationResult = savegame.migrate(data);
|
const migrationResult = savegame.migrate(data);
|
||||||
if (migrationResult.isBad()) {
|
if (migrationResult.isBad()) {
|
||||||
return Promise.reject("Failed to migrate: " + migrationResult.reason);
|
return Promise.reject("Failed to migrate: " + migrationResult.reason);
|
||||||
@ -170,7 +181,19 @@ export class SavegameManager extends ReadWriteProxy {
|
|||||||
return Promise.reject("Verification failed: " + verification.result);
|
return Promise.reject("Verification failed: " + verification.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return savegame.writeSavegameAndMetadata().then(() => this.sortSavegames());
|
return savegame
|
||||||
|
.writeSavegameAndMetadata()
|
||||||
|
.then(() => this.updateAfterSavegamesChanged())
|
||||||
|
.then(() => this.app.restrictionMgr.onHasLegacySavegamesChanged(isOldSavegame));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook after the savegames got changed
|
||||||
|
*/
|
||||||
|
updateAfterSavegamesChanged() {
|
||||||
|
return this.sortSavegames()
|
||||||
|
.then(() => this.writeAsync())
|
||||||
|
.then(() => this.app.restrictionMgr.onHasLegacySavegamesChanged(this.getHasAnyLegacySavegames()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,7 +242,7 @@ export class SavegameManager extends ReadWriteProxy {
|
|||||||
if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
|
if (G_IS_DEV && globalConfig.debug.disableSavegameWrite) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return this.sortSavegames().then(() => this.writeAsync());
|
return this.updateAfterSavegamesChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,7 +130,7 @@ export class SavegameSerializer {
|
|||||||
errorReason = errorReason || root.time.deserialize(savegame.time);
|
errorReason = errorReason || root.time.deserialize(savegame.time);
|
||||||
errorReason = errorReason || root.camera.deserialize(savegame.camera);
|
errorReason = errorReason || root.camera.deserialize(savegame.camera);
|
||||||
errorReason = errorReason || root.map.deserialize(savegame.map);
|
errorReason = errorReason || root.map.deserialize(savegame.map);
|
||||||
errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals);
|
errorReason = errorReason || root.hubGoals.deserialize(savegame.hubGoals, root);
|
||||||
errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
|
errorReason = errorReason || root.hud.parts.pinnedShapes.deserialize(savegame.pinnedShapes);
|
||||||
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
|
errorReason = errorReason || root.hud.parts.waypoints.deserialize(savegame.waypoints);
|
||||||
errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities);
|
errorReason = errorReason || this.internal.deserializeEntityArray(root, savegame.entities);
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { getCodeFromBuildingData } from "../../game/building_codes.js";
|
|||||||
import { StaticMapEntityComponent } from "../../game/components/static_map_entity.js";
|
import { StaticMapEntityComponent } from "../../game/components/static_map_entity.js";
|
||||||
import { Entity } from "../../game/entity.js";
|
import { Entity } from "../../game/entity.js";
|
||||||
import { defaultBuildingVariant, MetaBuilding } from "../../game/meta_building.js";
|
import { defaultBuildingVariant, MetaBuilding } from "../../game/meta_building.js";
|
||||||
import { finalGameShape } from "../../game/upgrades.js";
|
|
||||||
import { SavegameInterface_V1005 } from "./1005.js";
|
import { SavegameInterface_V1005 } from "./1005.js";
|
||||||
|
|
||||||
const schema = require("./1006.json");
|
const schema = require("./1006.json");
|
||||||
@ -152,7 +151,8 @@ export class SavegameInterface_V1006 extends SavegameInterface_V1005 {
|
|||||||
stored[shapeKey] = rebalance(stored[shapeKey]);
|
stored[shapeKey] = rebalance(stored[shapeKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
stored[finalGameShape] = 0;
|
// Reset final game shape
|
||||||
|
stored["RuCw--Cw:----Ru--"] = 0;
|
||||||
|
|
||||||
// Reduce goals
|
// Reduce goals
|
||||||
if (dump.hubGoals.currentGoal) {
|
if (dump.hubGoals.currentGoal) {
|
||||||
@ -248,7 +248,7 @@ export class SavegameInterface_V1006 extends SavegameInterface_V1005 {
|
|||||||
if (components.Storage) {
|
if (components.Storage) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
components.Storage = {
|
components.Storage = {
|
||||||
storedCount: 0,
|
storedCount: rebalance(components.Storage.storedCount),
|
||||||
storedItem: null,
|
storedItem: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,344 +1,351 @@
|
|||||||
import { createLogger } from "../core/logging";
|
import { createLogger } from "../core/logging";
|
||||||
import {
|
import {
|
||||||
BaseDataType,
|
BaseDataType,
|
||||||
TypeArray,
|
TypeArray,
|
||||||
TypeBoolean,
|
TypeBoolean,
|
||||||
TypeClass,
|
TypeClass,
|
||||||
TypeClassData,
|
TypeClassData,
|
||||||
TypeClassFromMetaclass,
|
TypeClassFromMetaclass,
|
||||||
TypeClassId,
|
TypeClassId,
|
||||||
TypeEntity,
|
TypeEntity,
|
||||||
TypeEntityWeakref,
|
TypeEntityWeakref,
|
||||||
TypeEnum,
|
TypeEnum,
|
||||||
TypeFixedClass,
|
TypeFixedClass,
|
||||||
TypeInteger,
|
TypeInteger,
|
||||||
TypeKeyValueMap,
|
TypeKeyValueMap,
|
||||||
TypeMetaClass,
|
TypeMetaClass,
|
||||||
TypeNullable,
|
TypeNullable,
|
||||||
TypeNumber,
|
TypeNumber,
|
||||||
TypePair,
|
TypePair,
|
||||||
TypePositiveInteger,
|
TypePositiveInteger,
|
||||||
TypePositiveNumber,
|
TypePositiveNumber,
|
||||||
TypeString,
|
TypeString,
|
||||||
TypeStructuredObject,
|
TypeStructuredObject,
|
||||||
TypeVector,
|
TypeVector,
|
||||||
} from "./serialization_data_types";
|
} from "./serialization_data_types";
|
||||||
|
|
||||||
const logger = createLogger("serialization");
|
const logger = createLogger("serialization");
|
||||||
|
|
||||||
// Schema declarations
|
// Schema declarations
|
||||||
export const types = {
|
export const types = {
|
||||||
int: new TypeInteger(),
|
int: new TypeInteger(),
|
||||||
uint: new TypePositiveInteger(),
|
uint: new TypePositiveInteger(),
|
||||||
float: new TypeNumber(),
|
float: new TypeNumber(),
|
||||||
ufloat: new TypePositiveNumber(),
|
ufloat: new TypePositiveNumber(),
|
||||||
string: new TypeString(),
|
string: new TypeString(),
|
||||||
entity: new TypeEntity(),
|
entity: new TypeEntity(),
|
||||||
weakEntityRef: new TypeEntityWeakref(),
|
weakEntityRef: new TypeEntityWeakref(),
|
||||||
vector: new TypeVector(),
|
vector: new TypeVector(),
|
||||||
tileVector: new TypeVector(),
|
tileVector: new TypeVector(),
|
||||||
bool: new TypeBoolean(),
|
bool: new TypeBoolean(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BaseDataType} wrapped
|
* @param {BaseDataType} wrapped
|
||||||
*/
|
*/
|
||||||
nullable(wrapped) {
|
nullable(wrapped) {
|
||||||
return new TypeNullable(wrapped);
|
return new TypeNullable(wrapped);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FactoryTemplate<*>|SingletonFactoryTemplate<*>} registry
|
* @param {FactoryTemplate<*>|SingletonFactoryTemplate<*>} registry
|
||||||
*/
|
*/
|
||||||
classId(registry) {
|
classId(registry) {
|
||||||
return new TypeClassId(registry);
|
return new TypeClassId(registry);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param {BaseDataType} valueType
|
* @param {BaseDataType} valueType
|
||||||
* @param {boolean=} includeEmptyValues
|
* @param {boolean=} includeEmptyValues
|
||||||
*/
|
*/
|
||||||
keyValueMap(valueType, includeEmptyValues = true) {
|
keyValueMap(valueType, includeEmptyValues = true) {
|
||||||
return new TypeKeyValueMap(valueType, includeEmptyValues);
|
return new TypeKeyValueMap(valueType, includeEmptyValues);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object<string, any>} values
|
* @param {Object<string, any>} values
|
||||||
*/
|
*/
|
||||||
enum(values) {
|
enum(values) {
|
||||||
return new TypeEnum(values);
|
return new TypeEnum(values);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FactoryTemplate<*>} registry
|
* @param {FactoryTemplate<*>} registry
|
||||||
* @param {(GameRoot, any) => object=} resolver
|
* @param {(GameRoot, any) => object=} resolver
|
||||||
*/
|
*/
|
||||||
obj(registry, resolver = null) {
|
obj(registry, resolver = null) {
|
||||||
return new TypeClass(registry, resolver);
|
return new TypeClass(registry, resolver);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FactoryTemplate<*>} registry
|
* @param {FactoryTemplate<*>} registry
|
||||||
*/
|
*/
|
||||||
objData(registry) {
|
objData(registry) {
|
||||||
return new TypeClassData(registry);
|
return new TypeClassData(registry);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {typeof BasicSerializableObject} cls
|
* @param {typeof BasicSerializableObject} cls
|
||||||
*/
|
*/
|
||||||
knownType(cls) {
|
knownType(cls) {
|
||||||
return new TypeFixedClass(cls);
|
return new TypeFixedClass(cls);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BaseDataType} innerType
|
* @param {BaseDataType} innerType
|
||||||
*/
|
*/
|
||||||
array(innerType) {
|
array(innerType) {
|
||||||
return new TypeArray(innerType);
|
return new TypeArray(innerType);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {SingletonFactoryTemplate<*>} innerType
|
* @param {BaseDataType} innerType
|
||||||
*/
|
*/
|
||||||
classRef(registry) {
|
fixedSizeArray(innerType) {
|
||||||
return new TypeMetaClass(registry);
|
return new TypeArray(innerType, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object.<string, BaseDataType>} descriptor
|
* @param {SingletonFactoryTemplate<*>} innerType
|
||||||
*/
|
*/
|
||||||
structured(descriptor) {
|
classRef(registry) {
|
||||||
return new TypeStructuredObject(descriptor);
|
return new TypeMetaClass(registry);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BaseDataType} a
|
* @param {Object.<string, BaseDataType>} descriptor
|
||||||
* @param {BaseDataType} b
|
*/
|
||||||
*/
|
structured(descriptor) {
|
||||||
pair(a, b) {
|
return new TypeStructuredObject(descriptor);
|
||||||
return new TypePair(a, b);
|
},
|
||||||
},
|
|
||||||
|
/**
|
||||||
/**
|
* @param {BaseDataType} a
|
||||||
* @param {typeof BasicSerializableObject} classHandle
|
* @param {BaseDataType} b
|
||||||
* @param {SingletonFactoryTemplate<*>} registry
|
*/
|
||||||
*/
|
pair(a, b) {
|
||||||
classWithMetaclass(classHandle, registry) {
|
return new TypePair(a, b);
|
||||||
return new TypeClassFromMetaclass(classHandle, registry);
|
},
|
||||||
},
|
|
||||||
};
|
/**
|
||||||
|
* @param {typeof BasicSerializableObject} classHandle
|
||||||
/**
|
* @param {SingletonFactoryTemplate<*>} registry
|
||||||
* A full schema declaration
|
*/
|
||||||
* @typedef {Object.<string, BaseDataType>} Schema
|
classWithMetaclass(classHandle, registry) {
|
||||||
*/
|
return new TypeClassFromMetaclass(classHandle, registry);
|
||||||
|
},
|
||||||
const globalSchemaCache = {};
|
};
|
||||||
|
|
||||||
/* dev:start */
|
/**
|
||||||
const classnamesCache = {};
|
* A full schema declaration
|
||||||
/* dev:end*/
|
* @typedef {Object.<string, BaseDataType>} Schema
|
||||||
|
*/
|
||||||
export class BasicSerializableObject {
|
|
||||||
/* dev:start */
|
const globalSchemaCache = {};
|
||||||
/**
|
|
||||||
* Fixes typeof DerivedComponent is not assignable to typeof Component, compiled out
|
/* dev:start */
|
||||||
* in non-dev builds
|
const classnamesCache = {};
|
||||||
*/
|
/* dev:end*/
|
||||||
constructor(...args) {}
|
|
||||||
|
export class BasicSerializableObject {
|
||||||
/* dev:end */
|
/* dev:start */
|
||||||
|
/**
|
||||||
static getId() {
|
* Fixes typeof DerivedComponent is not assignable to typeof Component, compiled out
|
||||||
abstract;
|
* in non-dev builds
|
||||||
}
|
*/
|
||||||
|
constructor(...args) {}
|
||||||
/**
|
|
||||||
* Should return the serialization schema
|
/* dev:end */
|
||||||
* @returns {Schema}
|
|
||||||
*/
|
static getId() {
|
||||||
static getSchema() {
|
abstract;
|
||||||
return {};
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
// Implementation
|
* Should return the serialization schema
|
||||||
/** @returns {Schema} */
|
* @returns {Schema}
|
||||||
static getCachedSchema() {
|
*/
|
||||||
const id = this.getId();
|
static getSchema() {
|
||||||
|
return {};
|
||||||
/* dev:start */
|
}
|
||||||
assert(
|
|
||||||
classnamesCache[id] === this || classnamesCache[id] === undefined,
|
// Implementation
|
||||||
"Class name taken twice: " + id + " (from " + this.name + ")"
|
/** @returns {Schema} */
|
||||||
);
|
static getCachedSchema() {
|
||||||
classnamesCache[id] = this;
|
const id = this.getId();
|
||||||
/* dev:end */
|
|
||||||
|
/* dev:start */
|
||||||
const entry = globalSchemaCache[id];
|
assert(
|
||||||
if (entry) {
|
classnamesCache[id] === this || classnamesCache[id] === undefined,
|
||||||
return entry;
|
"Class name taken twice: " + id + " (from " + this.name + ")"
|
||||||
}
|
);
|
||||||
|
classnamesCache[id] = this;
|
||||||
const schema = this.getSchema();
|
/* dev:end */
|
||||||
globalSchemaCache[id] = schema;
|
|
||||||
return schema;
|
const entry = globalSchemaCache[id];
|
||||||
}
|
if (entry) {
|
||||||
|
return entry;
|
||||||
/** @returns {object} */
|
}
|
||||||
serialize() {
|
|
||||||
return serializeSchema(
|
const schema = this.getSchema();
|
||||||
this,
|
globalSchemaCache[id] = schema;
|
||||||
/** @type {typeof BasicSerializableObject} */ (this.constructor).getCachedSchema()
|
return schema;
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/** @returns {object} */
|
||||||
/**
|
serialize() {
|
||||||
* @param {any} data
|
return serializeSchema(
|
||||||
* @param {import("./savegame_serializer").GameRoot} root
|
this,
|
||||||
* @returns {string|void}
|
/** @type {typeof BasicSerializableObject} */ (this.constructor).getCachedSchema()
|
||||||
*/
|
);
|
||||||
deserialize(data, root = null) {
|
}
|
||||||
return deserializeSchema(
|
|
||||||
this,
|
/**
|
||||||
/** @type {typeof BasicSerializableObject} */ (this.constructor).getCachedSchema(),
|
* @param {any} data
|
||||||
data,
|
* @param {import("./savegame_serializer").GameRoot} root
|
||||||
null,
|
* @returns {string|void}
|
||||||
root
|
*/
|
||||||
);
|
deserialize(data, root = null) {
|
||||||
}
|
return deserializeSchema(
|
||||||
|
this,
|
||||||
/** @returns {string|void} */
|
/** @type {typeof BasicSerializableObject} */ (this.constructor).getCachedSchema(),
|
||||||
static verify(data) {
|
data,
|
||||||
return verifySchema(this.getCachedSchema(), data);
|
null,
|
||||||
}
|
root
|
||||||
}
|
);
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Serializes an object using the given schema, mergin with the given properties
|
/** @returns {string|void} */
|
||||||
* @param {object} obj The object to serialize
|
static verify(data) {
|
||||||
* @param {Schema} schema The schema to use
|
return verifySchema(this.getCachedSchema(), data);
|
||||||
* @param {object=} mergeWith Any additional properties to merge with the schema, useful for super calls
|
}
|
||||||
* @returns {object} Serialized data object
|
}
|
||||||
*/
|
|
||||||
export function serializeSchema(obj, schema, mergeWith = {}) {
|
/**
|
||||||
for (const key in schema) {
|
* Serializes an object using the given schema, mergin with the given properties
|
||||||
if (!obj.hasOwnProperty(key)) {
|
* @param {object} obj The object to serialize
|
||||||
logger.error("Invalid schema, property", key, "does not exist on", obj, "(schema=", schema, ")");
|
* @param {Schema} schema The schema to use
|
||||||
assert(
|
* @param {object=} mergeWith Any additional properties to merge with the schema, useful for super calls
|
||||||
obj.hasOwnProperty(key),
|
* @returns {object} Serialized data object
|
||||||
"serialization: invalid schema, property does not exist on object: " + key
|
*/
|
||||||
);
|
export function serializeSchema(obj, schema, mergeWith = {}) {
|
||||||
}
|
for (const key in schema) {
|
||||||
if (!schema[key]) {
|
if (!obj.hasOwnProperty(key)) {
|
||||||
assert(false, "Invalid schema (bad key '" + key + "'): " + JSON.stringify(schema));
|
logger.error("Invalid schema, property", key, "does not exist on", obj, "(schema=", schema, ")");
|
||||||
}
|
assert(
|
||||||
|
obj.hasOwnProperty(key),
|
||||||
if (G_IS_DEV) {
|
"serialization: invalid schema, property does not exist on object: " + key
|
||||||
try {
|
);
|
||||||
mergeWith[key] = schema[key].serialize(obj[key]);
|
}
|
||||||
} catch (ex) {
|
if (!schema[key]) {
|
||||||
logger.error(
|
assert(false, "Invalid schema (bad key '" + key + "'): " + JSON.stringify(schema));
|
||||||
"Serialization of",
|
}
|
||||||
obj,
|
|
||||||
"failed on key '" + key + "' ->",
|
if (G_IS_DEV) {
|
||||||
ex,
|
try {
|
||||||
"(schema was",
|
mergeWith[key] = schema[key].serialize(obj[key]);
|
||||||
schema,
|
} catch (ex) {
|
||||||
")"
|
logger.error(
|
||||||
);
|
"Serialization of",
|
||||||
throw ex;
|
obj,
|
||||||
}
|
"failed on key '" + key + "' ->",
|
||||||
} else {
|
ex,
|
||||||
mergeWith[key] = schema[key].serialize(obj[key]);
|
"(schema was",
|
||||||
}
|
schema,
|
||||||
}
|
")"
|
||||||
return mergeWith;
|
);
|
||||||
}
|
throw ex;
|
||||||
|
}
|
||||||
/**
|
} else {
|
||||||
* Deserializes data into an object
|
mergeWith[key] = schema[key].serialize(obj[key]);
|
||||||
* @param {object} obj The object to store the deserialized data into
|
}
|
||||||
* @param {Schema} schema The schema to use
|
}
|
||||||
* @param {object} data The serialized data
|
return mergeWith;
|
||||||
* @param {string|void|null=} baseclassErrorResult Convenience, if this is a string error code, do nothing and return it
|
}
|
||||||
* @param {import("../game/root").GameRoot=} root Optional game root reference
|
|
||||||
* @returns {string|void} String error code or nothing on success
|
/**
|
||||||
*/
|
* Deserializes data into an object
|
||||||
export function deserializeSchema(obj, schema, data, baseclassErrorResult = null, root) {
|
* @param {object} obj The object to store the deserialized data into
|
||||||
if (baseclassErrorResult) {
|
* @param {Schema} schema The schema to use
|
||||||
return baseclassErrorResult;
|
* @param {object} data The serialized data
|
||||||
}
|
* @param {string|void|null=} baseclassErrorResult Convenience, if this is a string error code, do nothing and return it
|
||||||
|
* @param {import("../game/root").GameRoot=} root Optional game root reference
|
||||||
if (!data) {
|
* @returns {string|void} String error code or nothing on success
|
||||||
logger.error("Got 'NULL' data for", obj, "and schema", schema, "!");
|
*/
|
||||||
return "Got null data";
|
export function deserializeSchema(obj, schema, data, baseclassErrorResult = null, root) {
|
||||||
}
|
if (baseclassErrorResult) {
|
||||||
|
return baseclassErrorResult;
|
||||||
for (const key in schema) {
|
}
|
||||||
if (!data.hasOwnProperty(key)) {
|
|
||||||
logger.error("Data", data, "does not contain", key, "(schema:", schema, ")");
|
if (!data) {
|
||||||
return "Missing key in schema: " + key + " of class " + obj.constructor.name;
|
logger.error("Got 'NULL' data for", obj, "and schema", schema, "!");
|
||||||
}
|
return "Got null data";
|
||||||
if (!schema[key].allowNull() && (data[key] === null || data[key] === undefined)) {
|
}
|
||||||
logger.error("Data", data, "has null value for", key, "(schema:", schema, ")");
|
|
||||||
return "Non-nullable entry is null: " + key + " of class " + obj.constructor.name;
|
for (const key in schema) {
|
||||||
}
|
if (!data.hasOwnProperty(key)) {
|
||||||
|
logger.error("Data", data, "does not contain", key, "(schema:", schema, ")");
|
||||||
const errorStatus = schema[key].deserializeWithVerify(data[key], obj, key, obj.root || root);
|
return "Missing key in schema: " + key + " of class " + obj.constructor.name;
|
||||||
if (errorStatus) {
|
}
|
||||||
logger.error(
|
if (!schema[key].allowNull() && (data[key] === null || data[key] === undefined)) {
|
||||||
"Deserialization failed with error '" + errorStatus + "' on object",
|
logger.error("Data", data, "has null value for", key, "(schema:", schema, ")");
|
||||||
obj,
|
return "Non-nullable entry is null: " + key + " of class " + obj.constructor.name;
|
||||||
"and key",
|
}
|
||||||
key,
|
|
||||||
"(root? =",
|
const errorStatus = schema[key].deserializeWithVerify(data[key], obj, key, obj.root || root);
|
||||||
obj.root ? "y" : "n",
|
if (errorStatus) {
|
||||||
")"
|
logger.error(
|
||||||
);
|
"Deserialization failed with error '" + errorStatus + "' on object",
|
||||||
return errorStatus;
|
obj,
|
||||||
}
|
"and key",
|
||||||
}
|
key,
|
||||||
}
|
"(root? =",
|
||||||
|
obj.root ? "y" : "n",
|
||||||
/**
|
")"
|
||||||
* Verifies stored data using the given schema
|
);
|
||||||
* @param {Schema} schema The schema to use
|
return errorStatus;
|
||||||
* @param {object} data The data to verify
|
}
|
||||||
* @returns {string|void} String error code or nothing on success
|
}
|
||||||
*/
|
}
|
||||||
export function verifySchema(schema, data) {
|
|
||||||
for (const key in schema) {
|
/**
|
||||||
if (!data.hasOwnProperty(key)) {
|
* Verifies stored data using the given schema
|
||||||
logger.error("Data", data, "does not contain", key, "(schema:", schema, ")");
|
* @param {Schema} schema The schema to use
|
||||||
return "verify: missing key required by schema in stored data: " + key;
|
* @param {object} data The data to verify
|
||||||
}
|
* @returns {string|void} String error code or nothing on success
|
||||||
if (!schema[key].allowNull() && (data[key] === null || data[key] === undefined)) {
|
*/
|
||||||
logger.error("Data", data, "has null value for", key, "(schema:", schema, ")");
|
export function verifySchema(schema, data) {
|
||||||
return "verify: non-nullable entry is null: " + key;
|
for (const key in schema) {
|
||||||
}
|
if (!data.hasOwnProperty(key)) {
|
||||||
|
logger.error("Data", data, "does not contain", key, "(schema:", schema, ")");
|
||||||
const errorStatus = schema[key].verifySerializedValue(data[key]);
|
return "verify: missing key required by schema in stored data: " + key;
|
||||||
if (errorStatus) {
|
}
|
||||||
logger.error(errorStatus);
|
if (!schema[key].allowNull() && (data[key] === null || data[key] === undefined)) {
|
||||||
return "verify: " + errorStatus;
|
logger.error("Data", data, "has null value for", key, "(schema:", schema, ")");
|
||||||
}
|
return "verify: non-nullable entry is null: " + key;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const errorStatus = schema[key].verifySerializedValue(data[key]);
|
||||||
/**
|
if (errorStatus) {
|
||||||
* Extends a schema by adding the properties from the new schema to the existing base schema
|
logger.error(errorStatus);
|
||||||
* @param {Schema} base
|
return "verify: " + errorStatus;
|
||||||
* @param {Schema} newOne
|
}
|
||||||
* @returns {Schema}
|
}
|
||||||
*/
|
}
|
||||||
export function extendSchema(base, newOne) {
|
|
||||||
/** @type {Schema} */
|
/**
|
||||||
const result = Object.assign({}, base);
|
* Extends a schema by adding the properties from the new schema to the existing base schema
|
||||||
for (const key in newOne) {
|
* @param {Schema} base
|
||||||
if (result.hasOwnProperty(key)) {
|
* @param {Schema} newOne
|
||||||
logger.error("Extend schema got duplicate key:", key);
|
* @returns {Schema}
|
||||||
continue;
|
*/
|
||||||
}
|
export function extendSchema(base, newOne) {
|
||||||
result[key] = newOne[key];
|
/** @type {Schema} */
|
||||||
}
|
const result = Object.assign({}, base);
|
||||||
return result;
|
for (const key in newOne) {
|
||||||
}
|
if (result.hasOwnProperty(key)) {
|
||||||
|
logger.error("Extend schema got duplicate key:", key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result[key] = newOne[key];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@ -80,7 +80,10 @@ export class SerializerInternal {
|
|||||||
for (const componentId in data) {
|
for (const componentId in data) {
|
||||||
if (!entity.components[componentId]) {
|
if (!entity.components[componentId]) {
|
||||||
if (G_IS_DEV && !globalConfig.debug.disableSlowAsserts) {
|
if (G_IS_DEV && !globalConfig.debug.disableSlowAsserts) {
|
||||||
logger.warn("Entity no longer has component:", componentId);
|
// @ts-ignore
|
||||||
|
if (++window.componentWarningsShown < 100) {
|
||||||
|
logger.warn("Entity no longer has component:", componentId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -285,14 +285,10 @@ export class InGameState extends GameState {
|
|||||||
*/
|
*/
|
||||||
stage7Warmup() {
|
stage7Warmup() {
|
||||||
if (this.switchStage(stages.s7_warmup)) {
|
if (this.switchStage(stages.s7_warmup)) {
|
||||||
if (G_IS_DEV && globalConfig.debug.noArtificialDelays) {
|
if (this.creationPayload.fastEnter) {
|
||||||
this.warmupTimeSeconds = 0.05;
|
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
|
||||||
} else {
|
} else {
|
||||||
if (this.creationPayload.fastEnter) {
|
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
|
||||||
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
|
|
||||||
} else {
|
|
||||||
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,179 +1,173 @@
|
|||||||
import { TextualGameState } from "../core/textual_game_state";
|
import { Dialog } from "../core/modal_dialog_elements";
|
||||||
import { SOUNDS } from "../platform/sound";
|
import { TextualGameState } from "../core/textual_game_state";
|
||||||
import { T } from "../translations";
|
import { getStringForKeyCode, KEYMAPPINGS } from "../game/key_action_mapper";
|
||||||
import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper";
|
import { SOUNDS } from "../platform/sound";
|
||||||
import { Dialog } from "../core/modal_dialog_elements";
|
import { T } from "../translations";
|
||||||
import { IS_DEMO } from "../core/config";
|
|
||||||
|
export class KeybindingsState extends TextualGameState {
|
||||||
export class KeybindingsState extends TextualGameState {
|
constructor() {
|
||||||
constructor() {
|
super("KeybindingsState");
|
||||||
super("KeybindingsState");
|
}
|
||||||
}
|
|
||||||
|
getStateHeaderTitle() {
|
||||||
getStateHeaderTitle() {
|
return T.keybindings.title;
|
||||||
return T.keybindings.title;
|
}
|
||||||
}
|
|
||||||
|
getMainContentHTML() {
|
||||||
getMainContentHTML() {
|
return `
|
||||||
return `
|
|
||||||
|
<div class="topEntries">
|
||||||
<div class="topEntries">
|
<span class="hint">${T.keybindings.hint}</span>
|
||||||
<span class="hint">${T.keybindings.hint}</span>
|
<button class="styledButton resetBindings">${T.keybindings.resetKeybindings}</button>
|
||||||
<button class="styledButton resetBindings">${T.keybindings.resetKeybindings}</button>
|
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="keybindings">
|
||||||
<div class="keybindings">
|
|
||||||
|
</div>
|
||||||
</div>
|
`;
|
||||||
`;
|
}
|
||||||
}
|
|
||||||
|
onEnter() {
|
||||||
onEnter() {
|
const keybindingsElem = this.htmlElement.querySelector(".keybindings");
|
||||||
const keybindingsElem = this.htmlElement.querySelector(".keybindings");
|
|
||||||
|
this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings);
|
||||||
this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings);
|
|
||||||
|
for (const category in KEYMAPPINGS) {
|
||||||
for (const category in KEYMAPPINGS) {
|
const categoryDiv = document.createElement("div");
|
||||||
const categoryDiv = document.createElement("div");
|
categoryDiv.classList.add("category");
|
||||||
categoryDiv.classList.add("category");
|
keybindingsElem.appendChild(categoryDiv);
|
||||||
keybindingsElem.appendChild(categoryDiv);
|
|
||||||
|
const labelDiv = document.createElement("strong");
|
||||||
const labelDiv = document.createElement("strong");
|
labelDiv.innerText = T.keybindings.categoryLabels[category];
|
||||||
labelDiv.innerText = T.keybindings.categoryLabels[category];
|
labelDiv.classList.add("categoryLabel");
|
||||||
labelDiv.classList.add("categoryLabel");
|
categoryDiv.appendChild(labelDiv);
|
||||||
categoryDiv.appendChild(labelDiv);
|
|
||||||
|
for (const keybindingId in KEYMAPPINGS[category]) {
|
||||||
for (const keybindingId in KEYMAPPINGS[category]) {
|
const mapped = KEYMAPPINGS[category][keybindingId];
|
||||||
const mapped = KEYMAPPINGS[category][keybindingId];
|
|
||||||
|
const elem = document.createElement("div");
|
||||||
const elem = document.createElement("div");
|
elem.classList.add("entry");
|
||||||
elem.classList.add("entry");
|
elem.setAttribute("data-keybinding", keybindingId);
|
||||||
elem.setAttribute("data-keybinding", keybindingId);
|
categoryDiv.appendChild(elem);
|
||||||
categoryDiv.appendChild(elem);
|
|
||||||
|
const title = document.createElement("span");
|
||||||
const title = document.createElement("span");
|
title.classList.add("title");
|
||||||
title.classList.add("title");
|
title.innerText = T.keybindings.mappings[keybindingId];
|
||||||
title.innerText = T.keybindings.mappings[keybindingId];
|
elem.appendChild(title);
|
||||||
elem.appendChild(title);
|
|
||||||
|
const mappingDiv = document.createElement("span");
|
||||||
const mappingDiv = document.createElement("span");
|
mappingDiv.classList.add("mapping");
|
||||||
mappingDiv.classList.add("mapping");
|
elem.appendChild(mappingDiv);
|
||||||
elem.appendChild(mappingDiv);
|
|
||||||
|
const editBtn = document.createElement("button");
|
||||||
const editBtn = document.createElement("button");
|
editBtn.classList.add("styledButton", "editKeybinding");
|
||||||
editBtn.classList.add("styledButton", "editKeybinding");
|
|
||||||
|
const resetBtn = document.createElement("button");
|
||||||
const resetBtn = document.createElement("button");
|
resetBtn.classList.add("styledButton", "resetKeybinding");
|
||||||
resetBtn.classList.add("styledButton", "resetKeybinding");
|
|
||||||
|
if (mapped.builtin) {
|
||||||
if (mapped.builtin) {
|
editBtn.classList.add("disabled");
|
||||||
editBtn.classList.add("disabled");
|
resetBtn.classList.add("disabled");
|
||||||
resetBtn.classList.add("disabled");
|
} else {
|
||||||
} else {
|
this.trackClicks(editBtn, () => this.editKeybinding(keybindingId));
|
||||||
this.trackClicks(editBtn, () => this.editKeybinding(keybindingId));
|
this.trackClicks(resetBtn, () => this.resetKeybinding(keybindingId));
|
||||||
this.trackClicks(resetBtn, () => this.resetKeybinding(keybindingId));
|
}
|
||||||
}
|
elem.appendChild(editBtn);
|
||||||
elem.appendChild(editBtn);
|
elem.appendChild(resetBtn);
|
||||||
elem.appendChild(resetBtn);
|
}
|
||||||
}
|
}
|
||||||
}
|
this.updateKeybindings();
|
||||||
this.updateKeybindings();
|
}
|
||||||
}
|
|
||||||
|
editKeybinding(id) {
|
||||||
editKeybinding(id) {
|
const dialog = new Dialog({
|
||||||
// if (IS_DEMO) {
|
app: this.app,
|
||||||
// this.dialogs.showFeatureRestrictionInfo(T.demo.features.customizeKeybindings);
|
title: T.dialogs.editKeybinding.title,
|
||||||
// return;
|
contentHTML: T.dialogs.editKeybinding.desc,
|
||||||
// }
|
buttons: ["cancel:good"],
|
||||||
|
type: "info",
|
||||||
const dialog = new Dialog({
|
});
|
||||||
app: this.app,
|
|
||||||
title: T.dialogs.editKeybinding.title,
|
dialog.inputReciever.keydown.add(({ keyCode, shift, alt, event }) => {
|
||||||
contentHTML: T.dialogs.editKeybinding.desc,
|
if (keyCode === 27) {
|
||||||
buttons: ["cancel:good"],
|
this.dialogs.closeDialog(dialog);
|
||||||
type: "info",
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
dialog.inputReciever.keydown.add(({ keyCode, shift, alt, event }) => {
|
if (event) {
|
||||||
if (keyCode === 27) {
|
event.preventDefault();
|
||||||
this.dialogs.closeDialog(dialog);
|
}
|
||||||
return;
|
|
||||||
}
|
if (event.target && event.target.tagName === "BUTTON" && keyCode === 1) {
|
||||||
|
return;
|
||||||
if (event) {
|
}
|
||||||
event.preventDefault();
|
|
||||||
}
|
if (
|
||||||
|
// Enter
|
||||||
if (event.target && event.target.tagName === "BUTTON" && keyCode === 1) {
|
keyCode === 13
|
||||||
return;
|
) {
|
||||||
}
|
// Ignore builtins
|
||||||
|
return;
|
||||||
if (
|
}
|
||||||
// Enter
|
|
||||||
keyCode === 13
|
this.app.settings.updateKeybindingOverride(id, keyCode);
|
||||||
) {
|
|
||||||
// Ignore builtins
|
this.dialogs.closeDialog(dialog);
|
||||||
return;
|
this.updateKeybindings();
|
||||||
}
|
});
|
||||||
|
|
||||||
this.app.settings.updateKeybindingOverride(id, keyCode);
|
dialog.inputReciever.backButton.add(() => {});
|
||||||
|
this.dialogs.internalShowDialog(dialog);
|
||||||
this.dialogs.closeDialog(dialog);
|
|
||||||
this.updateKeybindings();
|
this.app.sound.playUiSound(SOUNDS.dialogOk);
|
||||||
});
|
}
|
||||||
|
|
||||||
dialog.inputReciever.backButton.add(() => {});
|
updateKeybindings() {
|
||||||
this.dialogs.internalShowDialog(dialog);
|
const overrides = this.app.settings.getKeybindingOverrides();
|
||||||
|
for (const category in KEYMAPPINGS) {
|
||||||
this.app.sound.playUiSound(SOUNDS.dialogOk);
|
for (const keybindingId in KEYMAPPINGS[category]) {
|
||||||
}
|
const mapped = KEYMAPPINGS[category][keybindingId];
|
||||||
|
|
||||||
updateKeybindings() {
|
const container = this.htmlElement.querySelector("[data-keybinding='" + keybindingId + "']");
|
||||||
const overrides = this.app.settings.getKeybindingOverrides();
|
assert(container, "Container for keybinding not found: " + keybindingId);
|
||||||
for (const category in KEYMAPPINGS) {
|
|
||||||
for (const keybindingId in KEYMAPPINGS[category]) {
|
let keyCode = mapped.keyCode;
|
||||||
const mapped = KEYMAPPINGS[category][keybindingId];
|
if (overrides[keybindingId]) {
|
||||||
|
keyCode = overrides[keybindingId];
|
||||||
const container = this.htmlElement.querySelector("[data-keybinding='" + keybindingId + "']");
|
}
|
||||||
assert(container, "Container for keybinding not found: " + keybindingId);
|
|
||||||
|
const mappingDiv = container.querySelector(".mapping");
|
||||||
let keyCode = mapped.keyCode;
|
mappingDiv.innerHTML = getStringForKeyCode(keyCode);
|
||||||
if (overrides[keybindingId]) {
|
mappingDiv.classList.toggle("changed", !!overrides[keybindingId]);
|
||||||
keyCode = overrides[keybindingId];
|
|
||||||
}
|
const resetBtn = container.querySelector("button.resetKeybinding");
|
||||||
|
resetBtn.classList.toggle("disabled", mapped.builtin || !overrides[keybindingId]);
|
||||||
const mappingDiv = container.querySelector(".mapping");
|
}
|
||||||
mappingDiv.innerHTML = getStringForKeyCode(keyCode);
|
}
|
||||||
mappingDiv.classList.toggle("changed", !!overrides[keybindingId]);
|
}
|
||||||
|
|
||||||
const resetBtn = container.querySelector("button.resetKeybinding");
|
resetKeybinding(id) {
|
||||||
resetBtn.classList.toggle("disabled", mapped.builtin || !overrides[keybindingId]);
|
this.app.settings.resetKeybindingOverride(id);
|
||||||
}
|
this.updateKeybindings();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
resetBindings() {
|
||||||
resetKeybinding(id) {
|
const { reset } = this.dialogs.showWarning(
|
||||||
this.app.settings.resetKeybindingOverride(id);
|
T.dialogs.resetKeybindingsConfirmation.title,
|
||||||
this.updateKeybindings();
|
T.dialogs.resetKeybindingsConfirmation.desc,
|
||||||
}
|
["cancel:good", "reset:bad"]
|
||||||
|
);
|
||||||
resetBindings() {
|
|
||||||
const { reset } = this.dialogs.showWarning(
|
reset.add(() => {
|
||||||
T.dialogs.resetKeybindingsConfirmation.title,
|
this.app.settings.resetKeybindingOverrides();
|
||||||
T.dialogs.resetKeybindingsConfirmation.desc,
|
this.updateKeybindings();
|
||||||
["cancel:good", "reset:bad"]
|
|
||||||
);
|
this.dialogs.showInfo(T.dialogs.keybindingsResetOk.title, T.dialogs.keybindingsResetOk.desc);
|
||||||
|
});
|
||||||
reset.add(() => {
|
}
|
||||||
this.app.settings.resetKeybindingOverrides();
|
|
||||||
this.updateKeybindings();
|
getDefaultPreviousState() {
|
||||||
|
return "SettingsState";
|
||||||
this.dialogs.showInfo(T.dialogs.keybindingsResetOk.title, T.dialogs.keybindingsResetOk.desc);
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultPreviousState() {
|
|
||||||
return "SettingsState";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
import { GameState } from "../core/game_state";
|
|
||||||
import { cachebust } from "../core/cachebust";
|
import { cachebust } from "../core/cachebust";
|
||||||
import { A_B_TESTING_LINK_TYPE, globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
|
import { A_B_TESTING_LINK_TYPE, globalConfig, THIRDPARTY_URLS } from "../core/config";
|
||||||
|
import { GameState } from "../core/game_state";
|
||||||
|
import { DialogWithForm } from "../core/modal_dialog_elements";
|
||||||
|
import { FormElementInput } from "../core/modal_dialog_forms";
|
||||||
|
import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||||
import {
|
import {
|
||||||
makeDiv,
|
|
||||||
makeButtonElement,
|
|
||||||
formatSecondsToTimeAgo,
|
formatSecondsToTimeAgo,
|
||||||
waitNextFrame,
|
generateFileDownload,
|
||||||
isSupportedBrowser,
|
isSupportedBrowser,
|
||||||
makeButton,
|
makeButton,
|
||||||
|
makeButtonElement,
|
||||||
|
makeDiv,
|
||||||
removeAllChildren,
|
removeAllChildren,
|
||||||
|
startFileChoose,
|
||||||
|
waitNextFrame,
|
||||||
} from "../core/utils";
|
} from "../core/utils";
|
||||||
import { ReadWriteProxy } from "../core/read_write_proxy";
|
|
||||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||||
import { T } from "../translations";
|
|
||||||
import { getApplicationSettingById } from "../profile/application_settings";
|
import { getApplicationSettingById } from "../profile/application_settings";
|
||||||
import { FormElementInput } from "../core/modal_dialog_forms";
|
import { T } from "../translations";
|
||||||
import { DialogWithForm } from "../core/modal_dialog_elements";
|
|
||||||
|
|
||||||
const trim = require("trim");
|
const trim = require("trim");
|
||||||
|
|
||||||
@ -24,23 +26,6 @@ const trim = require("trim");
|
|||||||
* @typedef {import("../profile/setting_types").EnumSetting} EnumSetting
|
* @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 {
|
export class MainMenuState extends GameState {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("MainMenuState");
|
super("MainMenuState");
|
||||||
@ -49,18 +34,16 @@ export class MainMenuState extends GameState {
|
|||||||
getInnerHTML() {
|
getInnerHTML() {
|
||||||
const bannerHtml = `
|
const bannerHtml = `
|
||||||
<h3>${T.demoBanners.title}</h3>
|
<h3>${T.demoBanners.title}</h3>
|
||||||
|
|
||||||
<p>${T.demoBanners.intro}</p>
|
<p>${T.demoBanners.intro}</p>
|
||||||
|
|
||||||
<a href="#" class="steamLink ${A_B_TESTING_LINK_TYPE}" target="_blank">Get the shapez.io standalone!</a>
|
<a href="#" class="steamLink ${A_B_TESTING_LINK_TYPE}" target="_blank">Get the shapez.io standalone!</a>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return `
|
const showDemoBadges = this.app.restrictionMgr.getIsStandaloneMarketingActive();
|
||||||
|
|
||||||
|
return `
|
||||||
<div class="topButtons">
|
<div class="topButtons">
|
||||||
<button class="languageChoose" data-languageicon="${this.app.settings.getLanguage()}"></button>
|
<button class="languageChoose" data-languageicon="${this.app.settings.getLanguage()}"></button>
|
||||||
<button class="settingsButton"></button>
|
<button class="settingsButton"></button>
|
||||||
|
|
||||||
${
|
${
|
||||||
G_IS_STANDALONE || G_IS_DEV
|
G_IS_STANDALONE || G_IS_DEV
|
||||||
? `
|
? `
|
||||||
@ -74,17 +57,14 @@ export class MainMenuState extends GameState {
|
|||||||
<source src="${cachebust("res/bg_render.webm")}" type="video/webm">
|
<source src="${cachebust("res/bg_render.webm")}" type="video/webm">
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
|
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
|
||||||
<span class="updateLabel">Wires update!</span>
|
<span class="updateLabel">Wires update!</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">
|
||||||
<div class="mainWrapper ${IS_DEMO ? "demo" : "noDemo"}">
|
|
||||||
|
|
||||||
<div class="sideContainer">
|
<div class="sideContainer">
|
||||||
${IS_DEMO ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
|
${showDemoBadges ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mainContainer">
|
<div class="mainContainer">
|
||||||
@ -95,12 +75,9 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
<div class="buttons"></div>
|
<div class="buttons"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
|
|
||||||
<a class="githubLink boxLink" target="_blank">
|
<a class="githubLink boxLink" target="_blank">
|
||||||
${T.mainMenu.openSourceHint}
|
${T.mainMenu.openSourceHint}
|
||||||
<span class="thirdpartyLogo githubLogo"></span>
|
<span class="thirdpartyLogo githubLogo"></span>
|
||||||
@ -123,32 +100,29 @@ export class MainMenuState extends GameState {
|
|||||||
"<author-link>",
|
"<author-link>",
|
||||||
'<a class="producerLink" target="_blank">Tobias Springer</a>'
|
'<a class="producerLink" target="_blank">Tobias Springer</a>'
|
||||||
)}</div>
|
)}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks the user to import a savegame
|
||||||
|
*/
|
||||||
requestImportSavegame() {
|
requestImportSavegame() {
|
||||||
if (
|
if (
|
||||||
IS_DEMO &&
|
|
||||||
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
|
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
|
||||||
!this.app.platformWrapper.getHasUnlimitedSavegames()
|
!this.app.restrictionMgr.getHasUnlimitedSavegames()
|
||||||
) {
|
) {
|
||||||
this.app.analytics.trackUiClick("importgame_slot_limit_show");
|
this.app.analytics.trackUiClick("importgame_slot_limit_show");
|
||||||
this.showSavegameSlotLimit();
|
this.showSavegameSlotLimit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var input = document.createElement("input");
|
// Create a 'fake' file-input to accept savegames
|
||||||
input.type = "file";
|
startFileChoose(".bin").then(file => {
|
||||||
input.accept = ".bin";
|
|
||||||
|
|
||||||
input.onchange = e => {
|
|
||||||
const file = input.files[0];
|
|
||||||
if (file) {
|
if (file) {
|
||||||
|
const closeLoader = this.dialogs.showLoadingDialog();
|
||||||
waitNextFrame().then(() => {
|
waitNextFrame().then(() => {
|
||||||
this.app.analytics.trackUiClick("import_savegame");
|
this.app.analytics.trackUiClick("import_savegame");
|
||||||
const closeLoader = this.dialogs.showLoadingDialog();
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.addEventListener("load", event => {
|
reader.addEventListener("load", event => {
|
||||||
const contents = event.target.result;
|
const contents = event.target.result;
|
||||||
@ -194,8 +168,7 @@ export class MainMenuState extends GameState {
|
|||||||
reader.readAsText(file, "utf-8");
|
reader.readAsText(file, "utf-8");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
input.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackButton() {
|
onBackButton() {
|
||||||
@ -347,20 +320,23 @@ export class MainMenuState extends GameState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
optionSelected.add(value => {
|
optionSelected.add(value => {
|
||||||
this.app.settings.updateLanguage(value);
|
this.app.settings.updateLanguage(value).then(() => {
|
||||||
if (setting.restartRequired) {
|
if (setting.restartRequired) {
|
||||||
if (this.app.platformWrapper.getSupportsRestart()) {
|
if (this.app.platformWrapper.getSupportsRestart()) {
|
||||||
this.app.platformWrapper.performRestart();
|
this.app.platformWrapper.performRestart();
|
||||||
} else {
|
} else {
|
||||||
this.dialogs.showInfo(T.dialogs.restartRequired.title, T.dialogs.restartRequired.text, [
|
this.dialogs.showInfo(
|
||||||
"ok:good",
|
T.dialogs.restartRequired.title,
|
||||||
]);
|
T.dialogs.restartRequired.text,
|
||||||
|
["ok:good"]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (setting.changeCb) {
|
if (setting.changeCb) {
|
||||||
setting.changeCb(this.app, value);
|
setting.changeCb(this.app, value);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Update current icon
|
// Update current icon
|
||||||
this.htmlElement.querySelector("button.languageChoose").setAttribute("data-languageIcon", value);
|
this.htmlElement.querySelector("button.languageChoose").setAttribute("data-languageIcon", value);
|
||||||
@ -557,9 +533,8 @@ export class MainMenuState extends GameState {
|
|||||||
|
|
||||||
onPlayButtonClicked() {
|
onPlayButtonClicked() {
|
||||||
if (
|
if (
|
||||||
IS_DEMO &&
|
|
||||||
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
|
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
|
||||||
!this.app.platformWrapper.getHasUnlimitedSavegames()
|
!this.app.restrictionMgr.getHasUnlimitedSavegames()
|
||||||
) {
|
) {
|
||||||
this.app.analytics.trackUiClick("startgame_slot_limit_show");
|
this.app.analytics.trackUiClick("startgame_slot_limit_show");
|
||||||
this.showSavegameSlotLimit();
|
this.showSavegameSlotLimit();
|
||||||
|
|||||||
@ -145,6 +145,11 @@ export class PreloadState extends GameState {
|
|||||||
this.app.backgroundResourceLoader.startLoading();
|
this.app.backgroundResourceLoader.startLoading();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.then(() => this.setStatus("Initializing restrictions"))
|
||||||
|
.then(() => {
|
||||||
|
return this.app.restrictionMgr.initialize();
|
||||||
|
})
|
||||||
|
|
||||||
.then(() => this.setStatus("Initializing savegame"))
|
.then(() => this.setStatus("Initializing savegame"))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.app.savegameMgr.initialize().catch(err => {
|
return this.app.savegameMgr.initialize().catch(err => {
|
||||||
|
|||||||
@ -1,169 +1,169 @@
|
|||||||
import { TextualGameState } from "../core/textual_game_state";
|
import { TextualGameState } from "../core/textual_game_state";
|
||||||
import { formatSecondsToTimeAgo } from "../core/utils";
|
import { formatSecondsToTimeAgo } from "../core/utils";
|
||||||
import { allApplicationSettings, enumCategories } from "../profile/application_settings";
|
import { allApplicationSettings, enumCategories } from "../profile/application_settings";
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
|
|
||||||
export class SettingsState extends TextualGameState {
|
export class SettingsState extends TextualGameState {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("SettingsState");
|
super("SettingsState");
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateHeaderTitle() {
|
getStateHeaderTitle() {
|
||||||
return T.settings.title;
|
return T.settings.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMainContentHTML() {
|
getMainContentHTML() {
|
||||||
return `
|
return `
|
||||||
|
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
${this.getCategoryButtonsHtml()}
|
${this.getCategoryButtonsHtml()}
|
||||||
|
|
||||||
${
|
${
|
||||||
this.app.platformWrapper.getSupportsKeyboard()
|
this.app.platformWrapper.getSupportsKeyboard()
|
||||||
? `
|
? `
|
||||||
<button class="styledButton categoryButton editKeybindings">
|
<button class="styledButton categoryButton editKeybindings">
|
||||||
${T.keybindings.title}
|
${T.keybindings.title}
|
||||||
</button>`
|
</button>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="other">
|
<div class="other">
|
||||||
<button class="styledButton about">${T.about.title}</button>
|
<button class="styledButton about">${T.about.title}</button>
|
||||||
|
|
||||||
<div class="versionbar">
|
<div class="versionbar">
|
||||||
<div class="buildVersion">${T.global.loading} ...</div>
|
<div class="buildVersion">${T.global.loading} ...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="categoryContainer">
|
<div class="categoryContainer">
|
||||||
${this.getSettingsHtml()}
|
${this.getSettingsHtml()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategoryButtonsHtml() {
|
getCategoryButtonsHtml() {
|
||||||
return Object.keys(enumCategories)
|
return Object.keys(enumCategories)
|
||||||
.map(key => enumCategories[key])
|
.map(key => enumCategories[key])
|
||||||
.map(
|
.map(
|
||||||
category =>
|
category =>
|
||||||
`
|
`
|
||||||
<button class="styledButton categoryButton" data-category-btn="${category}">
|
<button class="styledButton categoryButton" data-category-btn="${category}">
|
||||||
${T.settings.categories[category]}
|
${T.settings.categories[category]}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
getSettingsHtml() {
|
getSettingsHtml() {
|
||||||
const categoriesHTML = {};
|
const categoriesHTML = {};
|
||||||
|
|
||||||
Object.keys(enumCategories).forEach(key => {
|
Object.keys(enumCategories).forEach(key => {
|
||||||
const catName = enumCategories[key];
|
const catName = enumCategories[key];
|
||||||
categoriesHTML[catName] = `<div class="category" data-category="${catName}">`;
|
categoriesHTML[catName] = `<div class="category" data-category="${catName}">`;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = 0; i < allApplicationSettings.length; ++i) {
|
for (let i = 0; i < allApplicationSettings.length; ++i) {
|
||||||
const setting = allApplicationSettings[i];
|
const setting = allApplicationSettings[i];
|
||||||
|
|
||||||
categoriesHTML[setting.categoryId] += setting.getHtml();
|
categoriesHTML[setting.categoryId] += setting.getHtml(this.app);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(categoriesHTML)
|
return Object.keys(categoriesHTML)
|
||||||
.map(k => categoriesHTML[k] + "</div>")
|
.map(k => categoriesHTML[k] + "</div>")
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBuildText() {
|
renderBuildText() {
|
||||||
const labelVersion = this.htmlElement.querySelector(".buildVersion");
|
const labelVersion = this.htmlElement.querySelector(".buildVersion");
|
||||||
const lastBuildMs = new Date().getTime() - G_BUILD_TIME;
|
const lastBuildMs = new Date().getTime() - G_BUILD_TIME;
|
||||||
const lastBuildText = formatSecondsToTimeAgo(lastBuildMs / 1000.0);
|
const lastBuildText = formatSecondsToTimeAgo(lastBuildMs / 1000.0);
|
||||||
|
|
||||||
const version = T.settings.versionBadges[G_APP_ENVIRONMENT];
|
const version = T.settings.versionBadges[G_APP_ENVIRONMENT];
|
||||||
|
|
||||||
labelVersion.innerHTML = `
|
labelVersion.innerHTML = `
|
||||||
<span class='version'>
|
<span class='version'>
|
||||||
${G_BUILD_VERSION} @ ${version} @ ${G_BUILD_COMMIT_HASH}
|
${G_BUILD_VERSION} @ ${version} @ ${G_BUILD_COMMIT_HASH}
|
||||||
</span>
|
</span>
|
||||||
<span class='buildTime'>
|
<span class='buildTime'>
|
||||||
${T.settings.buildDate.replace("<at-date>", lastBuildText)}<br />
|
${T.settings.buildDate.replace("<at-date>", lastBuildText)}<br />
|
||||||
</span>`;
|
</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnter(payload) {
|
onEnter(payload) {
|
||||||
this.renderBuildText();
|
this.renderBuildText();
|
||||||
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
|
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
|
||||||
preventDefault: false,
|
preventDefault: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
|
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
|
||||||
|
|
||||||
if (keybindingsButton) {
|
if (keybindingsButton) {
|
||||||
this.trackClicks(keybindingsButton, this.onKeybindingsClicked, { preventDefault: false });
|
this.trackClicks(keybindingsButton, this.onKeybindingsClicked, { preventDefault: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initSettings();
|
this.initSettings();
|
||||||
this.initCategoryButtons();
|
this.initCategoryButtons();
|
||||||
|
|
||||||
this.htmlElement.querySelector(".category").classList.add("active");
|
this.htmlElement.querySelector(".category").classList.add("active");
|
||||||
this.htmlElement.querySelector(".categoryButton").classList.add("active");
|
this.htmlElement.querySelector(".categoryButton").classList.add("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveCategory(category) {
|
setActiveCategory(category) {
|
||||||
const previousCategory = this.htmlElement.querySelector(".category.active");
|
const previousCategory = this.htmlElement.querySelector(".category.active");
|
||||||
const previousCategoryButton = this.htmlElement.querySelector(".categoryButton.active");
|
const previousCategoryButton = this.htmlElement.querySelector(".categoryButton.active");
|
||||||
|
|
||||||
if (previousCategory.getAttribute("data-category") == category) {
|
if (previousCategory.getAttribute("data-category") == category) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
previousCategory.classList.remove("active");
|
previousCategory.classList.remove("active");
|
||||||
previousCategoryButton.classList.remove("active");
|
previousCategoryButton.classList.remove("active");
|
||||||
|
|
||||||
const newCategory = this.htmlElement.querySelector("[data-category='" + category + "']");
|
const newCategory = this.htmlElement.querySelector("[data-category='" + category + "']");
|
||||||
const newCategoryButton = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
|
const newCategoryButton = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
|
||||||
|
|
||||||
newCategory.classList.add("active");
|
newCategory.classList.add("active");
|
||||||
newCategoryButton.classList.add("active");
|
newCategoryButton.classList.add("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
initSettings() {
|
initSettings() {
|
||||||
allApplicationSettings.forEach(setting => {
|
allApplicationSettings.forEach(setting => {
|
||||||
/** @type {HTMLElement} */
|
/** @type {HTMLElement} */
|
||||||
const element = this.htmlElement.querySelector("[data-setting='" + setting.id + "']");
|
const element = this.htmlElement.querySelector("[data-setting='" + setting.id + "']");
|
||||||
setting.bind(this.app, element, this.dialogs);
|
setting.bind(this.app, element, this.dialogs);
|
||||||
setting.syncValueToElement();
|
setting.syncValueToElement();
|
||||||
this.trackClicks(
|
this.trackClicks(
|
||||||
element,
|
element,
|
||||||
() => {
|
() => {
|
||||||
setting.modify();
|
setting.modify();
|
||||||
},
|
},
|
||||||
{ preventDefault: false }
|
{ preventDefault: false }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initCategoryButtons() {
|
initCategoryButtons() {
|
||||||
Object.keys(enumCategories).forEach(key => {
|
Object.keys(enumCategories).forEach(key => {
|
||||||
const category = enumCategories[key];
|
const category = enumCategories[key];
|
||||||
const button = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
|
const button = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
|
||||||
this.trackClicks(
|
this.trackClicks(
|
||||||
button,
|
button,
|
||||||
() => {
|
() => {
|
||||||
this.setActiveCategory(category);
|
this.setActiveCategory(category);
|
||||||
},
|
},
|
||||||
{ preventDefault: false }
|
{ preventDefault: false }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onAboutClicked() {
|
onAboutClicked() {
|
||||||
this.moveToStateAddGoBack("AboutState");
|
this.moveToStateAddGoBack("AboutState");
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeybindingsClicked() {
|
onKeybindingsClicked() {
|
||||||
this.moveToStateAddGoBack("KeybindingsState");
|
this.moveToStateAddGoBack("KeybindingsState");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ The base language is English and can be found [here](base-en.yaml).
|
|||||||
- [Ukrainian](base-uk.yaml)
|
- [Ukrainian](base-uk.yaml)
|
||||||
- [Indonesian](base-ind.yaml)
|
- [Indonesian](base-ind.yaml)
|
||||||
- [Serbian](base-sr.yaml)
|
- [Serbian](base-sr.yaml)
|
||||||
|
- [Czech](base-cz.yaml)
|
||||||
|
|
||||||
(If you want to translate into a new language, see below!)
|
(If you want to translate into a new language, see below!)
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,17 @@
|
|||||||
steamPage:
|
steamPage:
|
||||||
shortText: shapez.io is a game about building factories to automate the creation
|
shortText: لعبة شيبز (أشكال) هي لعبة تدور حول بناء مصانع وتوصيلها حتى تقوم بشكل
|
||||||
and processing of increasingly complex shapes across an infinitely
|
.آلي بصناعة أشكال مختلفة تزداد تعقيدا في خريطة لانهائية.
|
||||||
expanding map.
|
|
||||||
discordLinkShort: Official Discord
|
discordLinkShort: Official Discord
|
||||||
intro: >-
|
intro: >-
|
||||||
Shapez.io is a relaxed game in which you have to build factories for the
|
لعبة شيبز (أشكال) هي لعبة مريحة تقوم فيها ببناء مصانع ووتشغيلها آليا
|
||||||
automated production of geometric shapes.
|
لصناعة أشكال هندسية.
|
||||||
|
|
||||||
As the level increases, the shapes become more and more complex, and you have to spread out on the infinite map.
|
مع التقدم في المستوى، تزداد الأشكال تعقيداً، فيتوجب عليك التوسع في الخريطة اللانهائية، وذلك ليس كافياً للتقدم في مستوى اللعبة حيث عليك صناعة المزيد بأضعاف مضاعفة لتلبية الطلب، الشيء الوحيد الذي يمكنه مساعدتك هو التوسع.
|
||||||
|
|
||||||
And as if that wasn't enough, you also have to produce exponentially more to satisfy the demands - the only thing that helps is scaling!
|
بينما في البداية تقوم بصناعة أشكال مختلفة، تتطلب منك المراحل المتقدمة تلوين هذه الأشكال، حيث يتوجب عليك استخراج وخلط الألوان.
|
||||||
|
|
||||||
While you only process shapes at the beginning, you have to color them later - for this you have to extract and mix colors!
|
عند شراءك اللعبة على ستيم (Steam) تحصل على الإصدار الكامل للعبة، ولكن يمكن أيضاً لعبة نسخة تجريبية على موقع shapez.io ثم يمكنك القرار لاحقا
|
||||||
|
title_advantages: ميزات نسخة الحاسوب
|
||||||
Buying the game on Steam gives you access to the full version, but you can also play a demo on shapez.io first and decide later!
|
|
||||||
title_advantages: Standalone Advantages
|
|
||||||
advantages:
|
advantages:
|
||||||
- <b>12 New Level</b> for a total of 26 levels
|
- <b>12 New Level</b> for a total of 26 levels
|
||||||
- <b>18 New Buildings</b> for a fully automated factory!
|
- <b>18 New Buildings</b> for a fully automated factory!
|
||||||
@ -199,11 +196,6 @@ dialogs:
|
|||||||
renameSavegame:
|
renameSavegame:
|
||||||
title: Rename Savegame
|
title: Rename Savegame
|
||||||
desc: You can rename your savegame here.
|
desc: You can rename your savegame here.
|
||||||
entityWarning:
|
|
||||||
title: Performance Warning
|
|
||||||
desc: You have placed a lot of buildings, this is just a friendly reminder that
|
|
||||||
the game can not handle an endless count of buildings - So try to
|
|
||||||
keep your factories compact!
|
|
||||||
ingame:
|
ingame:
|
||||||
keybindingsOverlay:
|
keybindingsOverlay:
|
||||||
moveMap: Move
|
moveMap: Move
|
||||||
@ -259,27 +251,6 @@ ingame:
|
|||||||
title: Upgrades
|
title: Upgrades
|
||||||
buttonUnlock: Upgrade
|
buttonUnlock: Upgrade
|
||||||
tier: Tier <x>
|
tier: Tier <x>
|
||||||
tierLabels:
|
|
||||||
- I
|
|
||||||
- II
|
|
||||||
- III
|
|
||||||
- IV
|
|
||||||
- V
|
|
||||||
- VI
|
|
||||||
- VII
|
|
||||||
- VIII
|
|
||||||
- IX
|
|
||||||
- X
|
|
||||||
- XI
|
|
||||||
- XII
|
|
||||||
- XIII
|
|
||||||
- XIV
|
|
||||||
- XV
|
|
||||||
- XVI
|
|
||||||
- XVII
|
|
||||||
- XVIII
|
|
||||||
- XIX
|
|
||||||
- XX
|
|
||||||
maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>)
|
maximumLevel: MAXIMUM LEVEL (Speed x<currentMult>)
|
||||||
statistics:
|
statistics:
|
||||||
title: Statistics
|
title: Statistics
|
||||||
@ -303,10 +274,6 @@ ingame:
|
|||||||
playtime: Playtime
|
playtime: Playtime
|
||||||
buildingsPlaced: Buildings
|
buildingsPlaced: Buildings
|
||||||
beltsPlaced: Belts
|
beltsPlaced: Belts
|
||||||
buttons:
|
|
||||||
continue: Continue
|
|
||||||
settings: Settings
|
|
||||||
menu: Return to menu
|
|
||||||
tutorialHints:
|
tutorialHints:
|
||||||
title: Need help?
|
title: Need help?
|
||||||
showHint: Show hint
|
showHint: Show hint
|
||||||
@ -676,7 +643,7 @@ storyRewards:
|
|||||||
title: Balancer
|
title: Balancer
|
||||||
desc: The multifunctional <strong>balancer</strong> has been unlocked - It can
|
desc: The multifunctional <strong>balancer</strong> has been unlocked - It can
|
||||||
be used to build bigger factories by <strong>splitting and merging
|
be used to build bigger factories by <strong>splitting and merging
|
||||||
items</strong> onto multiple belts!<br><br>
|
items</strong> onto multiple belts!
|
||||||
reward_merger:
|
reward_merger:
|
||||||
title: Compact Merger
|
title: Compact Merger
|
||||||
desc: You have unlocked a <strong>merger</strong> variant of the
|
desc: You have unlocked a <strong>merger</strong> variant of the
|
||||||
@ -893,6 +860,14 @@ settings:
|
|||||||
title: Enable Mouse Pan
|
title: Enable Mouse Pan
|
||||||
description: Allows to move the map by moving the cursor to the edges of the
|
description: Allows to move the map by moving the cursor to the edges of the
|
||||||
screen. The speed depends on the Movement Speed setting.
|
screen. The speed depends on the Movement Speed setting.
|
||||||
|
zoomToCursor:
|
||||||
|
title: Zoom towards Cursor
|
||||||
|
description: If activated the zoom will happen in the direction of your mouse
|
||||||
|
position, otherwise in the middle of the screen.
|
||||||
|
mapResourcesScale:
|
||||||
|
title: Map Resources Size
|
||||||
|
description: Controls the size of the shapes on the map overview (when zooming
|
||||||
|
out).
|
||||||
rangeSliderPercentage: <amount> %
|
rangeSliderPercentage: <amount> %
|
||||||
keybindings:
|
keybindings:
|
||||||
title: Keybindings
|
title: Keybindings
|
||||||
|
|||||||
@ -94,7 +94,6 @@ mainMenu:
|
|||||||
helpTranslate: Ajuda a traduir-lo!
|
helpTranslate: Ajuda a traduir-lo!
|
||||||
madeBy: Creat per <author-link>
|
madeBy: Creat per <author-link>
|
||||||
browserWarning: >-
|
browserWarning: >-
|
||||||
|
|
||||||
Disculpa, però el joc funcionarà lent al teu navegador! Aconsegueix el joc complet o descarrega't chrome per una millor experiència.
|
Disculpa, però el joc funcionarà lent al teu navegador! Aconsegueix el joc complet o descarrega't chrome per una millor experiència.
|
||||||
savegameLevel: Nivell <x>
|
savegameLevel: Nivell <x>
|
||||||
savegameLevelUnknown: Nivell desconegut
|
savegameLevelUnknown: Nivell desconegut
|
||||||
@ -205,11 +204,6 @@ dialogs:
|
|||||||
renameSavegame:
|
renameSavegame:
|
||||||
title: Canviar el nom.
|
title: Canviar el nom.
|
||||||
desc: Canviar el nom de la partida guardada.
|
desc: Canviar el nom de la partida guardada.
|
||||||
entityWarning:
|
|
||||||
title: Alerta de rendiment
|
|
||||||
desc: Has col·locat molts edificis, això és una alerta amistosa de que el joc no
|
|
||||||
pot suportar edificis infinits, així que intenta mantenir els teus
|
|
||||||
dissenys minimament compactes!
|
|
||||||
ingame:
|
ingame:
|
||||||
keybindingsOverlay:
|
keybindingsOverlay:
|
||||||
moveMap: Moure
|
moveMap: Moure
|
||||||
@ -265,27 +259,6 @@ ingame:
|
|||||||
title: Millores
|
title: Millores
|
||||||
buttonUnlock: Millorar
|
buttonUnlock: Millorar
|
||||||
tier: Nivell <x>
|
tier: Nivell <x>
|
||||||
tierLabels:
|
|
||||||
- I
|
|
||||||
- II
|
|
||||||
- III
|
|
||||||
- IV
|
|
||||||
- V
|
|
||||||
- VI
|
|
||||||
- VII
|
|
||||||
- VIII
|
|
||||||
- IX
|
|
||||||
- X
|
|
||||||
- XI
|
|
||||||
- XII
|
|
||||||
- XIII
|
|
||||||
- XIV
|
|
||||||
- XV
|
|
||||||
- XVI
|
|
||||||
- XVII
|
|
||||||
- XVIII
|
|
||||||
- XIX
|
|
||||||
- XX
|
|
||||||
maximumLevel: NIVELL MÀXIM (Velocitat x<currentMult>)
|
maximumLevel: NIVELL MÀXIM (Velocitat x<currentMult>)
|
||||||
statistics:
|
statistics:
|
||||||
title: Estadístiques
|
title: Estadístiques
|
||||||
@ -310,10 +283,6 @@ ingame:
|
|||||||
playtime: Temps de joc
|
playtime: Temps de joc
|
||||||
buildingsPlaced: Edificis
|
buildingsPlaced: Edificis
|
||||||
beltsPlaced: Cintes transportadores
|
beltsPlaced: Cintes transportadores
|
||||||
buttons:
|
|
||||||
continue: Continuar
|
|
||||||
settings: Configuració
|
|
||||||
menu: Tornar al menú
|
|
||||||
tutorialHints:
|
tutorialHints:
|
||||||
title: Necessites ajuda?
|
title: Necessites ajuda?
|
||||||
showHint: Mostrar pista
|
showHint: Mostrar pista
|
||||||
@ -692,9 +661,9 @@ storyRewards:
|
|||||||
completa - fora del web!
|
completa - fora del web!
|
||||||
reward_balancer:
|
reward_balancer:
|
||||||
title: Equilibrador
|
title: Equilibrador
|
||||||
desc: Has desbloquejat el multifuncional <strong>equilibrador</strong>! Pot
|
desc: The multifunctional <strong>balancer</strong> has been unlocked - It can
|
||||||
ésser emprat per construir fàbriques més grans <strong>dividint i
|
be used to build bigger factories by <strong>splitting and merging
|
||||||
fusionant ítems</strong> a múltiples cintes!<br><br>
|
items</strong> onto multiple belts!
|
||||||
reward_merger:
|
reward_merger:
|
||||||
title: Fusionador Compacte
|
title: Fusionador Compacte
|
||||||
desc: Has desbloquejat una variant <strong>fusionadora</strong> de
|
desc: Has desbloquejat una variant <strong>fusionadora</strong> de
|
||||||
@ -918,6 +887,14 @@ settings:
|
|||||||
description: Permet moure el mapa quan mous el cursor a les vores de la
|
description: Permet moure el mapa quan mous el cursor a les vores de la
|
||||||
pantalla. La velocitat de moviment depèn de la configuració de
|
pantalla. La velocitat de moviment depèn de la configuració de
|
||||||
Velocitat de Moviment.
|
Velocitat de Moviment.
|
||||||
|
zoomToCursor:
|
||||||
|
title: Zoom towards Cursor
|
||||||
|
description: If activated the zoom will happen in the direction of your mouse
|
||||||
|
position, otherwise in the middle of the screen.
|
||||||
|
mapResourcesScale:
|
||||||
|
title: Map Resources Size
|
||||||
|
description: Controls the size of the shapes on the map overview (when zooming
|
||||||
|
out).
|
||||||
rangeSliderPercentage: <amount> %
|
rangeSliderPercentage: <amount> %
|
||||||
keybindings:
|
keybindings:
|
||||||
title: Combinacions de tecles
|
title: Combinacions de tecles
|
||||||
|
|||||||