Updated to include latest changes
BIN
res/ui/interactive_tutorial.cn.noinline/1_1_extractor.gif
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/1_2_conveyor.gif
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/1_3_expand.gif
Normal file
|
After Width: | Height: | Size: 993 KiB |
|
After Width: | Height: | Size: 809 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/21_2_switch_to_wires.gif
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
res/ui/interactive_tutorial.cn.noinline/21_3_place_button.gif
Normal file
|
After Width: | Height: | Size: 531 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/21_4_press_button.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
res/ui/interactive_tutorial.cn.noinline/2_1_place_cutter.gif
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/2_2_place_trash.gif
Normal file
|
After Width: | Height: | Size: 575 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/2_3_more_cutters.gif
Normal file
|
After Width: | Height: | Size: 776 KiB |
BIN
res/ui/interactive_tutorial.cn.noinline/3_1_rectangles.gif
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
@ -137,16 +137,20 @@
|
|||||||
|
|
||||||
button.continue {
|
button.continue {
|
||||||
background: #555;
|
background: #555;
|
||||||
@include S(margin-right, 10px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.menu {
|
button.menu {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.nextPuzzle {
|
||||||
background-color: $colorGreenBright;
|
background-color: $colorGreenBright;
|
||||||
}
|
}
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
@include S(min-width, 100px);
|
@include S(min-width, 100px);
|
||||||
@include S(padding, 10px, 20px);
|
@include S(padding, 8px, 16px);
|
||||||
|
@include S(margin, 0, 6px);
|
||||||
@include IncreasedClickArea(0px);
|
@include IncreasedClickArea(0px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/css/ingame_hud/puzzle_next.scss
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ingame_HUD_PuzzleNextPuzzle {
|
||||||
|
position: absolute;
|
||||||
|
@include S(top, 17px);
|
||||||
|
@include S(right, 10px);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
backdrop-filter: blur(D(1px));
|
||||||
|
padding: D(3px);
|
||||||
|
|
||||||
|
> .button {
|
||||||
|
@include ButtonText;
|
||||||
|
@include IncreasedClickArea(0px);
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
color: #333438;
|
||||||
|
transition: all 0.12s ease-in-out;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition-property: opacity, transform;
|
||||||
|
@include PlainText;
|
||||||
|
@include S(padding-right, 25px);
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
@include DarkThemeInvert;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pressed {
|
||||||
|
transform: scale(0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& {
|
||||||
|
/* @load-async */
|
||||||
|
background: uiResource("icons/state_next_button.png") right center / D(15px) no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -65,7 +65,7 @@
|
|||||||
@import "ingame_hud/puzzle_play_settings";
|
@import "ingame_hud/puzzle_play_settings";
|
||||||
@import "ingame_hud/puzzle_play_metadata";
|
@import "ingame_hud/puzzle_play_metadata";
|
||||||
@import "ingame_hud/puzzle_complete_notification";
|
@import "ingame_hud/puzzle_complete_notification";
|
||||||
@import "ingame_hud/puzzle_import_export";
|
@import "ingame_hud/puzzle_next";
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
$elements:
|
$elements:
|
||||||
@ -84,8 +84,8 @@ ingame_HUD_PinnedShapes,
|
|||||||
ingame_HUD_GameMenu,
|
ingame_HUD_GameMenu,
|
||||||
ingame_HUD_KeybindingOverlay,
|
ingame_HUD_KeybindingOverlay,
|
||||||
ingame_HUD_PuzzleBackToMenu,
|
ingame_HUD_PuzzleBackToMenu,
|
||||||
|
ingame_HUD_PuzzleNextPuzzle,
|
||||||
ingame_HUD_PuzzleEditorReview,
|
ingame_HUD_PuzzleEditorReview,
|
||||||
ingame_HUD_PuzzleImportExport,
|
|
||||||
ingame_HUD_PuzzleEditorControls,
|
ingame_HUD_PuzzleEditorControls,
|
||||||
ingame_HUD_PuzzleEditorTitle,
|
ingame_HUD_PuzzleEditorTitle,
|
||||||
ingame_HUD_PuzzleEditorSettings,
|
ingame_HUD_PuzzleEditorSettings,
|
||||||
@ -135,11 +135,9 @@ body.uiHidden {
|
|||||||
#ingame_HUD_PlacementHints,
|
#ingame_HUD_PlacementHints,
|
||||||
#ingame_HUD_GameMenu,
|
#ingame_HUD_GameMenu,
|
||||||
#ingame_HUD_PinnedShapes,
|
#ingame_HUD_PinnedShapes,
|
||||||
#ingame_HUD_PuzzleEditorSettings,
|
#ingame_HUD_PuzzleBackToMenu,
|
||||||
#ingame_HUD_PuzzlePlaySettings,
|
#ingame_HUD_PuzzleNextPuzzle,
|
||||||
#ingame_HUD_PuzzleEditorControls,
|
#ingame_HUD_PuzzleEditorReview,
|
||||||
#ingame_HUD_PuzzleImportExport,
|
|
||||||
#ingame_HUD_PuzzlePlayMetadata,
|
|
||||||
#ingame_HUD_Notifications,
|
#ingame_HUD_Notifications,
|
||||||
#ingame_HUD_TutorialHints,
|
#ingame_HUD_TutorialHints,
|
||||||
#ingame_HUD_Waypoints,
|
#ingame_HUD_Waypoints,
|
||||||
|
|||||||
@ -571,7 +571,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@include S(grid-gap, 4px);
|
@include S(grid-gap, 4px);
|
||||||
|
|
||||||
&.china {
|
&.noLinks {
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,6 +580,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
grid-template-columns: 1fr auto 1fr;
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
> .disclaimer {
|
> .disclaimer {
|
||||||
grid-column: 2 / 3;
|
grid-column: 2 / 3;
|
||||||
|
|||||||
@ -19,8 +19,73 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> .container {
|
> .container {
|
||||||
|
.searchForm {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
color: #333;
|
||||||
|
background: $accentColorBright;
|
||||||
|
@include S(padding, 5px);
|
||||||
|
@include S(border-radius, $globalBorderRadius);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
input.search {
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
flex-grow: 1;
|
||||||
|
@include S(padding, 5px, 10px);
|
||||||
|
@include S(min-width, 50px);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
color: #333;
|
||||||
|
border: 0;
|
||||||
|
@include S(padding, 5px);
|
||||||
|
@include S(border-radius, $globalBorderRadius);
|
||||||
|
@include S(padding, 7px, 10px);
|
||||||
|
@include S(margin-left, 5px);
|
||||||
|
@include PlainText;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterCompleted {
|
||||||
|
@include S(margin-left, 20px);
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
@include PlainText;
|
||||||
|
@include S(margin-right, 10px);
|
||||||
|
|
||||||
|
input {
|
||||||
|
@include S(width, 15px);
|
||||||
|
@include S(height, 15px);
|
||||||
|
@include S(margin-right, 5px);
|
||||||
|
@include S(border-radius, $globalBorderRadius);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
|
@include S(padding, 7px, 10px, 5px);
|
||||||
|
@include S(margin-left, 20px);
|
||||||
|
@include S(margin-top, 4px);
|
||||||
|
@include S(margin-bottom, 4px);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .mainContent {
|
> .mainContent {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
> .categoryChooser {
|
> .categoryChooser {
|
||||||
> .categories {
|
> .categories {
|
||||||
@ -83,8 +148,8 @@
|
|||||||
@include S(grid-gap, 7px);
|
@include S(grid-gap, 7px);
|
||||||
@include S(margin-top, 10px);
|
@include S(margin-top, 10px);
|
||||||
@include S(padding-right, 4px);
|
@include S(padding-right, 4px);
|
||||||
@include S(height, 320px);
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
flex-grow: 1;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|||||||
@ -50,7 +50,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button.categoryButton,
|
button.categoryButton,
|
||||||
button.about {
|
button.about,
|
||||||
|
button.privacy {
|
||||||
background-color: $colorCategoryButton;
|
background-color: $colorCategoryButton;
|
||||||
color: #777a7f;
|
color: #777a7f;
|
||||||
|
|
||||||
@ -68,6 +69,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.privacy {
|
||||||
|
@include S(margin-top, 4px);
|
||||||
|
}
|
||||||
|
|
||||||
.versionbar {
|
.versionbar {
|
||||||
@include S(margin-top, 10px);
|
@include S(margin-top, 10px);
|
||||||
|
|
||||||
@ -180,7 +185,8 @@
|
|||||||
.container .content {
|
.container .content {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
button.categoryButton,
|
button.categoryButton,
|
||||||
button.about {
|
button.about,
|
||||||
|
button.privacy {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
background-color: darken($darkModeControlsBackground, 5);
|
background-color: darken($darkModeControlsBackground, 5);
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
export const CHANGELOG = [
|
export const CHANGELOG = [
|
||||||
// Not finished yet
|
|
||||||
{
|
{
|
||||||
version: "1.4.3",
|
version: "1.4.3",
|
||||||
date: "preview",
|
date: "preview",
|
||||||
entries: [
|
entries: [
|
||||||
|
"You can now hold 'ALT' while hovering a building to see its output! (Thanks to Sense101)",
|
||||||
|
"The map overview should now be much more performant! As a consequence, you can now zoom out farther! (Thanks to PFedak)",
|
||||||
|
"Puzzle DLC: There is now a 'next puzzle' button!",
|
||||||
|
"Puzzle DLC: There is now a search function!",
|
||||||
"Edit signal dialog now has the previous signal filled (Thanks to EmeraldBlock)",
|
"Edit signal dialog now has the previous signal filled (Thanks to EmeraldBlock)",
|
||||||
"Further performance improvements (Thanks to PFedak)",
|
"Further performance improvements (Thanks to PFedak)",
|
||||||
"Improved puzzle validation (Thanks to Sense101)",
|
"Improved puzzle validation (Thanks to Sense101)",
|
||||||
"Input fields in dialogs should now automatically focus",
|
"Input fields in dialogs should now automatically focus",
|
||||||
|
"Fix selected building being deselected at level up (Thanks to EmeraldBlock)",
|
||||||
"Updated translations",
|
"Updated translations",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -167,4 +167,25 @@ export class BufferMaintainer {
|
|||||||
});
|
});
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} param0
|
||||||
|
* @param {string} param0.key
|
||||||
|
* @param {string} param0.subKey
|
||||||
|
* @returns {HTMLCanvasElement?}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getForKeyOrNullNoUpdate({ key, subKey }) {
|
||||||
|
let parent = this.cache.get(key);
|
||||||
|
if (!parent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now search for sub key
|
||||||
|
const cacheHit = parent.get(subKey);
|
||||||
|
if (cacheHit) {
|
||||||
|
return cacheHit.canvas;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,8 @@ export const THIRDPARTY_URLS = {
|
|||||||
reddit: "https://www.reddit.com/r/shapezio",
|
reddit: "https://www.reddit.com/r/shapezio",
|
||||||
shapeViewer: "https://viewer.shapez.io",
|
shapeViewer: "https://viewer.shapez.io",
|
||||||
|
|
||||||
|
privacyPolicy: "https://tobspr.io/privacy.html",
|
||||||
|
|
||||||
standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/",
|
standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/",
|
||||||
stanaloneCampaignLink: "https://get.shapez.io",
|
stanaloneCampaignLink: "https://get.shapez.io",
|
||||||
puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC",
|
puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC",
|
||||||
@ -55,6 +57,7 @@ export const globalConfig = {
|
|||||||
|
|
||||||
// Map
|
// Map
|
||||||
mapChunkSize: 16,
|
mapChunkSize: 16,
|
||||||
|
chunkAggregateSize: 4,
|
||||||
mapChunkOverviewMinZoom: 0.9,
|
mapChunkOverviewMinZoom: 0.9,
|
||||||
mapChunkWorldSize: null, // COMPUTED
|
mapChunkWorldSize: null, // COMPUTED
|
||||||
|
|
||||||
|
|||||||
@ -123,6 +123,4 @@ function catchErrors(message, source, lineno, colno, error) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!G_IS_DEV) {
|
window.onerror = catchErrors;
|
||||||
window.onerror = catchErrors;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -89,6 +89,11 @@ export class RestrictionManager extends ReadWriteProxy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (queryParamOptions.embedProvider === "gamedistribution") {
|
||||||
|
// also full version on gamedistribution
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (G_IS_DEV) {
|
if (G_IS_DEV) {
|
||||||
return typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0;
|
return typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -734,6 +734,10 @@ const romanLiteralsCache = ["0"];
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getRomanNumber(number) {
|
export function getRomanNumber(number) {
|
||||||
|
if (G_WEGAME_VERSION) {
|
||||||
|
return String(number);
|
||||||
|
}
|
||||||
|
|
||||||
number = Math.max(0, Math.round(number));
|
number = Math.max(0, Math.round(number));
|
||||||
if (romanLiteralsCache[number]) {
|
if (romanLiteralsCache[number]) {
|
||||||
return romanLiteralsCache[number];
|
return romanLiteralsCache[number];
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { typeItemSingleton } from "../item_resolver";
|
|||||||
* pos: Vector,
|
* pos: Vector,
|
||||||
* direction: enumDirection,
|
* direction: enumDirection,
|
||||||
* item: BaseItem,
|
* item: BaseItem,
|
||||||
|
* lastItem: BaseItem,
|
||||||
* progress: number?,
|
* progress: number?,
|
||||||
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
|
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
|
||||||
* cachedBeltPath?: BeltPath,
|
* cachedBeltPath?: BeltPath,
|
||||||
@ -51,6 +52,7 @@ export class ItemEjectorComponent extends Component {
|
|||||||
clear() {
|
clear() {
|
||||||
for (const slot of this.slots) {
|
for (const slot of this.slots) {
|
||||||
slot.item = null;
|
slot.item = null;
|
||||||
|
slot.lastItem = null;
|
||||||
slot.progress = 0;
|
slot.progress = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,6 +69,7 @@ export class ItemEjectorComponent extends Component {
|
|||||||
pos: slot.pos,
|
pos: slot.pos,
|
||||||
direction: slot.direction,
|
direction: slot.direction,
|
||||||
item: null,
|
item: null,
|
||||||
|
lastItem: null,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
cachedDestSlot: null,
|
cachedDestSlot: null,
|
||||||
cachedTargetEntity: null,
|
cachedTargetEntity: null,
|
||||||
@ -131,6 +134,7 @@ export class ItemEjectorComponent extends Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.slots[slotIndex].item = item;
|
this.slots[slotIndex].item = item;
|
||||||
|
this.slots[slotIndex].lastItem = item;
|
||||||
this.slots[slotIndex].progress = 0;
|
this.slots[slotIndex].progress = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ export class GameMode extends BasicSerializableObject {
|
|||||||
|
|
||||||
/** @returns {number} */
|
/** @returns {number} */
|
||||||
getMinimumZoom() {
|
getMinimumZoom() {
|
||||||
return 0.1;
|
return 0.06;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {number} */
|
/** @returns {number} */
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { HUDEntityDebugger } from "./parts/entity_debugger";
|
|||||||
import { HUDModalDialogs } from "./parts/modal_dialogs";
|
import { HUDModalDialogs } from "./parts/modal_dialogs";
|
||||||
import { enumNotificationType } from "./parts/notifications";
|
import { enumNotificationType } from "./parts/notifications";
|
||||||
import { HUDSettingsMenu } from "./parts/settings_menu";
|
import { HUDSettingsMenu } from "./parts/settings_menu";
|
||||||
|
import { HUDShapeTooltip } from "./parts/shape_tooltip";
|
||||||
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
|
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
|
||||||
import { TrailerMaker } from "./trailer_maker";
|
import { TrailerMaker } from "./trailer_maker";
|
||||||
|
|
||||||
@ -49,6 +50,8 @@ export class GameHUD {
|
|||||||
blueprintPlacer: new HUDBlueprintPlacer(this.root),
|
blueprintPlacer: new HUDBlueprintPlacer(this.root),
|
||||||
buildingPlacer: new HUDBuildingPlacer(this.root),
|
buildingPlacer: new HUDBuildingPlacer(this.root),
|
||||||
|
|
||||||
|
shapeTooltip: new HUDShapeTooltip(this.root),
|
||||||
|
|
||||||
// Must always exist
|
// Must always exist
|
||||||
settingsMenu: new HUDSettingsMenu(this.root),
|
settingsMenu: new HUDSettingsMenu(this.root),
|
||||||
debugInfo: new HUDDebugInfo(this.root),
|
debugInfo: new HUDDebugInfo(this.root),
|
||||||
@ -189,6 +192,7 @@ export class GameHUD {
|
|||||||
"colorBlindHelper",
|
"colorBlindHelper",
|
||||||
"changesDebugger",
|
"changesDebugger",
|
||||||
"minerHighlight",
|
"minerHighlight",
|
||||||
|
"shapeTooltip",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i < partsOrder.length; ++i) {
|
for (let i = 0; i < partsOrder.length; ++i) {
|
||||||
|
|||||||
25
src/js/game/hud/parts/HUDPuzzleNextPuzzle.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { makeDiv } from "../../../core/utils";
|
||||||
|
import { T } from "../../../translations";
|
||||||
|
import { PuzzlePlayGameMode } from "../../modes/puzzle_play";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
|
export class HUDPuzzleNextPuzzle extends BaseHUDPart {
|
||||||
|
createElements(parent) {
|
||||||
|
this.element = makeDiv(parent, "ingame_HUD_PuzzleNextPuzzle");
|
||||||
|
this.button = document.createElement("button");
|
||||||
|
this.button.classList.add("button");
|
||||||
|
this.button.innerText = T.ingame.puzzleCompletion.nextPuzzle;
|
||||||
|
this.element.appendChild(this.button);
|
||||||
|
|
||||||
|
this.trackClicks(this.button, this.nextPuzzle);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {}
|
||||||
|
|
||||||
|
nextPuzzle() {
|
||||||
|
const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
this.root.gameState.moveToState("PuzzleMenuState", {
|
||||||
|
continueQueue: gameMode.nextPuzzles,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -128,7 +128,6 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart {
|
|||||||
this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this);
|
this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this);
|
||||||
this.root.hud.signals.pasteBlueprintRequested.add(this.abortPlacement, this);
|
this.root.hud.signals.pasteBlueprintRequested.add(this.abortPlacement, this);
|
||||||
this.root.signals.storyGoalCompleted.add(() => this.signals.variantChanged.dispatch());
|
this.root.signals.storyGoalCompleted.add(() => this.signals.variantChanged.dispatch());
|
||||||
this.root.signals.storyGoalCompleted.add(() => this.currentMetaBuilding.set(null));
|
|
||||||
this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
|
this.root.signals.upgradePurchased.add(() => this.signals.variantChanged.dispatch());
|
||||||
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
this.root.signals.editModeChanged.add(this.onEditModeChanged, this);
|
||||||
this.root.signals.testModeChanged.add(this.abortPlacement, this);
|
this.root.signals.testModeChanged.add(this.abortPlacement, this);
|
||||||
|
|||||||
@ -158,8 +158,13 @@ export class HUDInteractiveTutorial extends BaseHUDPart {
|
|||||||
|
|
||||||
onHintChanged(hintId) {
|
onHintChanged(hintId) {
|
||||||
this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId];
|
this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId];
|
||||||
|
|
||||||
|
const folder = G_WEGAME_VERSION
|
||||||
|
? "interactive_tutorial.cn.noinline"
|
||||||
|
: "interactive_tutorial.noinline";
|
||||||
|
|
||||||
this.elementGif.style.backgroundImage =
|
this.elementGif.style.backgroundImage =
|
||||||
"url('" + cachebust("res/ui/interactive_tutorial.noinline/" + hintId + ".gif") + "')";
|
"url('" + cachebust("res/ui/" + folder + "/" + hintId + ".gif") + "')";
|
||||||
this.element.classList.toggle("animEven");
|
this.element.classList.toggle("animEven");
|
||||||
this.element.classList.toggle("animOdd");
|
this.element.classList.toggle("animOdd");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,13 +6,8 @@ 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 { enumColors } from "../../colors";
|
|
||||||
import { ColorItem } from "../../items/color_item";
|
|
||||||
import { finalGameShape, rocketShape } from "../../modes/regular";
|
|
||||||
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 { ShapeItem } from "../../items/shape_item";
|
|
||||||
import { ShapeDefinition } from "../../shape_definition";
|
|
||||||
|
|
||||||
export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -68,10 +63,21 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
|||||||
this.menuBtn.classList.add("menu", "styledButton");
|
this.menuBtn.classList.add("menu", "styledButton");
|
||||||
this.menuBtn.innerText = T.ingame.puzzleCompletion.menuBtn;
|
this.menuBtn.innerText = T.ingame.puzzleCompletion.menuBtn;
|
||||||
buttonBar.appendChild(this.menuBtn);
|
buttonBar.appendChild(this.menuBtn);
|
||||||
|
|
||||||
this.trackClicks(this.menuBtn, () => {
|
this.trackClicks(this.menuBtn, () => {
|
||||||
this.close(true);
|
this.close(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
if (gameMode.nextPuzzles.length > 0) {
|
||||||
|
this.nextPuzzleBtn = document.createElement("button");
|
||||||
|
this.nextPuzzleBtn.classList.add("nextPuzzle", "styledButton");
|
||||||
|
this.nextPuzzleBtn.innerText = T.ingame.puzzleCompletion.nextPuzzle;
|
||||||
|
buttonBar.appendChild(this.nextPuzzleBtn);
|
||||||
|
|
||||||
|
this.trackClicks(this.nextPuzzleBtn, () => {
|
||||||
|
this.nextPuzzle();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState() {
|
updateState() {
|
||||||
@ -93,6 +99,15 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
|||||||
return this.visible;
|
return this.visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextPuzzle() {
|
||||||
|
const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
|
||||||
|
gameMode.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion)).then(() => {
|
||||||
|
this.root.gameState.moveToState("PuzzleMenuState", {
|
||||||
|
continueQueue: gameMode.nextPuzzles,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
close(toMenu) {
|
close(toMenu) {
|
||||||
/** @type {PuzzlePlayGameMode} */ (this.root.gameMode)
|
/** @type {PuzzlePlayGameMode} */ (this.root.gameMode)
|
||||||
.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion))
|
.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion))
|
||||||
|
|||||||
@ -192,8 +192,9 @@ export class HUDPuzzleEditorSettings extends BaseHUDPart {
|
|||||||
assertAlways(false, "Failed to re-place building in trim");
|
assertAlways(false, "Failed to re-place building in trim");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (building.components.ConstantSignal) {
|
for (const key in building.components) {
|
||||||
result.components.ConstantSignal.signal = building.components.ConstantSignal.signal;
|
/** @type {import("../../../core/global_registries").Component} */ (building
|
||||||
|
.components[key]).copyAdditionalStateTo(result.components[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
112
src/js/game/hud/parts/shape_tooltip.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { DrawParameters } from "../../../core/draw_parameters";
|
||||||
|
import { enumDirectionToVector, Vector } from "../../../core/vector";
|
||||||
|
import { Entity } from "../../entity";
|
||||||
|
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||||
|
import { THEME } from "../../theme";
|
||||||
|
import { BaseHUDPart } from "../base_hud_part";
|
||||||
|
|
||||||
|
export class HUDShapeTooltip extends BaseHUDPart {
|
||||||
|
createElements(parent) {}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
/** @type {Vector} */
|
||||||
|
this.currentTile = new Vector(0, 0);
|
||||||
|
|
||||||
|
/** @type {Entity} */
|
||||||
|
this.currentEntity = null;
|
||||||
|
|
||||||
|
this.isPlacingBuilding = false;
|
||||||
|
|
||||||
|
this.root.signals.entityQueuedForDestroy.add(() => {
|
||||||
|
this.currentEntity = null;
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.root.hud.signals.selectedPlacementBuildingChanged.add(metaBuilding => {
|
||||||
|
this.isPlacingBuilding = metaBuilding;
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive() {
|
||||||
|
const hudParts = this.root.hud.parts;
|
||||||
|
|
||||||
|
// return false if any other placer is active
|
||||||
|
return (
|
||||||
|
this.root.keyMapper.getBinding(KEYMAPPINGS.ingame.showShapeTooltip).pressed &&
|
||||||
|
!this.isPlacingBuilding &&
|
||||||
|
!hudParts.massSelector.currentSelectionStartWorld &&
|
||||||
|
hudParts.massSelector.selectedUids.size < 1 &&
|
||||||
|
!hudParts.blueprintPlacer.currentBlueprint.get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {DrawParameters} parameters
|
||||||
|
*/
|
||||||
|
draw(parameters) {
|
||||||
|
if (this.isActive()) {
|
||||||
|
const mousePos = this.root.app.mousePosition;
|
||||||
|
|
||||||
|
if (mousePos) {
|
||||||
|
const tile = this.root.camera.screenToWorld(mousePos.copy()).toTileSpace();
|
||||||
|
if (!tile.equals(this.currentTile)) {
|
||||||
|
this.currentTile = tile;
|
||||||
|
|
||||||
|
const entity = this.root.map.getLayerContentXY(tile.x, tile.y, this.root.currentLayer);
|
||||||
|
|
||||||
|
if (entity && entity.components.ItemProcessor && entity.components.ItemEjector) {
|
||||||
|
this.currentEntity = entity;
|
||||||
|
} else {
|
||||||
|
this.currentEntity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.currentEntity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ejectorComp = this.currentEntity.components.ItemEjector;
|
||||||
|
const staticComp = this.currentEntity.components.StaticMapEntity;
|
||||||
|
|
||||||
|
const context = parameters.context;
|
||||||
|
|
||||||
|
for (let i = 0; i < ejectorComp.slots.length; ++i) {
|
||||||
|
const slot = ejectorComp.slots[i];
|
||||||
|
|
||||||
|
if (!slot.lastItem || slot.lastItem._type != "shape") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawPos = staticComp
|
||||||
|
.localTileToWorld(slot.pos.add(enumDirectionToVector[slot.direction].multiplyScalar(1)))
|
||||||
|
.toWorldSpaceCenterOfTile();
|
||||||
|
|
||||||
|
const slotCenterPos = staticComp
|
||||||
|
.localTileToWorld(slot.pos.add(enumDirectionToVector[slot.direction].multiplyScalar(0.2)))
|
||||||
|
.toWorldSpaceCenterOfTile();
|
||||||
|
|
||||||
|
context.fillStyle = THEME.shapeTooltip.outline;
|
||||||
|
context.strokeStyle = THEME.shapeTooltip.outline;
|
||||||
|
|
||||||
|
context.lineWidth = 1.5;
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(slotCenterPos.x, slotCenterPos.y);
|
||||||
|
context.lineTo(drawPos.x, drawPos.y);
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
context.beginCircle(slotCenterPos.x, slotCenterPos.y, 3.5);
|
||||||
|
context.fill();
|
||||||
|
|
||||||
|
context.fillStyle = THEME.shapeTooltip.background;
|
||||||
|
context.strokeStyle = THEME.shapeTooltip.outline;
|
||||||
|
|
||||||
|
context.lineWidth = 1.2;
|
||||||
|
context.beginCircle(drawPos.x, drawPos.y, 11 + 1.2 / 2);
|
||||||
|
context.fill();
|
||||||
|
context.stroke();
|
||||||
|
slot.lastItem.drawItemCenteredClipped(drawPos.x, drawPos.y, parameters, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,7 +45,7 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
*/
|
*/
|
||||||
createElements(parent) {
|
createElements(parent) {
|
||||||
// Create the helper box on the lower right when zooming out
|
// Create the helper box on the lower right when zooming out
|
||||||
if (this.root.app.settings.getAllSettings().offerHints) {
|
if (this.root.app.settings.getAllSettings().offerHints && !G_WEGAME_VERSION) {
|
||||||
this.hintElement = makeDiv(
|
this.hintElement = makeDiv(
|
||||||
parent,
|
parent,
|
||||||
"ingame_HUD_Waypoints_Hint",
|
"ingame_HUD_Waypoints_Hint",
|
||||||
@ -121,10 +121,12 @@ export class HUDWaypoints extends BaseHUDPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Catch mouse and key events
|
// Catch mouse and key events
|
||||||
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
if (!G_WEGAME_VERSION) {
|
||||||
this.root.keyMapper
|
this.root.camera.downPreHandler.add(this.onMouseDown, this);
|
||||||
.getBinding(KEYMAPPINGS.navigation.createMarker)
|
this.root.keyMapper
|
||||||
.add(() => this.requestSaveMarker({}));
|
.getBinding(KEYMAPPINGS.navigation.createMarker)
|
||||||
|
.add(() => this.requestSaveMarker({}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores at how much opacity the markers should be rendered on the map.
|
* Stores at how much opacity the markers should be rendered on the map.
|
||||||
|
|||||||
@ -32,6 +32,8 @@ export const KEYMAPPINGS = {
|
|||||||
toggleFPSInfo: { keyCode: 115 }, // F4
|
toggleFPSInfo: { keyCode: 115 }, // F4
|
||||||
|
|
||||||
switchLayers: { keyCode: key("E") },
|
switchLayers: { keyCode: key("E") },
|
||||||
|
|
||||||
|
showShapeTooltip: { keyCode: 18 }, // ALT
|
||||||
},
|
},
|
||||||
|
|
||||||
navigation: {
|
navigation: {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Vector } from "../core/vector";
|
|||||||
import { BasicSerializableObject, types } from "../savegame/serialization";
|
import { BasicSerializableObject, types } from "../savegame/serialization";
|
||||||
import { BaseItem } from "./base_item";
|
import { BaseItem } from "./base_item";
|
||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
|
import { MapChunkAggregate } from "./map_chunk_aggregate";
|
||||||
import { MapChunkView } from "./map_chunk_view";
|
import { MapChunkView } from "./map_chunk_view";
|
||||||
import { GameRoot } from "./root";
|
import { GameRoot } from "./root";
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ export class BaseMap extends BasicSerializableObject {
|
|||||||
* Mapping of 'X|Y' to chunk
|
* Mapping of 'X|Y' to chunk
|
||||||
* @type {Map<string, MapChunkView>} */
|
* @type {Map<string, MapChunkView>} */
|
||||||
this.chunksById = new Map();
|
this.chunksById = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of 'X|Y' to chunk aggregate
|
||||||
|
* @type {Map<string, MapChunkAggregate>} */
|
||||||
|
this.aggregatesById = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,6 +61,39 @@ export class BaseMap extends BasicSerializableObject {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the chunk aggregate containing a given chunk
|
||||||
|
* @param {number} chunkX
|
||||||
|
* @param {number} chunkY
|
||||||
|
*/
|
||||||
|
getAggregateForChunk(chunkX, chunkY, createIfNotExistent = false) {
|
||||||
|
const aggX = Math.floor(chunkX / globalConfig.chunkAggregateSize);
|
||||||
|
const aggY = Math.floor(chunkY / globalConfig.chunkAggregateSize);
|
||||||
|
return this.getAggregate(aggX, aggY, createIfNotExistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given chunk aggregate by index
|
||||||
|
* @param {number} aggX
|
||||||
|
* @param {number} aggY
|
||||||
|
*/
|
||||||
|
getAggregate(aggX, aggY, createIfNotExistent = false) {
|
||||||
|
const aggIdentifier = aggX + "|" + aggY;
|
||||||
|
let storedAggregate;
|
||||||
|
|
||||||
|
if ((storedAggregate = this.aggregatesById.get(aggIdentifier))) {
|
||||||
|
return storedAggregate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createIfNotExistent) {
|
||||||
|
const instance = new MapChunkAggregate(this.root, aggX, aggY);
|
||||||
|
this.aggregatesById.set(aggIdentifier, instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or creates a new chunk if not existent for the given tile
|
* Gets or creates a new chunk if not existent for the given tile
|
||||||
* @param {number} tileX
|
* @param {number} tileX
|
||||||
|
|||||||
154
src/js/game/map_chunk_aggregate.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { globalConfig } from "../core/config";
|
||||||
|
import { DrawParameters } from "../core/draw_parameters";
|
||||||
|
import { MapChunk } from "./map_chunk";
|
||||||
|
import { GameRoot } from "./root";
|
||||||
|
import { drawSpriteClipped } from "../core/draw_utils";
|
||||||
|
|
||||||
|
export const CHUNK_OVERLAY_RES = 3;
|
||||||
|
|
||||||
|
export class MapChunkAggregate {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {GameRoot} root
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
constructor(root, x, y) {
|
||||||
|
this.root = root;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whenever something changes, we increase this number - so we know we need to redraw
|
||||||
|
*/
|
||||||
|
this.renderIteration = 0;
|
||||||
|
this.dirty = false;
|
||||||
|
/** @type {Array<boolean>} */
|
||||||
|
this.dirtyList = new Array(globalConfig.chunkAggregateSize ** 2).fill(true);
|
||||||
|
this.markDirty(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks this chunk as dirty, rerendering all caches
|
||||||
|
* @param {number} chunkX
|
||||||
|
* @param {number} chunkY
|
||||||
|
*/
|
||||||
|
markDirty(chunkX, chunkY) {
|
||||||
|
const relX = chunkX % globalConfig.chunkAggregateSize;
|
||||||
|
const relY = chunkY % globalConfig.chunkAggregateSize;
|
||||||
|
this.dirtyList[relY * globalConfig.chunkAggregateSize + relX] = true;
|
||||||
|
if (this.dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dirty = true;
|
||||||
|
++this.renderIteration;
|
||||||
|
this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLCanvasElement} canvas
|
||||||
|
* @param {CanvasRenderingContext2D} context
|
||||||
|
* @param {number} w
|
||||||
|
* @param {number} h
|
||||||
|
* @param {number} dpi
|
||||||
|
*/
|
||||||
|
generateOverlayBuffer(canvas, context, w, h, dpi) {
|
||||||
|
const prevKey = this.x + "/" + this.y + "@" + (this.renderIteration - 1);
|
||||||
|
const prevBuffer = this.root.buffers.getForKeyOrNullNoUpdate({
|
||||||
|
key: "agg@" + this.root.currentLayer,
|
||||||
|
subKey: prevKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
|
||||||
|
let onlyDirty = false;
|
||||||
|
if (prevBuffer) {
|
||||||
|
context.drawImage(prevBuffer, 0, 0);
|
||||||
|
onlyDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let x = 0; x < globalConfig.chunkAggregateSize; x++) {
|
||||||
|
for (let y = 0; y < globalConfig.chunkAggregateSize; y++) {
|
||||||
|
if (onlyDirty && !this.dirtyList[globalConfig.chunkAggregateSize * y + x]) continue;
|
||||||
|
this.root.map
|
||||||
|
.getChunk(
|
||||||
|
this.x * globalConfig.chunkAggregateSize + x,
|
||||||
|
this.y * globalConfig.chunkAggregateSize + y,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.generateOverlayBuffer(
|
||||||
|
context,
|
||||||
|
overlaySize,
|
||||||
|
overlaySize,
|
||||||
|
x * overlaySize,
|
||||||
|
y * overlaySize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dirty = false;
|
||||||
|
this.dirtyList.fill(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overlay
|
||||||
|
* @param {DrawParameters} parameters
|
||||||
|
*/
|
||||||
|
drawOverlay(parameters) {
|
||||||
|
const aggregateOverlaySize =
|
||||||
|
globalConfig.mapChunkSize * globalConfig.chunkAggregateSize * CHUNK_OVERLAY_RES;
|
||||||
|
const sprite = this.root.buffers.getForKey({
|
||||||
|
key: "agg@" + this.root.currentLayer,
|
||||||
|
subKey: this.renderKey,
|
||||||
|
w: aggregateOverlaySize,
|
||||||
|
h: aggregateOverlaySize,
|
||||||
|
dpi: 1,
|
||||||
|
redrawMethod: this.generateOverlayBuffer.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
const dims = globalConfig.mapChunkWorldSize * globalConfig.chunkAggregateSize;
|
||||||
|
const extrude = 0.05;
|
||||||
|
|
||||||
|
// Draw chunk "pixel" art
|
||||||
|
parameters.context.imageSmoothingEnabled = false;
|
||||||
|
drawSpriteClipped({
|
||||||
|
parameters,
|
||||||
|
sprite,
|
||||||
|
x: this.x * dims - extrude,
|
||||||
|
y: this.y * dims - extrude,
|
||||||
|
w: dims + 2 * extrude,
|
||||||
|
h: dims + 2 * extrude,
|
||||||
|
originalW: aggregateOverlaySize,
|
||||||
|
originalH: aggregateOverlaySize,
|
||||||
|
});
|
||||||
|
|
||||||
|
parameters.context.imageSmoothingEnabled = true;
|
||||||
|
const resourcesScale = this.root.app.settings.getAllSettings().mapResourcesScale;
|
||||||
|
|
||||||
|
// Draw patch items
|
||||||
|
if (
|
||||||
|
this.root.currentLayer === "regular" &&
|
||||||
|
resourcesScale > 0.05 &&
|
||||||
|
this.root.camera.zoomLevel > 0.1
|
||||||
|
) {
|
||||||
|
const diameter = (70 / Math.pow(parameters.zoomLevel, 0.35)) * (0.2 + 2 * resourcesScale);
|
||||||
|
|
||||||
|
for (let x = 0; x < globalConfig.chunkAggregateSize; x++) {
|
||||||
|
for (let y = 0; y < globalConfig.chunkAggregateSize; y++) {
|
||||||
|
this.root.map
|
||||||
|
.getChunk(
|
||||||
|
this.x * globalConfig.chunkAggregateSize + x,
|
||||||
|
this.y * globalConfig.chunkAggregateSize + y,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.drawOverlayPatches(
|
||||||
|
parameters,
|
||||||
|
this.x * dims + x * globalConfig.mapChunkWorldSize,
|
||||||
|
this.y * dims + y * globalConfig.mapChunkWorldSize,
|
||||||
|
diameter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ export class MapChunkView extends MapChunk {
|
|||||||
markDirty() {
|
markDirty() {
|
||||||
++this.renderIteration;
|
++this.renderIteration;
|
||||||
this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
|
this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration;
|
||||||
|
this.root.map.getAggregateForChunk(this.x, this.y, true).markDirty(this.x, this.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,73 +83,41 @@ export class MapChunkView extends MapChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overlay
|
|
||||||
* @param {DrawParameters} parameters
|
* @param {DrawParameters} parameters
|
||||||
|
* @param {number} xoffs
|
||||||
|
* @param {number} yoffs
|
||||||
|
* @param {number} diameter
|
||||||
*/
|
*/
|
||||||
drawOverlay(parameters) {
|
drawOverlayPatches(parameters, xoffs, yoffs, diameter) {
|
||||||
const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES;
|
for (let i = 0; i < this.patches.length; ++i) {
|
||||||
const sprite = this.root.buffers.getForKey({
|
const patch = this.patches[i];
|
||||||
key: "chunk@" + this.root.currentLayer,
|
if (patch.item.getItemType() === "shape") {
|
||||||
subKey: this.renderKey,
|
const destX = xoffs + patch.pos.x * globalConfig.tileSize;
|
||||||
w: overlaySize,
|
const destY = yoffs + patch.pos.y * globalConfig.tileSize;
|
||||||
h: overlaySize,
|
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
||||||
dpi: 1,
|
|
||||||
redrawMethod: this.generateOverlayBuffer.bind(this),
|
|
||||||
});
|
|
||||||
|
|
||||||
const dims = globalConfig.mapChunkWorldSize;
|
|
||||||
const extrude = 0.05;
|
|
||||||
|
|
||||||
// Draw chunk "pixel" art
|
|
||||||
parameters.context.imageSmoothingEnabled = false;
|
|
||||||
drawSpriteClipped({
|
|
||||||
parameters,
|
|
||||||
sprite,
|
|
||||||
x: this.x * dims - extrude,
|
|
||||||
y: this.y * dims - extrude,
|
|
||||||
w: dims + 2 * extrude,
|
|
||||||
h: dims + 2 * extrude,
|
|
||||||
originalW: overlaySize,
|
|
||||||
originalH: overlaySize,
|
|
||||||
});
|
|
||||||
|
|
||||||
parameters.context.imageSmoothingEnabled = true;
|
|
||||||
const resourcesScale = this.root.app.settings.getAllSettings().mapResourcesScale;
|
|
||||||
|
|
||||||
// Draw patch items
|
|
||||||
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) {
|
|
||||||
const patch = this.patches[i];
|
|
||||||
if (patch.item.getItemType() === "shape") {
|
|
||||||
const destX = this.x * dims + patch.pos.x * globalConfig.tileSize;
|
|
||||||
const destY = this.y * dims + patch.pos.y * globalConfig.tileSize;
|
|
||||||
patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {HTMLCanvasElement} canvas
|
|
||||||
* @param {CanvasRenderingContext2D} context
|
* @param {CanvasRenderingContext2D} context
|
||||||
* @param {number} w
|
* @param {number} w
|
||||||
* @param {number} h
|
* @param {number} h
|
||||||
* @param {number} dpi
|
* @param {number=} xoffs
|
||||||
|
* @param {number=} yoffs
|
||||||
*/
|
*/
|
||||||
generateOverlayBuffer(canvas, context, w, h, dpi) {
|
generateOverlayBuffer(context, w, h, xoffs, yoffs) {
|
||||||
context.fillStyle =
|
context.fillStyle =
|
||||||
this.containedEntities.length > 0
|
this.containedEntities.length > 0
|
||||||
? THEME.map.chunkOverview.filled
|
? THEME.map.chunkOverview.filled
|
||||||
: THEME.map.chunkOverview.empty;
|
: THEME.map.chunkOverview.empty;
|
||||||
context.fillRect(0, 0, w, h);
|
context.fillRect(xoffs, yoffs, w, h);
|
||||||
|
|
||||||
if (this.root.app.settings.getAllSettings().displayChunkBorders) {
|
if (this.root.app.settings.getAllSettings().displayChunkBorders) {
|
||||||
context.fillStyle = THEME.map.chunkBorders;
|
context.fillStyle = THEME.map.chunkBorders;
|
||||||
context.fillRect(0, 0, w, 1);
|
context.fillRect(xoffs, yoffs, w, 1);
|
||||||
context.fillRect(0, 1, 1, h);
|
context.fillRect(xoffs, yoffs + 1, 1, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||||
@ -174,8 +143,8 @@ export class MapChunkView extends MapChunk {
|
|||||||
if (lowerContent) {
|
if (lowerContent) {
|
||||||
context.fillStyle = lowerContent.getBackgroundColorAsResource();
|
context.fillStyle = lowerContent.getBackgroundColorAsResource();
|
||||||
context.fillRect(
|
context.fillRect(
|
||||||
x * CHUNK_OVERLAY_RES,
|
xoffs + x * CHUNK_OVERLAY_RES,
|
||||||
y * CHUNK_OVERLAY_RES,
|
yoffs + y * CHUNK_OVERLAY_RES,
|
||||||
CHUNK_OVERLAY_RES,
|
CHUNK_OVERLAY_RES,
|
||||||
CHUNK_OVERLAY_RES
|
CHUNK_OVERLAY_RES
|
||||||
);
|
);
|
||||||
@ -190,8 +159,8 @@ export class MapChunkView extends MapChunk {
|
|||||||
const isFilled = overlayMatrix[dx + dy * 3];
|
const isFilled = overlayMatrix[dx + dy * 3];
|
||||||
if (isFilled) {
|
if (isFilled) {
|
||||||
context.fillRect(
|
context.fillRect(
|
||||||
x * CHUNK_OVERLAY_RES + dx,
|
xoffs + x * CHUNK_OVERLAY_RES + dx,
|
||||||
y * CHUNK_OVERLAY_RES + dy,
|
yoffs + y * CHUNK_OVERLAY_RES + dy,
|
||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
@ -206,8 +175,8 @@ export class MapChunkView extends MapChunk {
|
|||||||
data.rotationVariant
|
data.rotationVariant
|
||||||
);
|
);
|
||||||
context.fillRect(
|
context.fillRect(
|
||||||
x * CHUNK_OVERLAY_RES,
|
xoffs + x * CHUNK_OVERLAY_RES,
|
||||||
y * CHUNK_OVERLAY_RES,
|
yoffs + y * CHUNK_OVERLAY_RES,
|
||||||
CHUNK_OVERLAY_RES,
|
CHUNK_OVERLAY_RES,
|
||||||
CHUNK_OVERLAY_RES
|
CHUNK_OVERLAY_RES
|
||||||
);
|
);
|
||||||
@ -220,8 +189,8 @@ export class MapChunkView extends MapChunk {
|
|||||||
if (lowerContent) {
|
if (lowerContent) {
|
||||||
context.fillStyle = lowerContent.getBackgroundColorAsResource();
|
context.fillStyle = lowerContent.getBackgroundColorAsResource();
|
||||||
context.fillRect(
|
context.fillRect(
|
||||||
x * CHUNK_OVERLAY_RES,
|
xoffs + x * CHUNK_OVERLAY_RES,
|
||||||
y * CHUNK_OVERLAY_RES,
|
yoffs + y * CHUNK_OVERLAY_RES,
|
||||||
CHUNK_OVERLAY_RES,
|
CHUNK_OVERLAY_RES,
|
||||||
CHUNK_OVERLAY_RES
|
CHUNK_OVERLAY_RES
|
||||||
);
|
);
|
||||||
@ -233,7 +202,7 @@ export class MapChunkView extends MapChunk {
|
|||||||
// Draw wires overlay
|
// Draw wires overlay
|
||||||
|
|
||||||
context.fillStyle = THEME.map.wires.overlayColor;
|
context.fillStyle = THEME.map.wires.overlayColor;
|
||||||
context.fillRect(0, 0, w, h);
|
context.fillRect(xoffs, yoffs, w, h);
|
||||||
|
|
||||||
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
for (let x = 0; x < globalConfig.mapChunkSize; ++x) {
|
||||||
const wiresArray = this.wireContents[x];
|
const wiresArray = this.wireContents[x];
|
||||||
@ -244,8 +213,8 @@ export class MapChunkView extends MapChunk {
|
|||||||
}
|
}
|
||||||
MapChunkView.drawSingleWiresOverviewTile({
|
MapChunkView.drawSingleWiresOverviewTile({
|
||||||
context,
|
context,
|
||||||
x: x * CHUNK_OVERLAY_RES,
|
x: xoffs + x * CHUNK_OVERLAY_RES,
|
||||||
y: y * CHUNK_OVERLAY_RES,
|
y: yoffs + y * CHUNK_OVERLAY_RES,
|
||||||
entity: content,
|
entity: content,
|
||||||
tileSizePixels: CHUNK_OVERLAY_RES,
|
tileSizePixels: CHUNK_OVERLAY_RES,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils";
|
|||||||
import { Entity } from "./entity";
|
import { Entity } from "./entity";
|
||||||
import { THEME } from "./theme";
|
import { THEME } from "./theme";
|
||||||
import { MapChunkView } from "./map_chunk_view";
|
import { MapChunkView } from "./map_chunk_view";
|
||||||
|
import { MapChunkAggregate } from "./map_chunk_aggregate";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the view of the map, it extends the map which is the raw model and allows
|
* This is the view of the map, it extends the map which is the raw model and allows
|
||||||
@ -164,6 +165,40 @@ export class MapView extends BaseMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a given method on all given chunks
|
||||||
|
* @param {DrawParameters} parameters
|
||||||
|
* @param {function} method
|
||||||
|
*/
|
||||||
|
drawVisibleAggregates(parameters, method) {
|
||||||
|
const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize);
|
||||||
|
const top = cullRange.top();
|
||||||
|
const right = cullRange.right();
|
||||||
|
const bottom = cullRange.bottom();
|
||||||
|
const left = cullRange.left();
|
||||||
|
|
||||||
|
const border = 0;
|
||||||
|
const minY = top - border;
|
||||||
|
const maxY = bottom + border;
|
||||||
|
const minX = left - border;
|
||||||
|
const maxX = right + border;
|
||||||
|
|
||||||
|
const aggregateTiles = globalConfig.chunkAggregateSize * globalConfig.mapChunkSize;
|
||||||
|
const aggStartX = Math.floor(minX / aggregateTiles);
|
||||||
|
const aggStartY = Math.floor(minY / aggregateTiles);
|
||||||
|
|
||||||
|
const aggEndX = Math.floor(maxX / aggregateTiles);
|
||||||
|
const aggEndY = Math.floor(maxY / aggregateTiles);
|
||||||
|
|
||||||
|
// Render y from top down for proper blending
|
||||||
|
for (let aggX = aggStartX; aggX <= aggEndX; ++aggX) {
|
||||||
|
for (let aggY = aggStartY; aggY <= aggEndY; ++aggY) {
|
||||||
|
const aggregate = this.root.map.getAggregate(aggX, aggY, true);
|
||||||
|
method.call(aggregate, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the wires foreground
|
* Draws the wires foreground
|
||||||
* @param {DrawParameters} parameters
|
* @param {DrawParameters} parameters
|
||||||
@ -177,7 +212,7 @@ export class MapView extends BaseMap {
|
|||||||
* @param {DrawParameters} parameters
|
* @param {DrawParameters} parameters
|
||||||
*/
|
*/
|
||||||
drawOverlay(parameters) {
|
drawOverlay(parameters) {
|
||||||
this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay);
|
this.drawVisibleAggregates(parameters, MapChunkAggregate.prototype.drawOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import { HUDPuzzlePlaySettings } from "../hud/parts/puzzle_play_settings";
|
|||||||
import { MetaBlockBuilding } from "../buildings/block";
|
import { MetaBlockBuilding } from "../buildings/block";
|
||||||
import { MetaBuilding } from "../meta_building";
|
import { MetaBuilding } from "../meta_building";
|
||||||
import { gMetaBuildingRegistry } from "../../core/global_registries";
|
import { gMetaBuildingRegistry } from "../../core/global_registries";
|
||||||
|
import { HUDPuzzleNextPuzzle } from "../hud/parts/HUDPuzzleNextPuzzle";
|
||||||
|
|
||||||
const logger = createLogger("puzzle-play");
|
const logger = createLogger("puzzle-play");
|
||||||
const copy = require("clipboard-copy");
|
const copy = require("clipboard-copy");
|
||||||
@ -43,8 +44,9 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
|
|||||||
* @param {GameRoot} root
|
* @param {GameRoot} root
|
||||||
* @param {object} payload
|
* @param {object} payload
|
||||||
* @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle
|
* @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle
|
||||||
|
* @param {Array<number> | undefined} payload.nextPuzzles
|
||||||
*/
|
*/
|
||||||
constructor(root, { puzzle }) {
|
constructor(root, { puzzle, nextPuzzles }) {
|
||||||
super(root);
|
super(root);
|
||||||
|
|
||||||
/** @type {Array<typeof MetaBuilding>} */
|
/** @type {Array<typeof MetaBuilding>} */
|
||||||
@ -95,6 +97,15 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
|
|||||||
root.signals.postLoadHook.add(this.loadPuzzle, this);
|
root.signals.postLoadHook.add(this.loadPuzzle, this);
|
||||||
|
|
||||||
this.puzzle = puzzle;
|
this.puzzle = puzzle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<number>}
|
||||||
|
*/
|
||||||
|
this.nextPuzzles = nextPuzzles || [];
|
||||||
|
|
||||||
|
if (this.nextPuzzles.length > 0) {
|
||||||
|
this.additionalHudParts.puzzleNext = HUDPuzzleNextPuzzle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPuzzle() {
|
loadPuzzle() {
|
||||||
|
|||||||
@ -575,7 +575,9 @@ export class RegularGameMode extends GameMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.root.app.settings.getAllSettings().offerHints) {
|
if (this.root.app.settings.getAllSettings().offerHints) {
|
||||||
this.additionalHudParts.tutorialHints = HUDPartTutorialHints;
|
if (!G_WEGAME_VERSION) {
|
||||||
|
this.additionalHudParts.tutorialHints = HUDPartTutorialHints;
|
||||||
|
}
|
||||||
this.additionalHudParts.interactiveTutorial = HUDInteractiveTutorial;
|
this.additionalHudParts.interactiveTutorial = HUDInteractiveTutorial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -334,14 +334,19 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutHalf(inputDefinition);
|
const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutHalf(inputDefinition);
|
||||||
|
|
||||||
|
const ejectorComp = payload.entity.components.ItemEjector;
|
||||||
for (let i = 0; i < cutDefinitions.length; ++i) {
|
for (let i = 0; i < cutDefinitions.length; ++i) {
|
||||||
const definition = cutDefinitions[i];
|
const definition = cutDefinitions[i];
|
||||||
if (!definition.isEntirelyEmpty()) {
|
|
||||||
payload.outItems.push({
|
if (definition.isEntirelyEmpty()) {
|
||||||
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
|
ejectorComp.slots[i].lastItem = null;
|
||||||
requiredSlot: i,
|
continue;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payload.outItems.push({
|
||||||
|
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
|
||||||
|
requiredSlot: i,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,14 +360,19 @@ export class ItemProcessorSystem extends GameSystemWithFilter {
|
|||||||
|
|
||||||
const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutQuad(inputDefinition);
|
const cutDefinitions = this.root.shapeDefinitionMgr.shapeActionCutQuad(inputDefinition);
|
||||||
|
|
||||||
|
const ejectorComp = payload.entity.components.ItemEjector;
|
||||||
for (let i = 0; i < cutDefinitions.length; ++i) {
|
for (let i = 0; i < cutDefinitions.length; ++i) {
|
||||||
const definition = cutDefinitions[i];
|
const definition = cutDefinitions[i];
|
||||||
if (!definition.isEntirelyEmpty()) {
|
|
||||||
payload.outItems.push({
|
if (definition.isEntirelyEmpty()) {
|
||||||
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
|
ejectorComp.slots[i].lastItem = null;
|
||||||
requiredSlot: i,
|
continue;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payload.outItems.push({
|
||||||
|
item: this.root.shapeDefinitionMgr.getShapeItemFromDefinition(definition),
|
||||||
|
requiredSlot: i,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,5 +59,10 @@
|
|||||||
"outline": "#111418",
|
"outline": "#111418",
|
||||||
"outlineWidth": 0.75,
|
"outlineWidth": 0.75,
|
||||||
"circleBackground": "rgba(20, 30, 40, 0.3)"
|
"circleBackground": "rgba(20, 30, 40, 0.3)"
|
||||||
|
},
|
||||||
|
|
||||||
|
"shapeTooltip": {
|
||||||
|
"background": "rgba(242, 245, 254, 0.9)",
|
||||||
|
"outline": "#44464e"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,5 +60,10 @@
|
|||||||
"outline": "#55575a",
|
"outline": "#55575a",
|
||||||
"outlineWidth": 0.75,
|
"outlineWidth": 0.75,
|
||||||
"circleBackground": "rgba(40, 50, 65, 0.1)"
|
"circleBackground": "rgba(40, 50, 65, 0.1)"
|
||||||
|
},
|
||||||
|
|
||||||
|
"shapeTooltip": {
|
||||||
|
"background": "#dee1ea",
|
||||||
|
"outline": "#54565e"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,9 @@ export const LANGUAGES = {
|
|||||||
"zh-CN": {
|
"zh-CN": {
|
||||||
// simplified chinese
|
// simplified chinese
|
||||||
name: "简体中文",
|
name: "简体中文",
|
||||||
data: require("./built-temp/base-zh-CN.json"),
|
data: G_WEGAME_VERSION
|
||||||
|
? require("./built-temp/base-zh-CN-ISBN.json")
|
||||||
|
: require("./built-temp/base-zh-CN.json"),
|
||||||
code: "zh",
|
code: "zh",
|
||||||
region: "CN",
|
region: "CN",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -143,6 +143,20 @@ export class ClientAPI {
|
|||||||
return this._request("/v1/puzzles/list/" + category, {});
|
return this._request("/v1/puzzles/list/" + category, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ searchTerm: string; difficulty: string; duration: string }} searchOptions
|
||||||
|
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>}
|
||||||
|
*/
|
||||||
|
apiSearchPuzzles(searchOptions) {
|
||||||
|
if (!this.isLoggedIn()) {
|
||||||
|
return Promise.reject("not-logged-in");
|
||||||
|
}
|
||||||
|
return this._request("/v1/puzzles/search", {
|
||||||
|
method: "POST",
|
||||||
|
body: searchOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} puzzleId
|
* @param {number} puzzleId
|
||||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
||||||
@ -169,7 +183,7 @@ export class ClientAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} shortKey
|
* @param {string} shortKey
|
||||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
||||||
*/
|
*/
|
||||||
apiDownloadPuzzleByKey(shortKey) {
|
apiDownloadPuzzleByKey(shortKey) {
|
||||||
|
|||||||
@ -135,15 +135,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
|
|||||||
|
|
||||||
openExternalLink(url, force = false) {
|
openExternalLink(url, force = false) {
|
||||||
logger.log("Opening external:", url);
|
logger.log("Opening external:", url);
|
||||||
if (force || this.embedProvider.externalLinks) {
|
window.open(url);
|
||||||
window.open(url);
|
|
||||||
} 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: " +
|
|
||||||
url
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
performRestart() {
|
performRestart() {
|
||||||
|
|||||||
@ -272,7 +272,7 @@ export const allApplicationSettings = [
|
|||||||
new EnumSetting("refreshRate", {
|
new EnumSetting("refreshRate", {
|
||||||
options: refreshRateOptions,
|
options: refreshRateOptions,
|
||||||
valueGetter: rate => rate,
|
valueGetter: rate => rate,
|
||||||
textGetter: rate => rate + " Hz",
|
textGetter: rate => T.settings.tickrateHz.replace("<amount>", rate),
|
||||||
category: enumCategories.performance,
|
category: enumCategories.performance,
|
||||||
restartRequired: false,
|
restartRequired: false,
|
||||||
changeCb: (app, id) => {},
|
changeCb: (app, id) => {},
|
||||||
|
|||||||
@ -64,6 +64,24 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
return savegameInterfaces[Savegame.getCurrentVersion()];
|
return savegameInterfaces[Savegame.getCurrentVersion()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Application} app
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static createPuzzleSavegame(app) {
|
||||||
|
return new Savegame(app, {
|
||||||
|
internalId: "puzzle",
|
||||||
|
metaDataRef: {
|
||||||
|
internalId: "puzzle",
|
||||||
|
lastUpdate: 0,
|
||||||
|
version: 0,
|
||||||
|
level: 0,
|
||||||
|
name: "puzzle",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
waitNextFrame,
|
waitNextFrame,
|
||||||
} from "../core/utils";
|
} from "../core/utils";
|
||||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||||
|
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
|
||||||
import { PlatformWrapperImplElectron } from "../platform/electron/wrapper";
|
import { PlatformWrapperImplElectron } from "../platform/electron/wrapper";
|
||||||
import { getApplicationSettingById } from "../profile/application_settings";
|
import { getApplicationSettingById } from "../profile/application_settings";
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
@ -34,35 +35,57 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getInnerHTML() {
|
getInnerHTML() {
|
||||||
|
const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION;
|
||||||
|
const showExitAppButton = G_IS_STANDALONE;
|
||||||
|
const showUpdateLabel = !G_WEGAME_VERSION;
|
||||||
|
const showBrowserWarning = !G_IS_STANDALONE && !isSupportedBrowser();
|
||||||
|
const showPuzzleDLC = !G_WEGAME_VERSION && (G_IS_STANDALONE || G_IS_DEV);
|
||||||
|
const showWegameFooter = G_WEGAME_VERSION;
|
||||||
|
|
||||||
|
let showExternalLinks = true;
|
||||||
|
|
||||||
|
if (G_IS_STANDALONE) {
|
||||||
|
if (G_WEGAME_VERSION || G_CHINA_VERSION) {
|
||||||
|
showExternalLinks = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const wrapper = /** @type {PlatformWrapperImplBrowser} */ (this.app.platformWrapper);
|
||||||
|
if (!wrapper.embedProvider.externalLinks) {
|
||||||
|
showExternalLinks = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let showDiscordLink = showExternalLinks;
|
||||||
|
if (G_CHINA_VERSION) {
|
||||||
|
showDiscordLink = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCrosspromo = !G_IS_STANDALONE && showExternalLinks;
|
||||||
|
const showDemoAdvertisement =
|
||||||
|
showExternalLinks && this.app.restrictionMgr.getIsStandaloneMarketingActive();
|
||||||
|
|
||||||
|
const ownsPuzzleDLC =
|
||||||
|
G_IS_DEV ||
|
||||||
|
(G_IS_STANDALONE &&
|
||||||
|
/** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle);
|
||||||
|
|
||||||
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>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const showDemoBadges = this.app.restrictionMgr.getIsStandaloneMarketingActive();
|
|
||||||
|
|
||||||
const puzzleDlc =
|
|
||||||
G_IS_STANDALONE &&
|
|
||||||
/** @type { PlatformWrapperImplElectron
|
|
||||||
}*/ (this.app.platformWrapper).dlcs.puzzle;
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="topButtons">
|
<div class="topButtons">
|
||||||
${
|
${
|
||||||
G_CHINA_VERSION || G_WEGAME_VERSION
|
showLanguageIcon
|
||||||
? ""
|
? `<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>
|
||||||
${
|
${showExitAppButton ? `<button class="exitAppButton"></button>` : ""}
|
||||||
G_IS_STANDALONE || G_IS_DEV
|
|
||||||
? `
|
|
||||||
<button class="exitAppButton"></button>
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<video autoplay muted loop class="fullscreenBackgroundVideo">
|
<video autoplay muted loop class="fullscreenBackgroundVideo">
|
||||||
@ -71,27 +94,25 @@ export class MainMenuState extends GameState {
|
|||||||
|
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
|
<img src="${cachebust("res/" + getLogoSprite())}" alt="shapez.io Logo">
|
||||||
${G_WEGAME_VERSION ? "" : `<span class="updateLabel">v${G_BUILD_VERSION}!</span>`}
|
${showUpdateLabel ? `<span class="updateLabel">v${G_BUILD_VERSION}!</span>` : ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}" data-columns="${
|
<div class="mainWrapper" data-columns="${showDemoAdvertisement || showPuzzleDLC ? 2 : 1}">
|
||||||
G_IS_STANDALONE && !G_WEGAME_VERSION ? 2 : showDemoBadges ? 2 : 1
|
|
||||||
}">
|
|
||||||
<div class="sideContainer">
|
<div class="sideContainer">
|
||||||
${showDemoBadges ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
|
${showDemoAdvertisement ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mainContainer">
|
<div class="mainContainer">
|
||||||
${
|
${
|
||||||
G_IS_STANDALONE || isSupportedBrowser()
|
showBrowserWarning
|
||||||
? ""
|
? `<div class="browserWarning">${T.mainMenu.browserWarning}</div>`
|
||||||
: `<div class="browserWarning">${T.mainMenu.browserWarning}</div>`
|
: ""
|
||||||
}
|
}
|
||||||
<div class="buttons"></div>
|
<div class="buttons"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${
|
${
|
||||||
(!G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc) || G_IS_DEV
|
showPuzzleDLC && ownsPuzzleDLC
|
||||||
? `
|
? `
|
||||||
<div class="puzzleContainer">
|
<div class="puzzleContainer">
|
||||||
<img class="dlcLogo" src="${cachebust(
|
<img class="dlcLogo" src="${cachebust(
|
||||||
@ -105,7 +126,7 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
${
|
${
|
||||||
!G_WEGAME_VERSION && G_IS_STANDALONE && !puzzleDlc
|
showPuzzleDLC && !ownsPuzzleDLC
|
||||||
? `
|
? `
|
||||||
<div class="puzzleContainer notOwned">
|
<div class="puzzleContainer notOwned">
|
||||||
<span class="badge">
|
<span class="badge">
|
||||||
@ -129,8 +150,9 @@ export class MainMenuState extends GameState {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
${
|
${
|
||||||
G_WEGAME_VERSION
|
showWegameFooter
|
||||||
? `<div class='footer wegameDisclaimer'>
|
? `
|
||||||
|
<div class='footer wegameDisclaimer'>
|
||||||
<div class="disclaimer">
|
<div class="disclaimer">
|
||||||
健康游戏忠告
|
健康游戏忠告
|
||||||
<br>
|
<br>
|
||||||
@ -142,46 +164,46 @@ export class MainMenuState extends GameState {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
<div class="footer ${G_CHINA_VERSION ? "china" : ""} ">
|
|
||||||
|
|
||||||
${
|
<div class="footer ${showExternalLinks ? "" : "noLinks"} ">
|
||||||
G_CHINA_VERSION
|
${
|
||||||
? ""
|
showExternalLinks
|
||||||
: `
|
? `
|
||||||
<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>
|
||||||
</a>`
|
</a>`
|
||||||
}
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
<a class="discordLink boxLink" target="_blank">
|
${
|
||||||
${T.mainMenu.discordLink}
|
showDiscordLink
|
||||||
<span class="thirdpartyLogo discordLogo"></span>
|
? `<a class="discordLink boxLink" target="_blank">
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="sidelinks">
|
${T.mainMenu.discordLink}
|
||||||
${G_CHINA_VERSION ? "" : `<a class="redditLink">${T.mainMenu.subreddit}</a>`}
|
<span class="thirdpartyLogo discordLogo"></span>
|
||||||
|
</a>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
${G_CHINA_VERSION ? "" : `<a class="changelog">${T.changelog.title}</a>`}
|
<div class="sidelinks">
|
||||||
|
${showExternalLinks ? `<a class="redditLink">${T.mainMenu.subreddit}</a>` : ""}
|
||||||
|
|
||||||
${G_CHINA_VERSION ? "" : `<a class="helpTranslate">${T.mainMenu.helpTranslate}</a>`}
|
${showExternalLinks ? `<a class="changelog">${T.changelog.title}</a>` : ""}
|
||||||
|
|
||||||
|
${showExternalLinks ? `<a class="helpTranslate">${T.mainMenu.helpTranslate}</a>` : ""}
|
||||||
|
</div>
|
||||||
|
<div class="author">${T.mainMenu.madeBy.replace(
|
||||||
|
"<author-link>",
|
||||||
|
'<a class="producerLink" target="_blank">Tobias Springer</a>'
|
||||||
|
)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
<div class="author">${T.mainMenu.madeBy.replace(
|
showCrosspromo
|
||||||
"<author-link>",
|
? `<iframe id="crosspromo" src="https://crosspromo.tobspr.io?src=shapez_web"></iframe>`
|
||||||
'<a class="producerLink" target="_blank">Tobias Springer</a>'
|
: ""
|
||||||
)}</div>
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
${
|
|
||||||
G_IS_STANDALONE
|
|
||||||
? ""
|
|
||||||
: `
|
|
||||||
<iframe id="crosspromo" src="https://crosspromo.tobspr.io?src=shapez_web"></iframe>
|
|
||||||
|
|
||||||
`
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -270,8 +292,6 @@ export class MainMenuState extends GameState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const qs = this.htmlElement.querySelector.bind(this.htmlElement);
|
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
||||||
this.onPuzzleModeButtonClicked(true);
|
this.onPuzzleModeButtonClicked(true);
|
||||||
return;
|
return;
|
||||||
@ -295,76 +315,38 @@ export class MainMenuState extends GameState {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.trackClicks(qs(".settingsButton"), this.onSettingsButtonClicked);
|
const clickHandling = {
|
||||||
|
".settingsButton": this.onSettingsButtonClicked,
|
||||||
|
".languageChoose": this.onLanguageChooseClicked,
|
||||||
|
".redditLink": this.onRedditClicked,
|
||||||
|
".changelog": this.onChangelogClicked,
|
||||||
|
".helpTranslate": this.onTranslationHelpLinkClicked,
|
||||||
|
".exitAppButton": this.onExitAppButtonClicked,
|
||||||
|
".steamLink": this.onSteamLinkClicked,
|
||||||
|
".discordLink": () => {
|
||||||
|
this.app.analytics.trackUiClick("main_menu_link_discord");
|
||||||
|
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.discord);
|
||||||
|
},
|
||||||
|
".githubLink": () => {
|
||||||
|
this.app.analytics.trackUiClick("main_menu_link_github");
|
||||||
|
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github);
|
||||||
|
},
|
||||||
|
".producerLink": () => this.app.platformWrapper.openExternalLink("https://tobspr.io"),
|
||||||
|
".puzzleDlcPlayButton": this.onPuzzleModeButtonClicked,
|
||||||
|
".puzzleDlcGetButton": this.onPuzzleWishlistButtonClicked,
|
||||||
|
".wegameDisclaimer > .rating": this.onWegameRatingClicked,
|
||||||
|
};
|
||||||
|
|
||||||
if (!G_CHINA_VERSION && !G_WEGAME_VERSION) {
|
for (const key in clickHandling) {
|
||||||
this.trackClicks(qs(".languageChoose"), this.onLanguageChooseClicked);
|
const handler = clickHandling[key];
|
||||||
this.trackClicks(qs(".redditLink"), this.onRedditClicked);
|
const element = this.htmlElement.querySelector(key);
|
||||||
this.trackClicks(qs(".changelog"), this.onChangelogClicked);
|
if (element) {
|
||||||
this.trackClicks(qs(".helpTranslate"), this.onTranslationHelpLinkClicked);
|
this.trackClicks(element, handler, { preventClick: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (G_IS_STANDALONE) {
|
|
||||||
this.trackClicks(qs(".exitAppButton"), this.onExitAppButtonClicked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderMainMenu();
|
this.renderMainMenu();
|
||||||
this.renderSavegames();
|
this.renderSavegames();
|
||||||
|
|
||||||
const steamLink = this.htmlElement.querySelector(".steamLink");
|
|
||||||
if (steamLink) {
|
|
||||||
this.trackClicks(steamLink, () => this.onSteamLinkClicked(), { preventClick: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const discordLink = this.htmlElement.querySelector(".discordLink");
|
|
||||||
if (discordLink) {
|
|
||||||
this.trackClicks(
|
|
||||||
discordLink,
|
|
||||||
() => {
|
|
||||||
this.app.analytics.trackUiClick("main_menu_link_discord");
|
|
||||||
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.discord);
|
|
||||||
},
|
|
||||||
{ preventClick: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const githubLink = this.htmlElement.querySelector(".githubLink");
|
|
||||||
if (githubLink) {
|
|
||||||
this.trackClicks(
|
|
||||||
githubLink,
|
|
||||||
() => {
|
|
||||||
this.app.analytics.trackUiClick("main_menu_link_github");
|
|
||||||
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github);
|
|
||||||
},
|
|
||||||
{ preventClick: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const producerLink = this.htmlElement.querySelector(".producerLink");
|
|
||||||
if (producerLink) {
|
|
||||||
this.trackClicks(
|
|
||||||
producerLink,
|
|
||||||
() => this.app.platformWrapper.openExternalLink("https://tobspr.io"),
|
|
||||||
{
|
|
||||||
preventClick: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const puzzleModeButton = qs(".puzzleDlcPlayButton");
|
|
||||||
if (puzzleModeButton) {
|
|
||||||
this.trackClicks(puzzleModeButton, () => this.onPuzzleModeButtonClicked());
|
|
||||||
}
|
|
||||||
|
|
||||||
const puzzleWishlistButton = qs(".puzzleDlcGetButton");
|
|
||||||
if (puzzleWishlistButton) {
|
|
||||||
this.trackClicks(puzzleWishlistButton, () => this.onPuzzleWishlistButtonClicked());
|
|
||||||
}
|
|
||||||
|
|
||||||
const wegameRating = qs(".wegameDisclaimer > .rating");
|
|
||||||
if (wegameRating) {
|
|
||||||
this.trackClicks(wegameRating, () => this.onWegameRatingClicked());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMainMenu() {
|
renderMainMenu() {
|
||||||
@ -535,9 +517,12 @@ export class MainMenuState extends GameState {
|
|||||||
downloadButton.classList.add("styledButton", "downloadGame");
|
downloadButton.classList.add("styledButton", "downloadGame");
|
||||||
elem.appendChild(downloadButton);
|
elem.appendChild(downloadButton);
|
||||||
|
|
||||||
const renameButton = document.createElement("button");
|
if (!G_WEGAME_VERSION) {
|
||||||
renameButton.classList.add("styledButton", "renameGame");
|
const renameButton = document.createElement("button");
|
||||||
name.appendChild(renameButton);
|
renameButton.classList.add("styledButton", "renameGame");
|
||||||
|
name.appendChild(renameButton);
|
||||||
|
this.trackClicks(renameButton, () => this.requestRenameSavegame(games[i]));
|
||||||
|
}
|
||||||
|
|
||||||
const resumeButton = document.createElement("button");
|
const resumeButton = document.createElement("button");
|
||||||
resumeButton.classList.add("styledButton", "resumeGame");
|
resumeButton.classList.add("styledButton", "resumeGame");
|
||||||
@ -546,7 +531,6 @@ export class MainMenuState extends GameState {
|
|||||||
this.trackClicks(deleteButton, () => this.deleteGame(games[i]));
|
this.trackClicks(deleteButton, () => this.deleteGame(games[i]));
|
||||||
this.trackClicks(downloadButton, () => this.downloadGame(games[i]));
|
this.trackClicks(downloadButton, () => this.downloadGame(games[i]));
|
||||||
this.trackClicks(resumeButton, () => this.resumeGame(games[i]));
|
this.trackClicks(resumeButton, () => this.resumeGame(games[i]));
|
||||||
this.trackClicks(renameButton, () => this.requestRenameSavegame(games[i]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -701,16 +685,12 @@ export class MainMenuState extends GameState {
|
|||||||
|
|
||||||
onWegameRatingClicked() {
|
onWegameRatingClicked() {
|
||||||
this.dialogs.showInfo(
|
this.dialogs.showInfo(
|
||||||
"",
|
"提示说明",
|
||||||
`
|
`
|
||||||
1)本游戏是一款休闲建造类单机游戏,适用于年满8周岁及以上的用户。<br>
|
1)本游戏是一款休闲建造类单机游戏,画面简洁而乐趣充足。适用于年满8周岁及以上的用户,建议未成年人在家长监护下使用游戏产品。<br>
|
||||||
2)本游戏模拟简单的生产流水线,剧情简单且积极向上,没有基于真实
|
2)本游戏模拟简单的生产流水线,剧情简单且积极向上,没有基于真实历史和现实事件的改编内容。游戏玩法为摆放简单的部件,完成生产目标。游戏为单机作品,没有基于文字和语音的陌生人社交系统。<br>
|
||||||
历史和现实事件的改编内容。游戏玩法为摆放简单的部件,完成生产目标。
|
3)本游戏中有用户实名认证系统,认证为未成年人的用户将接受以下管理:未满8周岁的用户不能付费;8周岁以上未满16周岁的未成年人用户,单次充值金额不得超过50元人民币,每月充值金额累计不得超过200元人民币;16周岁以上的未成年人用户,单次充值金额不得超过100元人民币,每月充值金额累计不得超过400元人民币。未成年人用户每日22点到次日8点不得使用,法定节假日每天不得使用超过3小时,其他时间每天不得使用超过1.5小时。<br>
|
||||||
游戏为单机作品,没有基于文字和语音的陌生人社交系统。<br>
|
4)游戏功能说明:一款关于传送带自动化生产特定形状产品的工厂流水线模拟游戏,画面简洁而乐趣充足,可以让玩家在轻松愉快的氛围下获得各种游戏乐趣,体验完成目标的成就感。游戏没有失败功能,自动存档,不存在较强的挫折体验。
|
||||||
3)游戏中有用户实名认证系统,认证为未成年人的用户将接受以下管理:
|
|
||||||
游戏为买断制,不存在后续充值付费内容。未成年人用户每日22点到次日
|
|
||||||
8点不得使用,法定节假日每天不得使用超过3小时,其它时间每天使用游
|
|
||||||
戏不得超过1.5小时。
|
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -726,11 +706,14 @@ export class MainMenuState extends GameState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const savegame = this.app.savegameMgr.getSavegameById(latestInternalId);
|
const savegame = this.app.savegameMgr.getSavegameById(latestInternalId);
|
||||||
savegame.readAsync().then(() => {
|
savegame
|
||||||
this.moveToState("InGameState", {
|
.readAsync()
|
||||||
savegame,
|
.then(() => this.app.adProvider.showVideoAd())
|
||||||
|
.then(() => {
|
||||||
|
this.moveToState("InGameState", {
|
||||||
|
savegame,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLeave() {
|
onLeave() {
|
||||||
|
|||||||
@ -14,17 +14,30 @@ const navigation = {
|
|||||||
categories: ["official", "top-rated", "trending", "trending-weekly", "new"],
|
categories: ["official", "top-rated", "trending", "trending-weekly", "new"],
|
||||||
difficulties: ["easy", "medium", "hard"],
|
difficulties: ["easy", "medium", "hard"],
|
||||||
account: ["mine", "completed"],
|
account: ["mine", "completed"],
|
||||||
|
search: ["search"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger("puzzle-menu");
|
const logger = createLogger("puzzle-menu");
|
||||||
|
|
||||||
let lastCategory = "official";
|
let lastCategory = "official";
|
||||||
|
|
||||||
|
let lastSearchOptions = {
|
||||||
|
searchTerm: "",
|
||||||
|
difficulty: "any",
|
||||||
|
duration: "any",
|
||||||
|
includeCompleted: false,
|
||||||
|
};
|
||||||
|
|
||||||
export class PuzzleMenuState extends TextualGameState {
|
export class PuzzleMenuState extends TextualGameState {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("PuzzleMenuState");
|
super("PuzzleMenuState");
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.activeCategory = "";
|
this.activeCategory = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<import("../savegame/savegame_typedefs").PuzzleMetadata>}
|
||||||
|
*/
|
||||||
|
this.puzzles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getThemeMusic() {
|
getThemeMusic() {
|
||||||
@ -100,13 +113,23 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
activeCategory.classList.remove("active");
|
activeCategory.classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.htmlElement.querySelector(`[data-category="${category}"]`).classList.add("active");
|
const categoryElement = this.htmlElement.querySelector(`[data-category="${category}"]`);
|
||||||
|
if (categoryElement) {
|
||||||
|
categoryElement.classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
const container = this.htmlElement.querySelector("#mainContainer");
|
const container = this.htmlElement.querySelector("#mainContainer");
|
||||||
while (container.firstChild) {
|
while (container.firstChild) {
|
||||||
container.removeChild(container.firstChild);
|
container.removeChild(container.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (category === "search") {
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
this.startSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const loadingElement = document.createElement("div");
|
const loadingElement = document.createElement("div");
|
||||||
loadingElement.classList.add("loader");
|
loadingElement.classList.add("loader");
|
||||||
loadingElement.innerText = T.global.loading + "...";
|
loadingElement.innerText = T.global.loading + "...";
|
||||||
@ -161,23 +184,148 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const children = navigation[rootCategory];
|
const children = navigation[rootCategory];
|
||||||
for (const category of children) {
|
if (children.length > 1) {
|
||||||
const button = document.createElement("button");
|
for (const category of children) {
|
||||||
button.setAttribute("data-category", category);
|
const button = document.createElement("button");
|
||||||
button.classList.add("styledButton", "category", "child");
|
button.setAttribute("data-category", category);
|
||||||
button.innerText = T.puzzleMenu.categories[category];
|
button.classList.add("styledButton", "category", "child");
|
||||||
this.trackClicks(button, () => this.selectCategory(category));
|
button.innerText = T.puzzleMenu.categories[category];
|
||||||
subContainer.appendChild(button);
|
this.trackClicks(button, () => this.selectCategory(category));
|
||||||
|
subContainer.appendChild(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootCategory === "search") {
|
||||||
|
this.renderSearchForm(subContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectCategory(subCategory);
|
this.selectCategory(subCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSearchForm(parent) {
|
||||||
|
const container = document.createElement("form");
|
||||||
|
container.classList.add("searchForm");
|
||||||
|
|
||||||
|
// Search
|
||||||
|
const searchField = document.createElement("input");
|
||||||
|
searchField.value = lastSearchOptions.searchTerm;
|
||||||
|
searchField.classList.add("search");
|
||||||
|
searchField.setAttribute("type", "text");
|
||||||
|
searchField.setAttribute("placeholder", T.puzzleMenu.search.placeholder);
|
||||||
|
searchField.addEventListener("input", () => {
|
||||||
|
lastSearchOptions.searchTerm = searchField.value.trim();
|
||||||
|
});
|
||||||
|
container.appendChild(searchField);
|
||||||
|
|
||||||
|
// Difficulty
|
||||||
|
const difficultyFilter = document.createElement("select");
|
||||||
|
for (const difficulty of ["any", "easy", "medium", "hard"]) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = difficulty;
|
||||||
|
option.innerText = T.puzzleMenu.search.difficulties[difficulty];
|
||||||
|
if (option.value === lastSearchOptions.difficulty) {
|
||||||
|
option.setAttribute("selected", "selected");
|
||||||
|
}
|
||||||
|
difficultyFilter.appendChild(option);
|
||||||
|
}
|
||||||
|
difficultyFilter.addEventListener("change", () => {
|
||||||
|
const option = difficultyFilter.value;
|
||||||
|
lastSearchOptions.difficulty = option;
|
||||||
|
});
|
||||||
|
container.appendChild(difficultyFilter);
|
||||||
|
|
||||||
|
// Duration
|
||||||
|
const durationFilter = document.createElement("select");
|
||||||
|
for (const duration of ["any", "short", "medium", "long"]) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = duration;
|
||||||
|
option.innerText = T.puzzleMenu.search.durations[duration];
|
||||||
|
if (option.value === lastSearchOptions.duration) {
|
||||||
|
option.setAttribute("selected", "selected");
|
||||||
|
}
|
||||||
|
durationFilter.appendChild(option);
|
||||||
|
}
|
||||||
|
durationFilter.addEventListener("change", () => {
|
||||||
|
const option = durationFilter.value;
|
||||||
|
lastSearchOptions.duration = option;
|
||||||
|
});
|
||||||
|
container.appendChild(durationFilter);
|
||||||
|
|
||||||
|
// Include completed
|
||||||
|
const labelCompleted = document.createElement("label");
|
||||||
|
labelCompleted.classList.add("filterCompleted");
|
||||||
|
|
||||||
|
const inputCompleted = document.createElement("input");
|
||||||
|
inputCompleted.setAttribute("type", "checkbox");
|
||||||
|
if (lastSearchOptions.includeCompleted) {
|
||||||
|
inputCompleted.setAttribute("checked", "checked");
|
||||||
|
}
|
||||||
|
inputCompleted.addEventListener("change", () => {
|
||||||
|
lastSearchOptions.includeCompleted = inputCompleted.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
labelCompleted.appendChild(inputCompleted);
|
||||||
|
|
||||||
|
const text = document.createTextNode(T.puzzleMenu.search.includeCompleted);
|
||||||
|
labelCompleted.appendChild(text);
|
||||||
|
|
||||||
|
container.appendChild(labelCompleted);
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
const submitButton = document.createElement("button");
|
||||||
|
submitButton.classList.add("styledButton");
|
||||||
|
submitButton.setAttribute("type", "submit");
|
||||||
|
submitButton.innerText = T.puzzleMenu.search.action;
|
||||||
|
container.appendChild(submitButton);
|
||||||
|
|
||||||
|
container.addEventListener("submit", event => {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log("Search:", searchField.value.trim());
|
||||||
|
this.startSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
parent.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
startSearch() {
|
||||||
|
if (this.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const container = this.htmlElement.querySelector("#mainContainer");
|
||||||
|
while (container.firstChild) {
|
||||||
|
container.removeChild(container.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingElement = document.createElement("div");
|
||||||
|
loadingElement.classList.add("loader");
|
||||||
|
loadingElement.innerText = T.global.loading + "...";
|
||||||
|
container.appendChild(loadingElement);
|
||||||
|
|
||||||
|
this.asyncChannel
|
||||||
|
.watch(this.app.clientApi.apiSearchPuzzles(lastSearchOptions))
|
||||||
|
.then(
|
||||||
|
puzzles => this.renderPuzzles(puzzles),
|
||||||
|
error => {
|
||||||
|
this.dialogs.showWarning(
|
||||||
|
T.dialogs.puzzleLoadFailed.title,
|
||||||
|
T.dialogs.puzzleLoadFailed.desc + " " + error
|
||||||
|
);
|
||||||
|
this.renderPuzzles([]);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => (this.loading = false));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
|
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
|
||||||
*/
|
*/
|
||||||
renderPuzzles(puzzles) {
|
renderPuzzles(puzzles) {
|
||||||
|
this.puzzles = puzzles;
|
||||||
|
|
||||||
const container = this.htmlElement.querySelector("#mainContainer");
|
const container = this.htmlElement.querySelector("#mainContainer");
|
||||||
while (container.firstChild) {
|
while (container.firstChild) {
|
||||||
container.removeChild(container.firstChild);
|
container.removeChild(container.firstChild);
|
||||||
@ -224,15 +372,15 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
difficulty.innerText = completionPercentage + "%";
|
difficulty.innerText = completionPercentage + "%";
|
||||||
stats.appendChild(difficulty);
|
stats.appendChild(difficulty);
|
||||||
|
|
||||||
if (completionPercentage < 40) {
|
if (puzzle.difficulty < 0.2) {
|
||||||
difficulty.classList.add("stage--hard");
|
|
||||||
difficulty.innerText = T.puzzleMenu.difficulties.hard;
|
|
||||||
} else if (completionPercentage < 80) {
|
|
||||||
difficulty.classList.add("stage--medium");
|
|
||||||
difficulty.innerText = T.puzzleMenu.difficulties.medium;
|
|
||||||
} else {
|
|
||||||
difficulty.classList.add("stage--easy");
|
difficulty.classList.add("stage--easy");
|
||||||
difficulty.innerText = T.puzzleMenu.difficulties.easy;
|
difficulty.innerText = T.puzzleMenu.difficulties.easy;
|
||||||
|
} else if (puzzle.difficulty > 0.6) {
|
||||||
|
difficulty.classList.add("stage--hard");
|
||||||
|
difficulty.innerText = T.puzzleMenu.difficulties.hard;
|
||||||
|
} else {
|
||||||
|
difficulty.classList.add("stage--medium");
|
||||||
|
difficulty.innerText = T.puzzleMenu.difficulties.medium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +424,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
|
|
||||||
container.appendChild(elem);
|
container.appendChild(elem);
|
||||||
|
|
||||||
this.trackClicks(elem, () => this.playPuzzle(puzzle));
|
this.trackClicks(elem, () => this.playPuzzle(puzzle.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (puzzles.length === 0) {
|
if (puzzles.length === 0) {
|
||||||
@ -329,20 +477,26 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata} puzzle
|
* @param {number} puzzleId
|
||||||
|
* @param {Array<number>=} nextPuzzles
|
||||||
*/
|
*/
|
||||||
playPuzzle(puzzle) {
|
playPuzzle(puzzleId, nextPuzzles) {
|
||||||
const closeLoading = this.dialogs.showLoadingDialog();
|
const closeLoading = this.dialogs.showLoadingDialog();
|
||||||
|
|
||||||
this.app.clientApi.apiDownloadPuzzle(puzzle.id).then(
|
this.asyncChannel.watch(this.app.clientApi.apiDownloadPuzzle(puzzleId)).then(
|
||||||
puzzleData => {
|
puzzleData => {
|
||||||
closeLoading();
|
closeLoading();
|
||||||
logger.log("Got puzzle:", puzzleData);
|
|
||||||
this.startLoadedPuzzle(puzzleData);
|
nextPuzzles =
|
||||||
|
nextPuzzles || this.puzzles.filter(puzzle => !puzzle.completed).map(puzzle => puzzle.id);
|
||||||
|
nextPuzzles = nextPuzzles.filter(id => id !== puzzleId);
|
||||||
|
|
||||||
|
logger.log("Got puzzle:", puzzleData, "next puzzles:", nextPuzzles);
|
||||||
|
this.startLoadedPuzzle(puzzleData, nextPuzzles);
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
closeLoading();
|
closeLoading();
|
||||||
logger.error("Failed to download puzzle", puzzle.id, ":", err);
|
logger.error("Failed to download puzzle", puzzleId, ":", err);
|
||||||
this.dialogs.showWarning(
|
this.dialogs.showWarning(
|
||||||
T.dialogs.puzzleDownloadError.title,
|
T.dialogs.puzzleDownloadError.title,
|
||||||
T.dialogs.puzzleDownloadError.desc + " " + err
|
T.dialogs.puzzleDownloadError.desc + " " + err
|
||||||
@ -355,18 +509,24 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
*
|
*
|
||||||
* @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle
|
* @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle
|
||||||
*/
|
*/
|
||||||
startLoadedPuzzle(puzzle) {
|
startLoadedPuzzle(puzzle, nextPuzzles) {
|
||||||
const savegame = this.createEmptySavegame();
|
const savegame = Savegame.createPuzzleSavegame(this.app);
|
||||||
this.moveToState("InGameState", {
|
this.moveToState("InGameState", {
|
||||||
gameModeId: enumGameModeIds.puzzlePlay,
|
gameModeId: enumGameModeIds.puzzlePlay,
|
||||||
gameModeParameters: {
|
gameModeParameters: {
|
||||||
puzzle,
|
puzzle,
|
||||||
|
nextPuzzles,
|
||||||
},
|
},
|
||||||
savegame,
|
savegame,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnter(payload) {
|
onEnter(payload) {
|
||||||
|
if (payload.continueQueue) {
|
||||||
|
logger.log("Continuing puzzle queue:", payload);
|
||||||
|
this.playPuzzle(payload.continueQueue[0], payload.continueQueue.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
// Find old category
|
// Find old category
|
||||||
let rootCategory = "categories";
|
let rootCategory = "categories";
|
||||||
for (const [id, children] of Object.entries(navigation)) {
|
for (const [id, children] of Object.entries(navigation)) {
|
||||||
@ -391,26 +551,13 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
|
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmptySavegame() {
|
|
||||||
return new Savegame(this.app, {
|
|
||||||
internalId: "puzzle",
|
|
||||||
metaDataRef: {
|
|
||||||
internalId: "puzzle",
|
|
||||||
lastUpdate: 0,
|
|
||||||
version: 0,
|
|
||||||
level: 0,
|
|
||||||
name: "puzzle",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPuzzle() {
|
loadPuzzle() {
|
||||||
const shortKeyInput = new FormElementInput({
|
const shortKeyInput = new FormElementInput({
|
||||||
id: "shortKey",
|
id: "shortKey",
|
||||||
label: null,
|
label: null,
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
validator: val => ShapeDefinition.isValidShortKey(val),
|
validator: val => ShapeDefinition.isValidShortKey(val) || val.startsWith("/"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialog = new DialogWithForm({
|
const dialog = new DialogWithForm({
|
||||||
@ -423,9 +570,16 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
this.dialogs.internalShowDialog(dialog);
|
this.dialogs.internalShowDialog(dialog);
|
||||||
|
|
||||||
dialog.buttonSignals.ok.add(() => {
|
dialog.buttonSignals.ok.add(() => {
|
||||||
|
const searchTerm = shortKeyInput.getValue();
|
||||||
|
|
||||||
|
if (searchTerm === "/apikey") {
|
||||||
|
alert("Your api key is: " + this.app.clientApi.token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const closeLoading = this.dialogs.showLoadingDialog();
|
const closeLoading = this.dialogs.showLoadingDialog();
|
||||||
|
|
||||||
this.app.clientApi.apiDownloadPuzzleByKey(shortKeyInput.getValue()).then(
|
this.app.clientApi.apiDownloadPuzzleByKey(searchTerm).then(
|
||||||
puzzle => {
|
puzzle => {
|
||||||
closeLoading();
|
closeLoading();
|
||||||
this.startLoadedPuzzle(puzzle);
|
this.startLoadedPuzzle(puzzle);
|
||||||
@ -452,7 +606,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savegame = this.createEmptySavegame();
|
const savegame = Savegame.createPuzzleSavegame(this.app);
|
||||||
this.moveToState("InGameState", {
|
this.moveToState("InGameState", {
|
||||||
gameModeId: enumGameModeIds.puzzleEdit,
|
gameModeId: enumGameModeIds.puzzleEdit,
|
||||||
gameModeParameters: {},
|
gameModeParameters: {},
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { THIRDPARTY_URLS } from "../core/config";
|
||||||
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";
|
||||||
@ -34,6 +35,8 @@ export class SettingsState extends TextualGameState {
|
|||||||
? ""
|
? ""
|
||||||
: `
|
: `
|
||||||
<button class="styledButton about">${T.about.title}</button>
|
<button class="styledButton about">${T.about.title}</button>
|
||||||
|
<button class="styledButton privacy">Privacy Policy</button>
|
||||||
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
<div class="versionbar">
|
<div class="versionbar">
|
||||||
@ -109,6 +112,9 @@ export class SettingsState extends TextualGameState {
|
|||||||
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
|
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
|
||||||
preventDefault: false,
|
preventDefault: false,
|
||||||
});
|
});
|
||||||
|
this.trackClicks(this.htmlElement.querySelector(".privacy"), this.onPrivacyClicked, {
|
||||||
|
preventDefault: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
|
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
|
||||||
@ -180,6 +186,10 @@ export class SettingsState extends TextualGameState {
|
|||||||
this.moveToStateAddGoBack("AboutState");
|
this.moveToStateAddGoBack("AboutState");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPrivacyClicked() {
|
||||||
|
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.privacyPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
onKeybindingsClicked() {
|
onKeybindingsClicked() {
|
||||||
this.moveToStateAddGoBack("KeybindingsState");
|
this.moveToStateAddGoBack("KeybindingsState");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class WegameSplashState extends GameState {
|
|||||||
<div>抵制不良游戏,拒绝盗版游戏。</div>
|
<div>抵制不良游戏,拒绝盗版游戏。</div>
|
||||||
<div>注意自我保护,谨防受骗上当。</div>
|
<div>注意自我保护,谨防受骗上当。</div>
|
||||||
<div>适度游戏益脑,沉迷游戏伤身。</div>
|
<div>适度游戏益脑,沉迷游戏伤身。</div>
|
||||||
<div>适度游戏益脑,沉迷游戏伤身。</div>
|
<div>合理安排时间,享受健康生活。</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -155,6 +155,23 @@ puzzleMenu:
|
|||||||
account: My Puzzles
|
account: My Puzzles
|
||||||
search: Search
|
search: Search
|
||||||
|
|
||||||
|
search:
|
||||||
|
action: Search
|
||||||
|
placeholder: Enter a puzzle or author name
|
||||||
|
includeCompleted: Include Completed
|
||||||
|
|
||||||
|
difficulties:
|
||||||
|
any: Any Difficulty
|
||||||
|
easy: Easy
|
||||||
|
medium: Medium
|
||||||
|
hard: Hard
|
||||||
|
|
||||||
|
durations:
|
||||||
|
any: Any Duration
|
||||||
|
short: Short (< 2 min)
|
||||||
|
medium: Normal
|
||||||
|
long: Long (> 10 min)
|
||||||
|
|
||||||
difficulties:
|
difficulties:
|
||||||
easy: Easy
|
easy: Easy
|
||||||
medium: Medium
|
medium: Medium
|
||||||
@ -671,6 +688,7 @@ ingame:
|
|||||||
|
|
||||||
continueBtn: Keep Playing
|
continueBtn: Keep Playing
|
||||||
menuBtn: Menu
|
menuBtn: Menu
|
||||||
|
nextPuzzle: Next Puzzle
|
||||||
|
|
||||||
puzzleMetadata:
|
puzzleMetadata:
|
||||||
author: Author
|
author: Author
|
||||||
@ -1094,7 +1112,7 @@ settings:
|
|||||||
staging: Staging
|
staging: Staging
|
||||||
prod: Production
|
prod: Production
|
||||||
buildDate: Built <at-date>
|
buildDate: Built <at-date>
|
||||||
|
tickrateHz: <amount> Hz
|
||||||
rangeSliderPercentage: <amount> %
|
rangeSliderPercentage: <amount> %
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
@ -1377,6 +1395,8 @@ keybindings:
|
|||||||
placeMultiple: Stay in placement mode
|
placeMultiple: Stay in placement mode
|
||||||
placeInverse: Invert automatic belt orientation
|
placeInverse: Invert automatic belt orientation
|
||||||
|
|
||||||
|
showShapeTooltip: Show shape output tooltip
|
||||||
|
|
||||||
about:
|
about:
|
||||||
title: About this Game
|
title: About this Game
|
||||||
body: >-
|
body: >-
|
||||||
|
|||||||
902
translations/base-zh-CN-ISBN.yaml
Normal file
@ -0,0 +1,902 @@
|
|||||||
|
steamPage:
|
||||||
|
shortText: “唯一能限制您的,只有您的想象力!” 《异形工厂》(Shapez.io)
|
||||||
|
是一款在无限拓展的地图上,通过建造各类工厂设施,来自动化生产与组合出愈加复杂图形的游戏。
|
||||||
|
discordLinkShort: 官方 Discord 服务器
|
||||||
|
intro: |-
|
||||||
|
“奇形怪状,放飞想象!”
|
||||||
|
“自动生产,尽情创造!”
|
||||||
|
《异形工厂》(Shapez.io)是一款能让您尽情发挥创造力,充分享受思维乐趣的IO游戏。
|
||||||
|
游戏很轻松,只需建造工厂,布好设施,无需操作即能自动创造出各种各样的几何图形。
|
||||||
|
挑战很烧脑,随着等级提升,需要创造的图形将会越来越复杂,同时您还需要在无限扩展的地图中持续扩建优化您的工厂。
|
||||||
|
以为这就是全部了吗? 不!图形的生产需求将会指数性增长,持续的扩大规模和熵增带来的无序,将会是令人头痛的问题!
|
||||||
|
这还不是全部! 一开始我们创造了图形,然后我们需要学会提取和混合来让它们五颜六色。
|
||||||
|
然后,还有吗? 当然,唯有思维,方能无限。
|
||||||
|
|
||||||
|
欢迎免费体验试玩版:“让您的想象力插上翅膀!”
|
||||||
|
和最聪明的玩家一起挑战,请访问 Steam 游戏商城购买《异形工厂》(Shapez.io)的完整版,
|
||||||
|
what_others_say: 来看看玩家们对《异形工厂》(Shapez.io)的评价
|
||||||
|
nothernlion_comment: 非常棒的有游戏,我的游戏过程充满乐趣,不觉时间飞逝。
|
||||||
|
notch_comment: 哦,天哪!我真得该去睡了!但我想我刚刚搞定如何在游戏里面制造一台电脑出来。
|
||||||
|
steam_review_comment: 这是一个不知不觉偷走你时间,但你并不会想要追回的游戏。非常烧脑的挑战,让我这样的完美主义者停不下来,总是希望可以再高效一些。
|
||||||
|
|
||||||
|
global:
|
||||||
|
loading: 加载中
|
||||||
|
error: 错误
|
||||||
|
thousandsDivider: ","
|
||||||
|
decimalSeparator: .
|
||||||
|
suffix:
|
||||||
|
thousands: 千
|
||||||
|
millions: 百万
|
||||||
|
billions: 亿万
|
||||||
|
trillions: 兆
|
||||||
|
infinite: 无限
|
||||||
|
time:
|
||||||
|
oneSecondAgo: 1秒前
|
||||||
|
xSecondsAgo: <x>秒前
|
||||||
|
oneMinuteAgo: 1分钟前
|
||||||
|
xMinutesAgo: <x>分钟前
|
||||||
|
oneHourAgo: 1小时前
|
||||||
|
xHoursAgo: <x>小时前
|
||||||
|
oneDayAgo: 1天前
|
||||||
|
xDaysAgo: <x>天前
|
||||||
|
secondsShort: <seconds>秒
|
||||||
|
minutesAndSecondsShort: <minutes>分 <seconds>秒
|
||||||
|
hoursAndMinutesShort: <hours>时 <minutes>分
|
||||||
|
xMinutes: <x>分钟
|
||||||
|
keys:
|
||||||
|
tab: TAB键
|
||||||
|
control: CTRL键
|
||||||
|
alt: ALT键
|
||||||
|
escape: ESC键
|
||||||
|
shift: SHIFT键
|
||||||
|
space: 空格键
|
||||||
|
demoBanners:
|
||||||
|
title: 试玩版
|
||||||
|
intro: 购买完整版以解锁所有游戏内容!
|
||||||
|
mainMenu:
|
||||||
|
play: 开始游戏
|
||||||
|
changelog: 更新日志
|
||||||
|
importSavegame: 读取存档
|
||||||
|
openSourceHint: 本游戏已开源!
|
||||||
|
discordLink: 官方Discord服务器
|
||||||
|
helpTranslate: 帮助我们翻译!
|
||||||
|
browserWarning: 很抱歉, 本游戏在当前浏览器上可能运行缓慢! 使用 Chrome 或者购买完整版以得到更好的体验。
|
||||||
|
savegameLevel: 第<x>关
|
||||||
|
savegameLevelUnknown: 未知关卡
|
||||||
|
continue: 继续游戏
|
||||||
|
newGame: 新游戏
|
||||||
|
madeBy: 作者:<author-link>
|
||||||
|
subreddit: Reddit
|
||||||
|
savegameUnnamed: 存档未命名
|
||||||
|
dialogs:
|
||||||
|
buttons:
|
||||||
|
ok: 确认
|
||||||
|
delete: 删除
|
||||||
|
cancel: 取消
|
||||||
|
later: 以后
|
||||||
|
restart: 重新开始
|
||||||
|
reset: 重置
|
||||||
|
getStandalone: 获取完整版
|
||||||
|
deleteGame: 我没疯!我知道我在做什么!
|
||||||
|
viewUpdate: 查看更新
|
||||||
|
showUpgrades: 显示设施升级
|
||||||
|
showKeybindings: 显示按键设置
|
||||||
|
importSavegameError:
|
||||||
|
title: 读取错误
|
||||||
|
text: 未能读取您的存档:
|
||||||
|
importSavegameSuccess:
|
||||||
|
title: 读取成功
|
||||||
|
text: 存档被成功读取
|
||||||
|
gameLoadFailure:
|
||||||
|
title: 存档损坏
|
||||||
|
text: 未能读取您的存档:
|
||||||
|
confirmSavegameDelete:
|
||||||
|
title: 确认删除
|
||||||
|
text: 您确定要删除这个游戏吗?<br><br> '<savegameName>' 等级 <savegameLevel><br><br> 该操作无法回退!
|
||||||
|
savegameDeletionError:
|
||||||
|
title: 删除失败
|
||||||
|
text: 未能删除您的存档
|
||||||
|
restartRequired:
|
||||||
|
title: 需要重启游戏
|
||||||
|
text: 您需要重启游戏以应用变更的设置。
|
||||||
|
editKeybinding:
|
||||||
|
title: 更改按键设定
|
||||||
|
desc: 请按下您想要使用的按键以设定,或者按下 ESC 键来取消设定。
|
||||||
|
resetKeybindingsConfirmation:
|
||||||
|
title: 重置按键设定
|
||||||
|
desc: 您将要重置所有按键设定,请确认。
|
||||||
|
keybindingsResetOk:
|
||||||
|
title: 重置按键设定
|
||||||
|
desc: 成功重置所有按键设定!
|
||||||
|
featureRestriction:
|
||||||
|
title: 试玩版
|
||||||
|
desc: 您尝试使用了<feature>一项功能。该功能在试玩版中不可用。请考虑购买完整版以获得更好的体验。
|
||||||
|
oneSavegameLimit:
|
||||||
|
title: 存档数量限制
|
||||||
|
desc: 试玩版中只能保存一份存档。请删除旧存档或者购买完整版!
|
||||||
|
updateSummary:
|
||||||
|
title: 新内容更新啦!
|
||||||
|
desc: "以下为游戏最新更新内容:"
|
||||||
|
upgradesIntroduction:
|
||||||
|
title: 解锁升级
|
||||||
|
desc: <strong>您生产过的所有图形都能被用来解锁升级。</strong> 所以不要销毁您之前建造的工厂! 注意:升级菜单在屏幕右上角。
|
||||||
|
massDeleteConfirm:
|
||||||
|
title: 确认删除
|
||||||
|
desc: 您将要删除很多设施,准确来说有<count>种! 您确定要这么做吗?
|
||||||
|
blueprintsNotUnlocked:
|
||||||
|
title: 尚未解锁
|
||||||
|
desc: 您还没有解锁蓝图功能!通过第12关的挑战后可解锁蓝图。
|
||||||
|
keybindingsIntroduction:
|
||||||
|
title: 实用快捷键
|
||||||
|
desc:
|
||||||
|
"这个游戏有很多有用的快捷键设定。 以下是其中的一些介绍,记得在<strong>按键设置</strong>中查看其他按键设定!<br><br>
|
||||||
|
<code class='keybinding'>CTRL键</code> + 拖动:选择区域以复制或删除。<br> <code
|
||||||
|
class='keybinding'>SHIFT键</code>: 按住以放置多个同一种设施。<br> <code
|
||||||
|
class='keybinding'>ALT键</code>: 反向放置传送带。<br>"
|
||||||
|
createMarker:
|
||||||
|
title: 创建地图标记
|
||||||
|
desc:
|
||||||
|
填写一个有意义的名称, 还可以同时包含一个形状的 <strong>短代码</strong> (您可以 <link>点击这里</link>
|
||||||
|
生成短代码)
|
||||||
|
titleEdit: 编辑地图标记
|
||||||
|
markerDemoLimit:
|
||||||
|
desc: 在试玩版中您只能创建两个地图标记。请获取完整版以创建更多标记。
|
||||||
|
massCutConfirm:
|
||||||
|
title: 确认剪切
|
||||||
|
desc: 您将要剪切很多设施,准确来说有<count>种! 您确定要这么做吗?
|
||||||
|
exportScreenshotWarning:
|
||||||
|
title: 工厂截图
|
||||||
|
desc: 您将要导出您整个工厂基地的截图。如果您已经建设了一个规模很大的基地,生成截图的过程将会很慢,且有可能导致游戏崩溃!
|
||||||
|
massCutInsufficientConfirm:
|
||||||
|
title: 确认剪切
|
||||||
|
desc: 您没有足够的图形来粘贴这个区域!您确定要剪切吗?
|
||||||
|
editSignal:
|
||||||
|
title: 设置信号
|
||||||
|
descItems: "选择一个预定义的项目:"
|
||||||
|
descShortKey: ... 或者输入图形的 <strong>短代码</strong> (您可以 <link>点击这里</link> 生成短代码)
|
||||||
|
renameSavegame:
|
||||||
|
title: 重命名游戏存档
|
||||||
|
desc: 您可以在此重命名游戏存档。
|
||||||
|
tutorialVideoAvailable:
|
||||||
|
title: 教程
|
||||||
|
desc: 这个关卡有视频攻略! 您想查看这个视频攻略?
|
||||||
|
tutorialVideoAvailableForeignLanguage:
|
||||||
|
title: 教程
|
||||||
|
desc: 这个关卡有英语版本的视频攻略! 您想查看这个视频攻略吗??
|
||||||
|
ingame:
|
||||||
|
keybindingsOverlay:
|
||||||
|
moveMap: 移动地图
|
||||||
|
selectBuildings: 选择区域
|
||||||
|
stopPlacement: 停止放置
|
||||||
|
rotateBuilding: 转动设施
|
||||||
|
placeMultiple: 放置多个
|
||||||
|
reverseOrientation: 反向放置
|
||||||
|
disableAutoOrientation: 关闭自动定向
|
||||||
|
toggleHud: 切换可视化界面
|
||||||
|
placeBuilding: 放置设施
|
||||||
|
createMarker: 创建地图标记
|
||||||
|
delete: 销毁
|
||||||
|
pasteLastBlueprint: 粘贴上一个蓝图
|
||||||
|
lockBeltDirection: 启用传送带规划器
|
||||||
|
plannerSwitchSide: 规划器换边
|
||||||
|
cutSelection: 剪切
|
||||||
|
copySelection: 复制
|
||||||
|
clearSelection: 取消选择
|
||||||
|
pipette: 吸取器
|
||||||
|
switchLayers: 切换层
|
||||||
|
buildingPlacement:
|
||||||
|
cycleBuildingVariants: 按 <key> 键以选择设施的变型体。
|
||||||
|
hotkeyLabel: "快捷键: <key>"
|
||||||
|
infoTexts:
|
||||||
|
speed: 速率
|
||||||
|
range: 范围
|
||||||
|
storage: 容量
|
||||||
|
oneItemPerSecond: 1个/秒
|
||||||
|
itemsPerSecond: <x>个/秒
|
||||||
|
itemsPerSecondDouble: (2倍)
|
||||||
|
tiles: <x>格
|
||||||
|
levelCompleteNotification:
|
||||||
|
levelTitle: 第<level>关
|
||||||
|
completed: 完成
|
||||||
|
unlockText: 解锁<reward>!
|
||||||
|
buttonNextLevel: 下一关
|
||||||
|
notifications:
|
||||||
|
newUpgrade: 有新内容更新啦!
|
||||||
|
gameSaved: 游戏已保存。
|
||||||
|
freeplayLevelComplete: 第 <level>关 完成了!
|
||||||
|
shop:
|
||||||
|
title: 升级
|
||||||
|
buttonUnlock: 升级
|
||||||
|
tier: <x>级
|
||||||
|
maximumLevel: 最高级(<currentMult>倍速率)
|
||||||
|
statistics:
|
||||||
|
title: 统计信息
|
||||||
|
dataSources:
|
||||||
|
stored:
|
||||||
|
title: 已存储
|
||||||
|
description: 所有图形已存储于中心。
|
||||||
|
produced:
|
||||||
|
title: 生产
|
||||||
|
description: 所有图形已在工厂内生产,包括中间产物。
|
||||||
|
delivered:
|
||||||
|
title: 交付
|
||||||
|
description: 图形已交付到中心基地。
|
||||||
|
noShapesProduced: 您还没有生产任何图形。
|
||||||
|
shapesDisplayUnits:
|
||||||
|
second: <shapes> / 秒
|
||||||
|
minute: <shapes> / 分
|
||||||
|
hour: <shapes> / 小时
|
||||||
|
settingsMenu:
|
||||||
|
playtime: 游戏时间
|
||||||
|
buildingsPlaced: 设施数量
|
||||||
|
beltsPlaced: 传送带数量
|
||||||
|
tutorialHints:
|
||||||
|
title: 需要帮助?
|
||||||
|
showHint: 显示帮助
|
||||||
|
hideHint: 关闭
|
||||||
|
blueprintPlacer:
|
||||||
|
cost: 成本
|
||||||
|
waypoints:
|
||||||
|
waypoints: 地图标记
|
||||||
|
hub: 中心
|
||||||
|
description: 左键点击地图标记以跳转到该处,右键点击可删除地图标记。<br><br>按 <keybinding>
|
||||||
|
在当前地点创建地图标记,或者在选定位置上<strong>右键</strong>创建地图标记。
|
||||||
|
creationSuccessNotification: 成功创建地图标记。
|
||||||
|
interactiveTutorial:
|
||||||
|
title: 新手教程
|
||||||
|
hints:
|
||||||
|
1_1_extractor: 在<strong>圆形</strong>上放置一个<strong>开采器</strong>来获取圆形!<br><br>提示:<strong>按下鼠标左键</strong>选中<strong>开采器</strong>
|
||||||
|
1_2_conveyor: 用<strong>传送带</strong>将您的开采器连接到中心基地上!<br><br>提示:选中<strong>传送带</strong>后<strong>按下鼠标左键可拖动</strong>布置传送带!
|
||||||
|
1_3_expand:
|
||||||
|
您可以放置更多的<strong>开采器</strong>和<strong>传送带</strong>来更有效率地完成关卡目标。<br><br>
|
||||||
|
提示:按住 <strong>SHIFT</strong>
|
||||||
|
键可放置多个<strong>开采器</strong>,注意用<strong>R</strong>
|
||||||
|
键可旋转<strong>开采器</strong>的出口方向,确保开采的图形可以顺利传送。
|
||||||
|
2_1_place_cutter: 现在放置一个<strong>切割器</strong>,这个设施可把<strong>圆形</strong>切成两半!<br><br>注意:无论如何放置,切割机总是<strong>从上到下</strong>切割。
|
||||||
|
2_2_place_trash:
|
||||||
|
使用切割机后产生的废弃图形会导致<strong>堵塞</strong>。<br><br>注意使用<strong>垃圾桶</strong>清除当前
|
||||||
|
(!) 不需要的废物。
|
||||||
|
2_3_more_cutters: 干的好!现在放置<strong>2个以上的切割机</strong>来加快当前缓慢的过程!<br><br>提示:用<strong>快捷键0-9</strong>可以快速选择各项设施!
|
||||||
|
3_1_rectangles:
|
||||||
|
现在让我们开采一些矩形!找到<strong>矩形地带</strong>并<strong>放置4个开采器</strong>并将它们用<strong>传送带</strong>连接到中心基地。<br><br>
|
||||||
|
提示:选中<strong>传送带</strong>后按住<strong>SHIFT键</strong>可快速准确地规划<strong>传送带路线!</strong>
|
||||||
|
21_1_place_quad_painter: 放置<strong>四口上色器</strong>并且获取一些<strong>圆形</strong>,<strong>白色</strong>和<strong>红色</strong>!
|
||||||
|
21_2_switch_to_wires: 按 <strong>E</strong> 键选择<strong>电线层</strong>!<br><br>
|
||||||
|
然后用导线连接上色器的<strong>四个输入口</strong>!
|
||||||
|
21_3_place_button: 很好!现在放置一个<strong>开关</strong>并连接导线!
|
||||||
|
21_4_press_button: 按下<strong>开关</strong>来<strong>产生正信号</strong>以激活<strong>上色器</strong>。<br><br>注:您不用连上所有的输入口!试着只接两个。
|
||||||
|
colors:
|
||||||
|
red: 红色
|
||||||
|
green: 绿色
|
||||||
|
blue: 蓝色
|
||||||
|
yellow: 黄色
|
||||||
|
purple: 紫色
|
||||||
|
cyan: 青色
|
||||||
|
white: 白色
|
||||||
|
uncolored: 无色
|
||||||
|
black: 黑色
|
||||||
|
shapeViewer:
|
||||||
|
title: 层
|
||||||
|
empty: 空
|
||||||
|
copyKey: 复制短代码
|
||||||
|
connectedMiners:
|
||||||
|
one_miner: 1 个开采器
|
||||||
|
n_miners: <amount> 个开采器
|
||||||
|
limited_items: 限制在 <max_throughput>
|
||||||
|
watermark:
|
||||||
|
title: 试玩版
|
||||||
|
desc: 点击这里了解完整版内容
|
||||||
|
get_on_steam: 在Steam商城购买
|
||||||
|
standaloneAdvantages:
|
||||||
|
title: 购买完整版!
|
||||||
|
no_thanks: 不需要,谢谢
|
||||||
|
points:
|
||||||
|
levels:
|
||||||
|
title: 12 个全新关卡!
|
||||||
|
desc: 总共 26 个不同关卡!
|
||||||
|
buildings:
|
||||||
|
title: 18 个全新设施!
|
||||||
|
desc: 呈现完全体的全自动工厂!
|
||||||
|
upgrades:
|
||||||
|
title: 20个等级升级
|
||||||
|
desc: 试玩版只有5个等级!
|
||||||
|
markers:
|
||||||
|
title: 无限数量地图标记
|
||||||
|
desc: 地图再大,不会迷路!
|
||||||
|
wires:
|
||||||
|
title: 电线更新包
|
||||||
|
desc: 发挥创造力的全新维度!
|
||||||
|
darkmode:
|
||||||
|
title: 暗色模式
|
||||||
|
desc: 优雅且护眼的配色!
|
||||||
|
support:
|
||||||
|
title: 支持作者
|
||||||
|
desc: 我使用闲暇时间开发游戏!
|
||||||
|
achievements:
|
||||||
|
title: 成就
|
||||||
|
desc: 挑战全成就解锁!
|
||||||
|
shopUpgrades:
|
||||||
|
belt:
|
||||||
|
name: 传送、分发、隧道
|
||||||
|
description: 效率 <currentMult>倍 → <newMult>倍
|
||||||
|
miner:
|
||||||
|
name: 开采
|
||||||
|
description: 效率 <currentMult>倍 → <newMult>倍
|
||||||
|
processors:
|
||||||
|
name: 切割、旋转、堆叠
|
||||||
|
description: 效率 <currentMult>倍 → <newMult>倍
|
||||||
|
painting:
|
||||||
|
name: 混色、上色
|
||||||
|
description: 效率 <currentMult>倍 → <newMult>倍
|
||||||
|
buildings:
|
||||||
|
belt:
|
||||||
|
default:
|
||||||
|
name: 传送带
|
||||||
|
description: 运送物品,选中后<strong>按住鼠标并拖动</strong>可一次性放置多个传送带。
|
||||||
|
miner:
|
||||||
|
default:
|
||||||
|
name: 开采器
|
||||||
|
description: 放置在<strong>图形</strong>或者<strong>颜色</strong>上进行开采。
|
||||||
|
chainable:
|
||||||
|
name: 开采器(链式)
|
||||||
|
description: 放置在<strong>图形</strong>或者<strong>颜色</strong>上进行开采。它们可以被链接在一起。
|
||||||
|
underground_belt:
|
||||||
|
default:
|
||||||
|
name: 隧道
|
||||||
|
description: 可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
||||||
|
tier2:
|
||||||
|
name: 二级隧道
|
||||||
|
description: 可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
||||||
|
cutter:
|
||||||
|
default:
|
||||||
|
name: 切割机
|
||||||
|
description: 始终将<strong>图形</strong>从上到下切开并分别输出。<strong>如果您只需要其中一半的图形,使用<strong>垃圾桶</strong>清除另一半图形,否则切割机会停止工作!</strong>
|
||||||
|
quad:
|
||||||
|
name: 切割机(四向)
|
||||||
|
description: 将输入的图形切成四块。<strong>如果您只需要其中一块图形,使用<strong>垃圾桶</strong>清除其他图形,否则切割机会停止工作!</strong>
|
||||||
|
rotater:
|
||||||
|
default:
|
||||||
|
name: 旋转机
|
||||||
|
description: 将<strong>图形</strong>顺时针旋转90度。
|
||||||
|
ccw:
|
||||||
|
name: 旋转机(逆时针)
|
||||||
|
description: 将<strong>图形</strong>逆时针旋转90度。
|
||||||
|
rotate180:
|
||||||
|
name: 旋转机 (180度)
|
||||||
|
description: 将<strong>图形</strong>旋转180度。
|
||||||
|
stacker:
|
||||||
|
default:
|
||||||
|
name: 堆叠机
|
||||||
|
description: 将输入的<strong>图形</strong>在同一层内组合在一起。如果不能被直接组合,则右边输入<strong>图形</strong>会堆叠在左边输入<strong>图形</strong>上面。
|
||||||
|
mixer:
|
||||||
|
default:
|
||||||
|
name: 混色器
|
||||||
|
description: 用叠加混色法将两个<strong>颜色</strong>混合。
|
||||||
|
painter:
|
||||||
|
default:
|
||||||
|
name: 上色器
|
||||||
|
description: 将整个<strong>图形</strong>涂上输入的<strong>颜色</strong>。
|
||||||
|
double:
|
||||||
|
name: 上色器(双面)
|
||||||
|
description: 使用顶部输入的<strong>颜色</strong>为左侧输入的<strong>图形</strong>上色。
|
||||||
|
quad:
|
||||||
|
name: 上色器(四口)
|
||||||
|
description: 能够为<strong>图形</strong>的四个象限单独上色。记住只有通过电线层上带有<strong>正信号</strong>的插槽才可以上色!
|
||||||
|
mirrored:
|
||||||
|
name: 上色器 (镜像)
|
||||||
|
description: 将整个<strong>图形</strong>涂上输入的颜色。
|
||||||
|
trash:
|
||||||
|
default:
|
||||||
|
name: 垃圾桶
|
||||||
|
description: 可以从所有四个方向上输入物品并永远清除它们。
|
||||||
|
hub:
|
||||||
|
deliver: 交付
|
||||||
|
toUnlock: 解锁
|
||||||
|
levelShortcut: LVL
|
||||||
|
endOfDemo: 试玩版结束
|
||||||
|
wire:
|
||||||
|
default:
|
||||||
|
name: 电线
|
||||||
|
description: 可用来传输<strong>信号<strong>,信号可以是物品,颜色或者开关值(0或1)。
|
||||||
|
不同颜色的<strong>电线</strong>不会互相连接
|
||||||
|
second:
|
||||||
|
name: 电线
|
||||||
|
description: 可用来传输<strong>信号<strong>,信号可以是物品,颜色或者开关值(0或1)。
|
||||||
|
不同颜色的<strong>电线</strong>不会互相连接
|
||||||
|
balancer:
|
||||||
|
default:
|
||||||
|
name: 平衡器
|
||||||
|
description: 多功能的设施:可将所有输入均匀地分配到所有输出上。
|
||||||
|
merger:
|
||||||
|
name: 合并器 (小型)
|
||||||
|
description: 可将两条传送带合并为一条。
|
||||||
|
merger-inverse:
|
||||||
|
name: 合并器 (小型)
|
||||||
|
description: 可将两条传送带合并为一条。
|
||||||
|
splitter:
|
||||||
|
name: 分离器 (小型)
|
||||||
|
description: 可将一条传送带分成为两条。
|
||||||
|
splitter-inverse:
|
||||||
|
name: 分离器 (小型)
|
||||||
|
description: 可将一条传送带分成为两条。
|
||||||
|
storage:
|
||||||
|
default:
|
||||||
|
name: 存储器
|
||||||
|
description: 储存多余的物品,直到储满。 优先处理左边的输出,并可以用作溢出门。
|
||||||
|
wire_tunnel:
|
||||||
|
default:
|
||||||
|
name: 交叉电线
|
||||||
|
description: 使两根<strong>电线</strong>交叉而不会连接起来。
|
||||||
|
constant_signal:
|
||||||
|
default:
|
||||||
|
name: 恒定信号
|
||||||
|
description: 发出固定信号,可以是<strong>图形</strong>、<strong>颜色</strong>、<strong>开关值(1 /
|
||||||
|
0)</strong>。
|
||||||
|
lever:
|
||||||
|
default:
|
||||||
|
name: 开关
|
||||||
|
description: 可以在电线层上发出<strong>开关值(1 / 0)</strong>信号,以达到控制部件的作用,比如可以用来控制物品过滤器。
|
||||||
|
logic_gate:
|
||||||
|
default:
|
||||||
|
name: 与门
|
||||||
|
description: 如果输入<strong>都是</strong>正信号,则发出<strong>开(1)</strong>信号。(正信号:图形,颜色,开(1)信号)
|
||||||
|
not:
|
||||||
|
name: 非门
|
||||||
|
description: 如果输入<strong>不是</strong>正信号,则发出<strong>开(1)</strong>信号。(正信号:图形,颜色,开(1)信号)
|
||||||
|
xor:
|
||||||
|
name: 异或门
|
||||||
|
description: 如果输入<strong>只有一个</strong>正信号,则发出<strong>开(1)</strong>信号。(正信号:图形,颜色,开(1)信号)
|
||||||
|
or:
|
||||||
|
name: 或门
|
||||||
|
description: 如果输入<strong>有一个</strong>是正信号,则发出<strong>开(1)</strong>信号。(正信号:图形,颜色,开(1)信号)
|
||||||
|
transistor:
|
||||||
|
default:
|
||||||
|
name: 晶体管
|
||||||
|
description: 如果侧边输入正信号,输入可以通过并转发。(正信号:图形,颜色,开(1)信号)
|
||||||
|
mirrored:
|
||||||
|
name: 晶体管
|
||||||
|
description: 如果侧边输入正信号,输入可以通过并转发。(正信号:图形,颜色,开(1)信号)
|
||||||
|
filter:
|
||||||
|
default:
|
||||||
|
name: 过滤器
|
||||||
|
description: 在顶侧输出和<strong>信号</strong>匹配的内容,在右侧输出不匹配的内容。如果是开关量的话,开(1)信号从顶侧输出,关(0)信号从右侧输出。
|
||||||
|
display:
|
||||||
|
default:
|
||||||
|
name: 显示器
|
||||||
|
description: 在显示器上显示连接的<strong>信号</strong>(信号可以是:图形、颜色、开关值)。
|
||||||
|
reader:
|
||||||
|
default:
|
||||||
|
name: 传送带读取器
|
||||||
|
description: 可以读取<strong>传送带</strong>平均<strong>吞吐量</strong>。输出最后在<strong>电线层</strong>上读取的物品(一旦解锁。)
|
||||||
|
analyzer:
|
||||||
|
default:
|
||||||
|
name: 图形分析器
|
||||||
|
description: 分析<strong>图形</strong>最底层的右上象限并返回其<strong>图形</strong>和<strong>颜色</strong>。
|
||||||
|
comparator:
|
||||||
|
default:
|
||||||
|
name: 比较器
|
||||||
|
description: 如果输入的两个<strong>信号</strong>一样将输出开(1)信号,可以比较图形,颜色,和开关值。
|
||||||
|
virtual_processor:
|
||||||
|
default:
|
||||||
|
name: 虚拟切割机
|
||||||
|
description: 模拟将<strong>图形</strong>切割成两半。
|
||||||
|
rotater:
|
||||||
|
name: 模拟旋转机
|
||||||
|
description: 模拟顺时针旋转<strong>图形</strong>。
|
||||||
|
unstacker:
|
||||||
|
name: 模拟拆分器
|
||||||
|
description: 模拟提取最上层<strong>图形</strong>从右侧输出,提取其余的<strong>图形</strong>从左侧输出。
|
||||||
|
stacker:
|
||||||
|
name: 模拟堆叠机
|
||||||
|
description: 模拟将右侧<strong>图形</strong>叠在左侧<strong>图形</strong>上。
|
||||||
|
painter:
|
||||||
|
name: 模拟上色器
|
||||||
|
description: 模拟使用右侧输入的<strong>颜色</strong>给底部输入的<strong>图形</strong>上色
|
||||||
|
item_producer:
|
||||||
|
default:
|
||||||
|
name: 物品生成器
|
||||||
|
description: 仅在沙盒模式下可用,在常规层上输出<strong>电线层</strong>给定的<strong>信号</strong>。
|
||||||
|
storyRewards:
|
||||||
|
reward_cutter_and_trash:
|
||||||
|
title: 切割图形
|
||||||
|
desc: 恭喜!您解锁了<strong>切割机</strong>,不管如何放置,它只会从上到下切开<strong>图形</strong>!
|
||||||
|
<br>注意一定要处理掉切割后废弃的<strong>图形</strong>,不然它会<strong>阻塞</strong>传送带,
|
||||||
|
<br>使用<strong>垃圾桶</strong>,它会清除所有放进去的图形!
|
||||||
|
reward_rotater:
|
||||||
|
title: 旋转
|
||||||
|
desc: 恭喜!您解锁了<strong>旋转机</strong>。它会顺时针将输入的<strong>图形旋转90度</strong>。
|
||||||
|
reward_painter:
|
||||||
|
title: 上色
|
||||||
|
desc:
|
||||||
|
恭喜!您解锁了<strong>上色器</strong>。开采一些颜色 (就像您开采图形一样),将其在上色器中与图形结合来将图形上色!
|
||||||
|
<br>注意:如果您不幸患有色盲,可以在设置中启用<strong>色盲模式</strong>
|
||||||
|
reward_mixer:
|
||||||
|
title: 混合颜色
|
||||||
|
desc: 恭喜!您解锁了<strong>混色器</strong>。它使用<strong>叠加混色法</strong>将两种颜色混合起来。
|
||||||
|
reward_stacker:
|
||||||
|
title: 堆叠
|
||||||
|
desc: 恭喜!您解锁了<strong>堆叠机</strong>。它会将将输入的<strong>图形</strong>在同一层内组合在一起。
|
||||||
|
<br>如果不能被直接组合,则右边输入<strong>图形</strong>会堆叠在左边输入<strong>图形</strong>上面。
|
||||||
|
reward_splitter:
|
||||||
|
title: 分离器(小型)
|
||||||
|
desc: 您已经解锁了<strong>平衡器</strong>的变体<strong>分离器</strong>,它会把输入的东西一分为二!
|
||||||
|
reward_tunnel:
|
||||||
|
title: 隧道
|
||||||
|
desc: 恭喜!您解锁了<strong>隧道</strong>。它可放置在<strong>传送带</strong>或<strong>设施</strong>下方以运送物品。
|
||||||
|
reward_rotater_ccw:
|
||||||
|
title: 逆时针旋转
|
||||||
|
desc:
|
||||||
|
恭喜!您解锁了<strong>旋转机</strong>的<strong>逆时针</strong>变体。它可以逆时针旋转<strong>图形</strong>。
|
||||||
|
<br>选择<strong>旋转机</strong>然后按"T"键来选取这个变体。
|
||||||
|
reward_miner_chainable:
|
||||||
|
title: 链式开采器
|
||||||
|
desc:
|
||||||
|
您已经解锁了<strong>链式开采器</strong>!它能<strong>转发资源</strong>给其他的开采器,这样您就能更有效率的开采各类资源了!<br><br>
|
||||||
|
注意:新的开采器已替换了工具栏里旧的开采器!
|
||||||
|
reward_underground_belt_tier_2:
|
||||||
|
title: 二级隧道
|
||||||
|
desc: 恭喜!您解锁了<strong>二级隧道</strong>。这是隧道的一个变体。二级隧道有<strong>更长的传输距离</strong>。您还可以混用不同的隧道变体!
|
||||||
|
reward_cutter_quad:
|
||||||
|
title: 四向切割机
|
||||||
|
desc: 恭喜!您解锁了<strong>切割机</strong>的<strong>四向</strong>变体。它可以将输入的<strong>图形</strong>切成四块而不只是左右两块!
|
||||||
|
reward_painter_double:
|
||||||
|
title: 双面上色器
|
||||||
|
desc: 恭喜!您解锁了<strong>上色器</strong>的<strong>双面</strong>变体。它可以同时为两个图形上色,但每次只消耗一份颜色!
|
||||||
|
reward_storage:
|
||||||
|
title: 存储器
|
||||||
|
desc: 您已经解锁了<strong>存储器</strong>,它能存满指定容量的物品!
|
||||||
|
<br>它<strong>优先从左边</strong>输出,这样您就可以用它做一个<strong>溢流门</strong>了!
|
||||||
|
reward_freeplay:
|
||||||
|
title: 自由模式
|
||||||
|
desc:
|
||||||
|
成功了!您解锁了<strong>自由模式</strong>!挑战升级!这意味着现在将<strong>随机</strong>生成图形!
|
||||||
|
从现在起,中心基地最为需要的是<strong>产量</strong>,我强烈建议您去制造一台能够自动交付所需图形的机器!<br><br>
|
||||||
|
基地会在<strong>电线层</strong>输出需要的图形,您需要去分析图形并在此基础上自动配置您的工厂。
|
||||||
|
reward_blueprints:
|
||||||
|
title: 蓝图
|
||||||
|
desc:
|
||||||
|
您现在可以<strong>复制粘贴</strong>您的工厂的一部分了!按住 CTRL键并拖动鼠标来选择一块区域,然后按C键复制。
|
||||||
|
<br><br>粘贴并<strong>不是免费的</strong>,您需要制造<strong>蓝图图形</strong>来负担。蓝图图形是您刚刚交付的图形。
|
||||||
|
no_reward:
|
||||||
|
title: 下一关
|
||||||
|
desc: 这一关没有奖励,但是下一关有! <br><br>
|
||||||
|
注意:最高明的规划师都不会破坏原有的工厂设施,您生产过的<strong>所有图形</strong>都会被用于<strong>解锁升级</strong>。
|
||||||
|
no_reward_freeplay:
|
||||||
|
title: 下一关
|
||||||
|
desc: 恭喜您!另外,我们已经计划在完整版中加入更多内容!
|
||||||
|
reward_balancer:
|
||||||
|
title: 平衡器
|
||||||
|
desc: 恭喜!您解锁了多功能<strong>平衡器</strong>,它能够<strong>分割和合并</strong>多个传送带的资源,可以用来建造更大的工厂!
|
||||||
|
reward_merger:
|
||||||
|
title: 合并器(小型)
|
||||||
|
desc: 恭喜!您解锁了<strong>平衡器</strong>的变体<strong>合并器</strong>,它能合并两个输入到同一个传送带上!
|
||||||
|
reward_belt_reader:
|
||||||
|
title: 传送带读取器
|
||||||
|
desc: 恭喜!您解锁了<strong>传送带读取器</strong>!它能够测量传送带上的生产率。
|
||||||
|
<br><br>等您解锁了<strong>电线层</strong>后,它将会极其有用!
|
||||||
|
reward_rotater_180:
|
||||||
|
title: 旋转机(180度)
|
||||||
|
desc: 恭喜!您解锁了<strong>旋转器(180度)</strong>!它能帮您把一个图形旋转180度(Surprise! :D)
|
||||||
|
reward_display:
|
||||||
|
title: 显示器
|
||||||
|
desc: 恭喜!您已经解锁了<strong>显示器</strong>,它可以显示一个在<strong>电线层上连接的信号</strong>!
|
||||||
|
<br>注意:您注意到<strong>传送读取器</strong>和<strong>存储器</strong>输出的他们最后读取的物品了吗?试着在显示屏上展示一下!"
|
||||||
|
reward_constant_signal:
|
||||||
|
title: 恒定信号
|
||||||
|
desc:
|
||||||
|
恭喜!您解锁了生成于电线层之上的<strong>恒定信号</strong>,把它连接到<strong>过滤器</strong>时非常有用。
|
||||||
|
<br>比如,它能发出图形、颜色、开关值(1 / 0)的固定信号。
|
||||||
|
reward_logic_gates:
|
||||||
|
title: 逻辑门
|
||||||
|
desc: 您解锁了<strong>逻辑门</strong>!它们是个好东西!<br>
|
||||||
|
您可以用它们来进行'与,或,非,异或'操作。<br><br>作为奖励,我还给您解锁了<strong>晶体管</strong>!
|
||||||
|
reward_virtual_processing:
|
||||||
|
title: 模拟处理器
|
||||||
|
desc: 我刚刚给了一大堆新设施,让您可以<strong>模拟形状的处理过程</strong>!<br>
|
||||||
|
您现在可以在电线层上模拟切割机,旋转机,堆叠机和其他机器!<br> 有了这些,您可以选择下面三个方向来继续游戏:<br>
|
||||||
|
-建立一个<strong>自动化机器</strong>以生产出任何中心基地需要图形(建议一试!)。<br>
|
||||||
|
-用<strong>电线层</strong>做些酷炫的东西。<br> -继续正常游戏。<br> 放飞想象,尽情创造!
|
||||||
|
reward_wires_painter_and_levers:
|
||||||
|
title: 电线 & 四口上色器
|
||||||
|
desc: 恭喜!您解锁了<strong>电线层</strong>:它是正常层之上的一个层,它将带来了许多新的机制!<br><br>
|
||||||
|
首先我解锁了您的<strong>四口上色器</strong>,按<strong>E</strong>键切换到电线层,然后连接您想要染色的槽,用开关来控制开启。<br><br>
|
||||||
|
<strong>提示</strong>:可在设置中打开电线层教程!"
|
||||||
|
reward_filter:
|
||||||
|
title: 物品过滤器
|
||||||
|
desc:
|
||||||
|
恭喜!您解锁了<strong>物品过滤器</strong>!它会根据在电线层上输入的信号决定是从上面还是右边输出物品。<br><br>
|
||||||
|
您也可以输入开关值(1 / 0)信号来激活或者禁用它。
|
||||||
|
reward_demo_end:
|
||||||
|
title: 试玩结束
|
||||||
|
desc: 恭喜!您已经通关了试玩版本! <br>更多挑战,请至Steam商城购买完整版!谢谢支持!
|
||||||
|
settings:
|
||||||
|
title: 设置
|
||||||
|
categories:
|
||||||
|
general: 通用
|
||||||
|
userInterface: 用户界面
|
||||||
|
advanced: 高级
|
||||||
|
performance: 性能
|
||||||
|
versionBadges:
|
||||||
|
dev: 开发版本
|
||||||
|
staging: 预览版本
|
||||||
|
prod: 正式版本
|
||||||
|
buildDate: 与<at-date>编译
|
||||||
|
tickrateHz: <amount> 赫兹
|
||||||
|
labels:
|
||||||
|
uiScale:
|
||||||
|
title: 用户界面大小
|
||||||
|
description: 改变用户界面大小。用户界面会随着屏幕分辨率缩放,这个设置决定缩放比例。
|
||||||
|
scales:
|
||||||
|
super_small: 最小
|
||||||
|
small: 较小
|
||||||
|
regular: 正常
|
||||||
|
large: 较大
|
||||||
|
huge: 最大
|
||||||
|
scrollWheelSensitivity:
|
||||||
|
title: 缩放灵敏度
|
||||||
|
description: 改变屏幕缩放灵敏度(用鼠标滚轮或者触控板控制缩放)。
|
||||||
|
sensitivity:
|
||||||
|
super_slow: 最低
|
||||||
|
slow: 较低
|
||||||
|
regular: 正常
|
||||||
|
fast: 较高
|
||||||
|
super_fast: 最高
|
||||||
|
language:
|
||||||
|
title: 语言
|
||||||
|
description: 改变语言。官方中文版已更新,欢迎玩家继续提供更好的翻译意见。
|
||||||
|
fullscreen:
|
||||||
|
title: 全屏
|
||||||
|
description: 全屏可获得更好的游戏体验。仅在完整版中可用。
|
||||||
|
soundsMuted:
|
||||||
|
title: 关闭音效
|
||||||
|
description: 关闭所有音效。
|
||||||
|
musicMuted:
|
||||||
|
title: 关闭音乐
|
||||||
|
description: 关闭所有音乐。
|
||||||
|
theme:
|
||||||
|
title: 界面主题
|
||||||
|
description: 选择界面主题(深色或浅色)。
|
||||||
|
themes:
|
||||||
|
dark: 深色
|
||||||
|
light: 浅色
|
||||||
|
refreshRate:
|
||||||
|
title: 模拟频率、刷新频率
|
||||||
|
description: 如果您的显示器刷新频率是
|
||||||
|
144赫兹,请在这里更改刷新频率,这样游戏可以正确地根据您的屏幕进行模拟。但是如果您的电脑性能不佳,提高刷新频率可能降低帧数。
|
||||||
|
alwaysMultiplace:
|
||||||
|
title: 多重放置
|
||||||
|
description: 开启这个选项之后放下设施将不会取消设施选择。等同于一直按下 SHIFT 键。
|
||||||
|
offerHints:
|
||||||
|
title: 提示与教程
|
||||||
|
description: 是否显示提示、教程以及一些其他的帮助理解游戏的用户界面元素。建议新手玩家开启。
|
||||||
|
movementSpeed:
|
||||||
|
title: 移动速度
|
||||||
|
description: 改变摄像头的移动速度。
|
||||||
|
speeds:
|
||||||
|
super_slow: 最慢
|
||||||
|
slow: 较慢
|
||||||
|
regular: 正常
|
||||||
|
fast: 较快
|
||||||
|
super_fast: 非常快
|
||||||
|
extremely_fast: 最快
|
||||||
|
enableTunnelSmartplace:
|
||||||
|
title: 智能隧道放置
|
||||||
|
description: 启用后,放置隧道时会将多余的传送带移除。 此外,拖动隧道可以快速铺设隧道,以及移除不必要的隧道。
|
||||||
|
vignette:
|
||||||
|
title: 晕映
|
||||||
|
description: 启用晕映功能,可将屏幕角落里的颜色变深,更容易阅读文本。
|
||||||
|
autosaveInterval:
|
||||||
|
title: 自动存档间隔
|
||||||
|
description: 在这里控制您的游戏多长时间自动存档一次,你也可以完全关闭这个功能。建议打开。
|
||||||
|
intervals:
|
||||||
|
one_minute: 1分钟
|
||||||
|
two_minutes: 2分钟
|
||||||
|
five_minutes: 5分钟
|
||||||
|
ten_minutes: 10分钟
|
||||||
|
twenty_minutes: 20分钟
|
||||||
|
disabled: 关闭
|
||||||
|
compactBuildingInfo:
|
||||||
|
title: 精简设施信息
|
||||||
|
description: 缩小设施信息展示框。如果打开,放置设施时将不再显示说明和图片,只显示建造速度或其他数据。
|
||||||
|
disableCutDeleteWarnings:
|
||||||
|
title: 关闭剪切/删除警告
|
||||||
|
description: 如果打开,将不再在剪切或者删除100+实体时显示警告信息。
|
||||||
|
enableColorBlindHelper:
|
||||||
|
title: 色盲模式
|
||||||
|
description: 提供多种工具,帮助色盲玩家可正常进行游戏。
|
||||||
|
rotationByBuilding:
|
||||||
|
title: 记忆设施方向
|
||||||
|
description: 每一类设施都会记住各自上一次的旋转方向。如果您经常在不同设施类型之间切换,这个设置会让游戏操控更加便捷。
|
||||||
|
soundVolume:
|
||||||
|
title: 音效音量
|
||||||
|
description: 设置音效的音量
|
||||||
|
musicVolume:
|
||||||
|
title: 音乐音量
|
||||||
|
description: 设置音乐的音量
|
||||||
|
lowQualityMapResources:
|
||||||
|
title: 低质量地图资源
|
||||||
|
description: 放大时简化地图上资源的渲染以提高性能。开启甚至会让画面看起来更干净,低配置电脑玩家建议开启!
|
||||||
|
disableTileGrid:
|
||||||
|
title: 禁用网格
|
||||||
|
description: 禁用平铺网格有助于提高性能。这也让游戏画面看起来更干净!
|
||||||
|
clearCursorOnDeleteWhilePlacing:
|
||||||
|
title: 右键取消
|
||||||
|
description: 默认启用。在选择要放置的设施时,单击鼠标右键即可取消。如果禁用,则可以通过在放置设施时单击鼠标右键来删除设施。
|
||||||
|
lowQualityTextures:
|
||||||
|
title: 低质量纹理
|
||||||
|
description: 使用低质量纹理提高游戏性能。但是这样游戏会以低画面质量运行!
|
||||||
|
displayChunkBorders:
|
||||||
|
title: 显示大块的边框
|
||||||
|
description: 游戏将每一个大块分成16*16的小块,如果启用将会显示每个大块的边框。
|
||||||
|
pickMinerOnPatch:
|
||||||
|
title: 在资源块上选择开采器
|
||||||
|
description: 默认开启,当在资源块上使用选取器时会选择开采器。
|
||||||
|
simplifiedBelts:
|
||||||
|
title: 简单的传送带
|
||||||
|
description: 除非鼠标放在传送带上,不然不会渲染传送带上的物品。启用可提升游戏性能。但除非特别需要性能,否则不推荐启用。
|
||||||
|
enableMousePan:
|
||||||
|
title: 鼠标平移屏幕
|
||||||
|
description: 在鼠标滑到屏幕边缘时可以移动地图。移动速度取决于移动速度设置。
|
||||||
|
zoomToCursor:
|
||||||
|
title: 鼠标位置缩放
|
||||||
|
description: 启用后在鼠标所在位置进行屏幕缩放,否则在屏幕中间进行缩放。
|
||||||
|
mapResourcesScale:
|
||||||
|
title: 地图资源图形尺寸
|
||||||
|
description: 控制地图总览时图形的尺寸(指缩小视野时)。
|
||||||
|
rangeSliderPercentage: <amount> %
|
||||||
|
keybindings:
|
||||||
|
title: 按键设定
|
||||||
|
hint: 提示:使用 CTRL、SHIFT、ALT!这些键在放置设施时有不同的效果。
|
||||||
|
resetKeybindings: 重置按键设定
|
||||||
|
categoryLabels:
|
||||||
|
general: 通用
|
||||||
|
ingame: 游戏
|
||||||
|
navigation: 视角
|
||||||
|
placement: 放置
|
||||||
|
massSelect: 批量选择
|
||||||
|
buildings: 设施快捷键
|
||||||
|
placementModifiers: 放置设施修饰键
|
||||||
|
mappings:
|
||||||
|
confirm: 确认
|
||||||
|
back: 返回
|
||||||
|
mapMoveUp: 上
|
||||||
|
mapMoveRight: 右
|
||||||
|
mapMoveDown: 下
|
||||||
|
mapMoveLeft: 左
|
||||||
|
centerMap: 回到中心基地
|
||||||
|
mapZoomIn: 放大
|
||||||
|
mapZoomOut: 缩小
|
||||||
|
createMarker: 创建地图标记
|
||||||
|
menuOpenShop: 升级菜单
|
||||||
|
menuOpenStats: 统计菜单
|
||||||
|
toggleHud: 开关可视化界面
|
||||||
|
toggleFPSInfo: 开关帧数与调试信息
|
||||||
|
belt: 传送带
|
||||||
|
underground_belt: 隧道
|
||||||
|
miner: 开采器
|
||||||
|
cutter: 切割机
|
||||||
|
rotater: 旋转机
|
||||||
|
stacker: 堆叠机
|
||||||
|
mixer: 混色器
|
||||||
|
painter: 上色器
|
||||||
|
trash: 垃圾桶
|
||||||
|
rotateWhilePlacing: 顺时针旋转
|
||||||
|
rotateInverseModifier: "修饰键: 改为逆时针旋转"
|
||||||
|
cycleBuildingVariants: 切换所选择设施变体
|
||||||
|
confirmMassDelete: 确认批量删除
|
||||||
|
cycleBuildings: 切换所选择设施
|
||||||
|
massSelectStart: 开始批量选择
|
||||||
|
massSelectSelectMultiple: 选择多个区域
|
||||||
|
massSelectCopy: 复制区域
|
||||||
|
placementDisableAutoOrientation: 取消自动定向
|
||||||
|
placeMultiple: 继续放置
|
||||||
|
placeInverse: 反向自动传送带方向
|
||||||
|
pasteLastBlueprint: 粘贴上一张蓝图
|
||||||
|
massSelectCut: 剪切区域
|
||||||
|
exportScreenshot: 导出截图
|
||||||
|
mapMoveFaster: 快速移动
|
||||||
|
lockBeltDirection: 启用传送带规划
|
||||||
|
switchDirectionLockSide: 规划器:换边
|
||||||
|
pipette: 吸取器
|
||||||
|
menuClose: 关闭菜单
|
||||||
|
switchLayers: 切换层
|
||||||
|
wire: 电线
|
||||||
|
balancer: 平衡器
|
||||||
|
storage: 存储器
|
||||||
|
constant_signal: 恒定信号
|
||||||
|
logic_gate: 逻辑门
|
||||||
|
lever: 控制杆
|
||||||
|
filter: 过滤器
|
||||||
|
wire_tunnel: 电线隧道
|
||||||
|
display: 显示器
|
||||||
|
reader: 传送带读取器
|
||||||
|
virtual_processor: 模拟切割机
|
||||||
|
transistor: 晶体管
|
||||||
|
analyzer: 图形分析器
|
||||||
|
comparator: 比较器
|
||||||
|
item_producer: 物品生产器 (沙盒模式)
|
||||||
|
copyWireValue: 电线:复制指定电线上的值
|
||||||
|
rotateToUp: "向上旋转"
|
||||||
|
rotateToDown: "向下旋转"
|
||||||
|
rotateToRight: "向右旋转"
|
||||||
|
rotateToLeft: "向左旋转"
|
||||||
|
about:
|
||||||
|
title: 关于游戏
|
||||||
|
body: >-
|
||||||
|
本游戏由 <a href="https://github.com/tobspr" target="_blank">Tobias
|
||||||
|
Springer</a>(我)开发,并且已经开源。<br><br>
|
||||||
|
|
||||||
|
如果您想参与开发,请查看 <a href="<githublink>" target="_blank">shapez.io on github</a>。<br><br>
|
||||||
|
|
||||||
|
这个游戏的开发获得了 Discord 社区内热情玩家的巨大支持。诚挚邀请您加入我们的 <a href="<discordlink>" target="_blank">Discord 服务器</a>!<br><br>
|
||||||
|
|
||||||
|
本游戏的音乐由 <a href="https://soundcloud.com/pettersumelius" target="_blank">Peppsen</a> 制作——他是个很棒的伙伴。<br><br>
|
||||||
|
|
||||||
|
最后,我想感谢我最好的朋友 <a href="https://github.com/niklas-dahl" target="_blank">Niklas</a> ——如果没有他的《异星工厂》(factorio)带给我的体验和启发,《异形工厂》(shapez.io)将不会存在。
|
||||||
|
changelog:
|
||||||
|
title: 版本日志
|
||||||
|
demo:
|
||||||
|
features:
|
||||||
|
restoringGames: 恢复存档
|
||||||
|
importingGames: 导入存档
|
||||||
|
oneGameLimit: 最多一个存档
|
||||||
|
customizeKeybindings: 按键设定
|
||||||
|
exportingBase: 导出工厂截图
|
||||||
|
settingNotAvailable: 在试玩版中不可用。
|
||||||
|
tips:
|
||||||
|
- 基地接受所有创造后输入的图形!并不限于现有的图形!
|
||||||
|
- 让你的工厂尽量模块化,不然后期你会面对大麻烦!
|
||||||
|
- 不要让设施太过靠近基地,不然可能会乱成一锅粥!
|
||||||
|
- 如果堆叠不起作用,尝试切换输入的图形来重新组合。
|
||||||
|
- 您可以通过 <b>R</b> 键切换传送带规化方向。
|
||||||
|
- 按住 <b>CTRL</b> 键拖动传送带将始终保持它现有的传送方向。
|
||||||
|
- 只要所有设施等级一致,效率也将一致。
|
||||||
|
- 串行执行比并行执行更有效。
|
||||||
|
- 在后面的游戏中您会解锁更多设施的变种!
|
||||||
|
- 您可以使用<b>T</b>键切换不同的设施变种。
|
||||||
|
- 对称是关键!
|
||||||
|
- 您可以使用隧道构建不同层次的通道。
|
||||||
|
- 试着建造紧凑型工厂,它会给您带来好处的!
|
||||||
|
- 您可以按<b>T</b>来切换上色器的镜像变体。
|
||||||
|
- 正确的设施比例将使效率最大化。
|
||||||
|
- 在传送带和开采器等级一致时,5个开采器就可以占满一条传送带的运量。
|
||||||
|
- 别忘了隧道!
|
||||||
|
- 您不必为了充分发挥效率而平均分配物品。
|
||||||
|
- 按住<b>SHIFT</b>键将激活传送带路线规划,这样可以更有效地规划如何放置长距离的传送带。
|
||||||
|
- 切割机总是垂直切割图形,而不管图形方向如何。
|
||||||
|
- 还记得吗?混合三原色能够获得白色。
|
||||||
|
- 存储缓冲区优先处理左侧的输出。
|
||||||
|
- 您值得花时间来构建可重复的设计!
|
||||||
|
- 按住<b>CTRL</b>键能够放置多个设施。
|
||||||
|
- 您可以按住<b>ALT</b>来反向放置传送带的方向。
|
||||||
|
- 效率是关键!
|
||||||
|
- 离基地越远图形越复杂。
|
||||||
|
- 机器的速度是有限的,把它们分开可以获得最高的效率。
|
||||||
|
- 使用平衡器最大化您的效率。
|
||||||
|
- 有条不紊!尽量不要过多地穿过传送带。
|
||||||
|
- 凡事预则立!不预则废!
|
||||||
|
- 尽量不要删除旧的设施和生产线,您会需要他们生产的东西来升级设施并提高效率。
|
||||||
|
- 先给自己定一个小目标:自己完成20级!!不去看别人的攻略!
|
||||||
|
- 不要把问题复杂化,试着保持简单,您会成功的。
|
||||||
|
- 您可能需要在游戏的后期重复使用工厂。把您的工厂规划成可重复使用的。
|
||||||
|
- 有时,您可以在地图上直接找到您需要的图形,并不需要使用堆叠机去合成它。
|
||||||
|
- 风车图形不会自动产生
|
||||||
|
- 在切割前,给您的图形上色可以获得最高的效率。
|
||||||
|
- 模块化,可以使您提高效率。
|
||||||
|
- 记得做一个单独的蓝图工厂。
|
||||||
|
- 仔细看看调色器,您就会调色了。
|
||||||
|
- <b>CTRL+点击</b>能够选择一块区域。
|
||||||
|
- 设施建得离基地太近很可能会妨碍以后的工作。
|
||||||
|
- 使用升级列表中每个形状旁边的固定图标将其固定到屏幕上。
|
||||||
|
- 地图无限,放飞想象,尽情创造。
|
||||||
|
- 向您推荐Factorio!这是我最喜欢的游戏。向神作致敬!
|
||||||
|
- 四向切割机从右上开始进行顺时针切割!
|
||||||
|
- 在主界面您可以下载您的游戏存档文件!
|
||||||
|
- 这个游戏有很多有用的快捷键!一定要到快捷键页面看看。
|
||||||
|
- 这个游戏有很多设置可以提高游戏效率,请一定要了解一下!
|
||||||
|
- 中心基地有个指向它所在方向的小指南指针!
|
||||||
|
- 想清理传送带,可剪切那块区域然后将其在相同位置粘贴。
|
||||||
|
- 按F4显示FPS。
|
||||||
|
- 按两次F4显示您鼠标和镜头所在的块。
|
||||||
|
- 您可以点击被固定在屏幕左侧的图形来解除固定。
|
||||||
|
- 您可以点击被固定在屏幕左侧的图形来解除固定。
|
||||||