From 26e854f9831b8b11b464f4ce26d5a1f911f34b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Thu, 11 Jul 2024 11:13:57 +0300 Subject: [PATCH 01/11] [TS] Rename TextualGameState --- src/js/core/{textual_game_state.js => textual_game_state.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/js/core/{textual_game_state.js => textual_game_state.ts} (100%) diff --git a/src/js/core/textual_game_state.js b/src/js/core/textual_game_state.ts similarity index 100% rename from src/js/core/textual_game_state.js rename to src/js/core/textual_game_state.ts From ee11931c9fa17c660d8f44c8f6888a1ee3630a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Thu, 11 Jul 2024 12:52:03 +0300 Subject: [PATCH 02/11] Resolve @/jsx-runtime in webpack TypeScript compiler on its own will only inject this import, but not resolve it. The bundler is supposed to resolve the import, so just do that. --- gulp/webpack.config.js | 1 + gulp/webpack.production.config.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gulp/webpack.config.js b/gulp/webpack.config.js index cac0161b..79318745 100644 --- a/gulp/webpack.config.js +++ b/gulp/webpack.config.js @@ -88,6 +88,7 @@ export default { alias: { "global-compression": resolve("../src/js/core/lzstring.js"), "root": resolve("../src/js/"), + "@/jsx-runtime": resolve("../src/js/jsx-runtime.ts"), }, fullySpecified: false, extensions: [".ts", ".js", ".tsx", ".jsx"], diff --git a/gulp/webpack.production.config.js b/gulp/webpack.production.config.js index 4962843a..b0942134 100644 --- a/gulp/webpack.production.config.js +++ b/gulp/webpack.production.config.js @@ -1,9 +1,9 @@ import { resolve } from "path/posix"; import TerserPlugin from "terser-webpack-plugin"; import webpack from "webpack"; -const { DefinePlugin, IgnorePlugin } = webpack; import DeadCodePlugin from "webpack-deadcode-plugin"; import { getAllResourceImages, getRevision, getVersion } from "./buildutils.js"; +const { DefinePlugin, IgnorePlugin } = webpack; const globalDefs = { "assert": "false && window.assert", @@ -97,6 +97,7 @@ export default { alias: { "global-compression": resolve("../src/js/core/lzstring.js"), "root": resolve("../src/js/"), + "@/jsx-runtime": resolve("../src/js/jsx-runtime.ts"), }, fullySpecified: false, extensions: [".ts", ".js", ".tsx", ".jsx"], From 400a4cae37c47bbbb1925bb6c3e8cf2d44cffa9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sat, 20 Jul 2024 13:34:13 +0300 Subject: [PATCH 03/11] [TS] Rename GameState --- src/js/core/{game_state.js => game_state.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/js/core/{game_state.js => game_state.ts} (100%) diff --git a/src/js/core/game_state.js b/src/js/core/game_state.ts similarity index 100% rename from src/js/core/game_state.js rename to src/js/core/game_state.ts From 78b9ab49f1c75d5e032637ed8ae053b9d6796ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sat, 20 Jul 2024 13:35:11 +0300 Subject: [PATCH 04/11] Allow Node in JSX runtime The typings are not consistent, but they seem to work. I'll leave the rest as-is and just hope that someone fixes them later. --- src/js/globals.d.ts | 8 +++++--- src/js/jsx-runtime.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index 8d62ff0f..2caea804 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -158,8 +158,8 @@ declare namespace JSX { */ type IntrinsicElements = { [K in keyof HTMLElementTagNameMap]: { - children?: Node | Node[]; - [k: string]: Node | Node[] | string | number | boolean; + children?: JSXNode | JSXNode[]; + [k: string]: JSXNode | JSXNode[] | string | number | boolean; }; }; /** @@ -173,14 +173,16 @@ declare namespace JSX { * An attributes object. */ type Props = { [k: string]: unknown }; + /** * A functional component requiring attributes to match `T`. */ type Component = { (props: T): Element; }; + /** * A child of a JSX element. */ - type Node = Element | string | boolean | null | undefined; + type JSXNode = Node | string | boolean | null | undefined; } diff --git a/src/js/jsx-runtime.ts b/src/js/jsx-runtime.ts index be285029..1db25d1f 100644 --- a/src/js/jsx-runtime.ts +++ b/src/js/jsx-runtime.ts @@ -1,4 +1,4 @@ -function isDisplayed(node: JSX.Node): node is Exclude { +function isDisplayed(node: JSX.JSXNode): node is Exclude { return typeof node !== "boolean" && node != null; } @@ -27,7 +27,7 @@ function jsx( } throw new TypeError("JSX element attribute assigned invalid type"); }); - element.append(...([children].flat(Infinity) as JSX.Node[]).filter(isDisplayed)); + element.append(...([children].flat(Infinity) as JSX.JSXNode[]).filter(isDisplayed)); return element; } @@ -38,4 +38,4 @@ function jsx( const Fragment = (props: JSX.Props) => props.children as JSX.Element; // jsxs is used when there are multiple children -export { jsx, jsx as jsxs, Fragment }; +export { Fragment, jsx, jsx as jsxs }; From 3a20244e3887bce473eb7e5e0a9b27e778153771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sun, 21 Jul 2024 01:38:08 +0300 Subject: [PATCH 05/11] Use @ exclusively for root imports Remove "root" resolve alias and now-redundant jsx-runtime alias. --- gulp/webpack.config.js | 3 +-- gulp/webpack.production.config.js | 3 +-- src/js/platform/api.js | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gulp/webpack.config.js b/gulp/webpack.config.js index 79318745..71b06a64 100644 --- a/gulp/webpack.config.js +++ b/gulp/webpack.config.js @@ -87,8 +87,7 @@ export default { fallback: { fs: false }, alias: { "global-compression": resolve("../src/js/core/lzstring.js"), - "root": resolve("../src/js/"), - "@/jsx-runtime": resolve("../src/js/jsx-runtime.ts"), + "@": resolve("../src/js/"), }, fullySpecified: false, extensions: [".ts", ".js", ".tsx", ".jsx"], diff --git a/gulp/webpack.production.config.js b/gulp/webpack.production.config.js index b0942134..0cba84a3 100644 --- a/gulp/webpack.production.config.js +++ b/gulp/webpack.production.config.js @@ -96,8 +96,7 @@ export default { fallback: { fs: false }, alias: { "global-compression": resolve("../src/js/core/lzstring.js"), - "root": resolve("../src/js/"), - "@/jsx-runtime": resolve("../src/js/jsx-runtime.ts"), + "@": resolve("../src/js/"), }, fullySpecified: false, extensions: [".ts", ".js", ".tsx", ".jsx"], diff --git a/src/js/platform/api.js b/src/js/platform/api.js index 2355e10c..9f0cafd3 100644 --- a/src/js/platform/api.js +++ b/src/js/platform/api.js @@ -2,10 +2,10 @@ import { Application } from "../application"; /* typehints:end */ -import { DialogWithForm } from "root/core/modal_dialog_elements"; -import { FormElementInput } from "root/core/modal_dialog_forms"; import { createLogger } from "../core/logging"; import { compressX64 } from "../core/lzstring"; +import { DialogWithForm } from "../core/modal_dialog_elements"; +import { FormElementInput } from "../core/modal_dialog_forms"; import { timeoutPromise } from "../core/utils"; import { T } from "../translations"; From 178744e065735fb3d70faeed2ea6cbb0a0b115a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sun, 21 Jul 2024 01:48:43 +0300 Subject: [PATCH 06/11] [TS] Rename TextualGameState This time with .tsx extension. --- src/js/core/{textual_game_state.ts => textual_game_state.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/js/core/{textual_game_state.ts => textual_game_state.tsx} (100%) diff --git a/src/js/core/textual_game_state.ts b/src/js/core/textual_game_state.tsx similarity index 100% rename from src/js/core/textual_game_state.ts rename to src/js/core/textual_game_state.tsx From a2b21cc6dd203ea70b0b26b37e86849769f75457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sun, 21 Jul 2024 02:27:06 +0300 Subject: [PATCH 07/11] Fix all instances of "reciever" typo --- src/js/core/game_state.ts | 20 ++--- src/js/core/input_distributor.ts | 80 +++++++++---------- src/js/core/modal_dialog_elements.ts | 36 ++++----- src/js/game/components/underground_belt.js | 2 +- src/js/game/core.js | 10 +-- .../game/hud/parts/building_placer_logic.js | 15 ++-- src/js/game/hud/parts/entity_debugger.js | 2 +- .../hud/parts/puzzle_complete_notification.js | 6 +- src/js/game/hud/parts/sandbox_controller.js | 2 +- src/js/game/hud/parts/settings_menu.js | 18 ++--- src/js/game/hud/parts/shape_viewer.js | 10 +-- src/js/game/hud/parts/shop.js | 10 +-- src/js/game/hud/parts/statistics.js | 14 ++-- src/js/game/hud/parts/tutorial_hints.js | 10 +-- src/js/game/hud/parts/unlock_notification.js | 8 +- src/js/game/key_action_mapper.js | 26 +++--- src/js/game/systems/underground_belt.js | 6 +- src/js/states/keybindings.js | 4 +- 18 files changed, 139 insertions(+), 140 deletions(-) diff --git a/src/js/core/game_state.ts b/src/js/core/game_state.ts index 6f276e99..bea5f8d4 100644 --- a/src/js/core/game_state.ts +++ b/src/js/core/game_state.ts @@ -3,13 +3,13 @@ import { Application } from "../application"; import { StateManager } from "./state_manager"; /* typehints:end */ -import { globalConfig } from "./config"; -import { ClickDetector } from "./click_detector"; -import { logSection, createLogger } from "./logging"; -import { InputReceiver } from "./input_receiver"; -import { waitNextFrame } from "./utils"; -import { RequestChannel } from "./request_channel"; import { MUSIC } from "../platform/sound"; +import { ClickDetector } from "./click_detector"; +import { globalConfig } from "./config"; +import { InputReceiver } from "./input_receiver"; +import { createLogger, logSection } from "./logging"; +import { RequestChannel } from "./request_channel"; +import { waitNextFrame } from "./utils"; const logger = createLogger("game_state"); @@ -38,8 +38,8 @@ export class GameState { this.clickDetectors = []; // Every state captures keyboard events by default - this.inputReciever = new InputReceiver("state-" + key); - this.inputReciever.backButton.add(this.onBackButton, this); + this.inputReceiver = new InputReceiver("state-" + key); + this.inputReceiver.backButton.add(this.onBackButton, this); // A channel we can use to perform async ops this.asyncChannel = new RequestChannel(); @@ -267,7 +267,7 @@ export class GameState { */ internalEnterCallback(payload, callCallback = true) { logSection(this.key, "#26a69a"); - this.app.inputMgr.pushReciever(this.inputReciever); + this.app.inputMgr.pushReceiver(this.inputReceiver); this.htmlElement = this.getDivElement(); this.htmlElement.classList.add("active"); @@ -293,7 +293,7 @@ export class GameState { this.onLeave(); this.htmlElement.classList.remove("active"); - this.app.inputMgr.popReciever(this.inputReciever); + this.app.inputMgr.popReceiver(this.inputReceiver); this.internalCleanUpClickDetectors(); this.asyncChannel.cancelAll(); } diff --git a/src/js/core/input_distributor.ts b/src/js/core/input_distributor.ts index b373164d..dad0417b 100644 --- a/src/js/core/input_distributor.ts +++ b/src/js/core/input_distributor.ts @@ -1,14 +1,14 @@ import type { Application } from "../application"; import type { InputReceiver, ReceiverId } from "./input_receiver"; -import { Signal, STOP_PROPAGATION } from "./signal"; import { createLogger } from "./logging"; +import { Signal, STOP_PROPAGATION } from "./signal"; import { arrayDeleteValue, fastArrayDeleteValue } from "./utils"; const logger = createLogger("input_distributor"); export class InputDistributor { - public recieverStack: InputReceiver[] = []; + public receiverStack: InputReceiver[] = []; public filters: ((arg: string) => boolean)[] = []; /** @@ -34,71 +34,71 @@ export class InputDistributor { fastArrayDeleteValue(this.filters, filter); } - pushReciever(reciever: InputReceiver) { - if (this.isRecieverAttached(reciever)) { - assert(false, "Can not add reciever " + reciever.context + " twice"); - logger.error("Can not add reciever", reciever.context, "twice"); + pushReceiver(receiver: InputReceiver) { + if (this.isReceiverAttached(receiver)) { + assert(false, "Can not add receiver " + receiver.context + " twice"); + logger.error("Can not add receiver", receiver.context, "twice"); return; } - this.recieverStack.push(reciever); + this.receiverStack.push(receiver); - if (this.recieverStack.length > 10) { + if (this.receiverStack.length > 10) { logger.error( - "Reciever stack is huge, probably some dead receivers arround:", - this.recieverStack.map(x => x.context) + "Receiver stack is huge, probably some dead receivers arround:", + this.receiverStack.map(x => x.context) ); } } - popReciever(reciever: InputReceiver) { - if (this.recieverStack.indexOf(reciever) < 0) { - assert(false, "Can not pop reciever " + reciever.context + " since its not contained"); - logger.error("Can not pop reciever", reciever.context, "since its not contained"); + popReceiver(receiver: InputReceiver) { + if (this.receiverStack.indexOf(receiver) < 0) { + assert(false, "Can not pop receiver " + receiver.context + " since its not contained"); + logger.error("Can not pop receiver", receiver.context, "since its not contained"); return; } - if (this.recieverStack[this.recieverStack.length - 1] !== reciever) { + if (this.receiverStack[this.receiverStack.length - 1] !== receiver) { logger.warn( - "Popping reciever", - reciever.context, + "Popping receiver", + receiver.context, "which is not on top of the stack. Stack is: ", - this.recieverStack.map(x => x.context) + this.receiverStack.map(x => x.context) ); } - arrayDeleteValue(this.recieverStack, reciever); + arrayDeleteValue(this.receiverStack, receiver); } - isRecieverAttached(reciever: InputReceiver) { - return this.recieverStack.indexOf(reciever) >= 0; + isReceiverAttached(receiver: InputReceiver) { + return this.receiverStack.indexOf(receiver) >= 0; } - isRecieverOnTop(reciever: InputReceiver) { + isReceiverOnTop(receiver: InputReceiver) { return ( - this.isRecieverAttached(reciever) && - this.recieverStack[this.recieverStack.length - 1] === reciever + this.isReceiverAttached(receiver) && + this.receiverStack[this.receiverStack.length - 1] === receiver ); } - makeSureAttachedAndOnTop(reciever: InputReceiver) { - this.makeSureDetached(reciever); - this.pushReciever(reciever); + makeSureAttachedAndOnTop(receiver: InputReceiver) { + this.makeSureDetached(receiver); + this.pushReceiver(receiver); } - makeSureDetached(reciever: InputReceiver) { - if (this.isRecieverAttached(reciever)) { - arrayDeleteValue(this.recieverStack, reciever); + makeSureDetached(receiver: InputReceiver) { + if (this.isReceiverAttached(receiver)) { + arrayDeleteValue(this.receiverStack, receiver); } } - destroyReceiver(reciever: InputReceiver) { - this.makeSureDetached(reciever); - reciever.cleanup(); + destroyReceiver(receiver: InputReceiver) { + this.makeSureDetached(receiver); + receiver.cleanup(); } // Internal - getTopReciever() { - if (this.recieverStack.length > 0) { - return this.recieverStack[this.recieverStack.length - 1]; + getTopReceiver() { + if (this.receiverStack.length > 0) { + return this.receiverStack[this.receiverStack.length - 1]; } return null; } @@ -129,12 +129,12 @@ export class InputDistributor { } } - const reciever = this.getTopReciever(); - if (!reciever) { - logger.warn("Dismissing event because not reciever was found:", eventId); + const receiver = this.getTopReceiver(); + if (!receiver) { + logger.warn("Dismissing event because not receiver was found:", eventId); return; } - const signal = reciever[eventId]; + const signal = receiver[eventId]; assert(signal instanceof Signal, "Not a valid event id"); // probably not possible to type properly, since the types of `signal` and `payload` are correlated return signal.dispatch(payload as never); diff --git a/src/js/core/modal_dialog_elements.ts b/src/js/core/modal_dialog_elements.ts index 8154a071..4b2936a9 100644 --- a/src/js/core/modal_dialog_elements.ts +++ b/src/js/core/modal_dialog_elements.ts @@ -1,15 +1,15 @@ import type { Application } from "../application"; +import { getStringForKeyCode } from "../game/key_action_mapper"; +import { SOUNDS } from "../platform/sound"; +import { T } from "../translations"; +import { ClickDetector, ClickDetectorConstructorArgs } from "./click_detector"; +import { globalConfig } from "./config"; +import { InputReceiver, KeydownEvent } from "./input_receiver"; +import { createLogger } from "./logging"; +import { FormElement } from "./modal_dialog_forms"; import { Signal, STOP_PROPAGATION } from "./signal"; import { arrayDeleteValue, waitNextFrame } from "./utils"; -import { ClickDetector, ClickDetectorConstructorArgs } from "./click_detector"; -import { SOUNDS } from "../platform/sound"; -import { InputReceiver, KeydownEvent } from "./input_receiver"; -import { FormElement } from "./modal_dialog_forms"; -import { globalConfig } from "./config"; -import { getStringForKeyCode } from "../game/key_action_mapper"; -import { createLogger } from "./logging"; -import { T } from "../translations"; /* * *************************************************** @@ -51,7 +51,7 @@ export class Dialog { public timeouts: number[] = []; public clickDetectors: ClickDetector[] = []; - public inputReciever: InputReceiver; + public inputReceiver: InputReceiver; public enterHandler: T = null; public escapeHandler: T = null; @@ -103,9 +103,9 @@ export class Dialog { this.buttonSignals[buttonId] = new Signal(); } - this.inputReciever = new InputReceiver("dialog-" + this.title); + this.inputReceiver = new InputReceiver("dialog-" + this.title); - this.inputReciever.keydown.add(this.handleKeydown, this); + this.inputReceiver.keydown.add(this.handleKeydown, this); } /** @@ -124,7 +124,7 @@ export class Dialog { } internalButtonHandler(id: T | "close-button", ...payload: U | []) { - this.app.inputMgr.popReciever(this.inputReciever); + this.app.inputMgr.popReceiver(this.inputReceiver); if (id !== "close-button") { this.buttonSignals[id].dispatch(...payload); @@ -160,7 +160,7 @@ export class Dialog { }); title.appendChild(closeBtn); - this.inputReciever.backButton.add(() => this.internalButtonHandler("close-button")); + this.inputReceiver.backButton.add(() => this.internalButtonHandler("close-button")); } const content = document.createElement("div"); @@ -231,7 +231,7 @@ export class Dialog { } this.element = elem; - this.app.inputMgr.pushReciever(this.inputReciever); + this.app.inputMgr.pushReceiver(this.inputReceiver); return this.element; } @@ -248,7 +248,7 @@ export class Dialog { // We need to do this here, because if the backbutton event gets // dispatched to the modal dialogs, it will not call the internalButtonHandler, // and thus our receiver stays attached the whole time - this.app.inputMgr.destroyReceiver(this.inputReciever); + this.app.inputMgr.destroyReceiver(this.inputReceiver); for (let i = 0; i < this.clickDetectors.length; ++i) { this.clickDetectors[i].cleanup(); @@ -300,8 +300,8 @@ export class DialogLoading extends Dialog { }); // Loading dialog can not get closed with back button - this.inputReciever.backButton.removeAll(); - this.inputReciever.context = "dialog-loading"; + this.inputReceiver.backButton.removeAll(); + this.inputReceiver.context = "dialog-loading"; } createElement() { @@ -322,7 +322,7 @@ export class DialogLoading extends Dialog { loader.classList.add("loadingIndicator"); elem.appendChild(loader); - this.app.inputMgr.pushReciever(this.inputReciever); + this.app.inputMgr.pushReceiver(this.inputReceiver); return elem; } diff --git a/src/js/game/components/underground_belt.js b/src/js/game/components/underground_belt.js index 2b744edd..fe465350 100644 --- a/src/js/game/components/underground_belt.js +++ b/src/js/game/components/underground_belt.js @@ -57,7 +57,7 @@ export class UndergroundBeltComponent extends Component { /** * Used on both receiver and sender. - * Reciever: Used to store the next item to transfer, and to block input while doing this + * Receiver: Used to store the next item to transfer, and to block input while doing this * Sender: Used to store which items are currently "travelling" * @type {Array<[BaseItem, number]>} Format is [Item, ingame time to eject the item] */ diff --git a/src/js/game/core.js b/src/js/game/core.js index 4fbb8ddb..f6f4e947 100644 --- a/src/js/game/core.js +++ b/src/js/game/core.js @@ -17,13 +17,16 @@ import { Rectangle } from "../core/rectangle"; import { ORIGINAL_SPRITE_SCALE } from "../core/sprites"; import { lerp, randomInt, round2Digits } from "../core/utils"; import { Vector } from "../core/vector"; +import { MOD_SIGNALS } from "../mods/mod_signals"; import { Savegame } from "../savegame/savegame"; import { SavegameSerializer } from "../savegame/savegame_serializer"; +import { AchievementProxy } from "./achievement_proxy"; import { AutomaticSave } from "./automatic_save"; import { MetaHubBuilding } from "./buildings/hub"; import { Camera } from "./camera"; import { DynamicTickrate } from "./dynamic_tickrate"; import { EntityManager } from "./entity_manager"; +import { GameMode } from "./game_mode"; import { GameSystemManager } from "./game_system_manager"; import { HubGoals } from "./hub_goals"; import { GameHUD } from "./hud/hud"; @@ -31,14 +34,11 @@ import { KeyActionMapper } from "./key_action_mapper"; import { GameLogic } from "./logic"; import { MapView } from "./map_view"; import { defaultBuildingVariant } from "./meta_building"; -import { GameMode } from "./game_mode"; import { ProductionAnalytics } from "./production_analytics"; import { GameRoot } from "./root"; import { ShapeDefinitionManager } from "./shape_definition_manager"; -import { AchievementProxy } from "./achievement_proxy"; import { SoundProxy } from "./sound_proxy"; import { GameTime } from "./time/game_time"; -import { MOD_SIGNALS } from "../mods/mod_signals"; const logger = createLogger("ingame/core"); @@ -101,7 +101,7 @@ export class GameCore { const root = this.root; // This isn't nice, but we need it right here - root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReciever); + root.keyMapper = new KeyActionMapper(root, this.root.gameState.inputReceiver); // Init game mode root.gameMode = GameMode.create(root, gameModeId, parentState.creationPayload.gameModeParameters); @@ -142,7 +142,7 @@ export class GameCore { // @todo Find better place if (G_IS_DEV && globalConfig.debug.manualTickOnly) { - this.root.gameState.inputReciever.keydown.add(key => { + this.root.gameState.inputReceiver.keydown.add(key => { if (key.keyCode === 84) { // 'T' diff --git a/src/js/game/hud/parts/building_placer_logic.js b/src/js/game/hud/parts/building_placer_logic.js index 08fc143e..c25f4210 100644 --- a/src/js/game/hud/parts/building_placer_logic.js +++ b/src/js/game/hud/parts/building_placer_logic.js @@ -1,20 +1,19 @@ -import { globalConfig } from "../../../core/config"; import { gMetaBuildingRegistry } from "../../../core/global_registries"; import { Signal, STOP_PROPAGATION } from "../../../core/signal"; import { TrackedState } from "../../../core/tracked_state"; +import { safeModulo } from "../../../core/utils"; import { Vector } from "../../../core/vector"; +import { SOUNDS } from "../../../platform/sound"; +import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes"; +import { MetaHubBuilding } from "../../buildings/hub"; +import { enumMinerVariants, MetaMinerBuilding } from "../../buildings/miner"; import { enumMouseButton } from "../../camera"; import { StaticMapEntityComponent } from "../../components/static_map_entity"; import { Entity } from "../../entity"; import { KEYMAPPINGS } from "../../key_action_mapper"; import { defaultBuildingVariant, MetaBuilding } from "../../meta_building"; -import { BaseHUDPart } from "../base_hud_part"; -import { SOUNDS } from "../../../platform/sound"; -import { MetaMinerBuilding, enumMinerVariants } from "../../buildings/miner"; import { enumHubGoalRewards } from "../../tutorial_goals"; -import { getBuildingDataFromCode, getCodeFromBuildingData } from "../../building_codes"; -import { MetaHubBuilding } from "../../buildings/hub"; -import { safeModulo } from "../../../core/utils"; +import { BaseHUDPart } from "../base_hud_part"; /** * Contains all logic for the building placer - this doesn't include the rendering @@ -122,7 +121,7 @@ export class HUDBuildingPlacerLogic extends BaseHUDPart { .add(this.switchDirectionLockSide, this); keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.abortPlacement, this); keyActionMapper.getBinding(KEYMAPPINGS.placement.pipette).add(this.startPipette, this); - this.root.gameState.inputReciever.keyup.add(this.checkForDirectionLockSwitch, this); + this.root.gameState.inputReceiver.keyup.add(this.checkForDirectionLockSwitch, this); // BINDINGS TO GAME EVENTS this.root.hud.signals.buildingsSelectedForCopy.add(this.abortPlacement, this); diff --git a/src/js/game/hud/parts/entity_debugger.js b/src/js/game/hud/parts/entity_debugger.js index debd456d..1023a335 100644 --- a/src/js/game/hud/parts/entity_debugger.js +++ b/src/js/game/hud/parts/entity_debugger.js @@ -27,7 +27,7 @@ export class HUDEntityDebugger extends BaseHUDPart { } initialize() { - this.root.gameState.inputReciever.keydown.add(key => { + this.root.gameState.inputReceiver.keydown.add(key => { if (key.keyCode === 119) { // F8 this.pickEntity(); diff --git a/src/js/game/hud/parts/puzzle_complete_notification.js b/src/js/game/hud/parts/puzzle_complete_notification.js index d83e33f1..37b7e50b 100644 --- a/src/js/game/hud/parts/puzzle_complete_notification.js +++ b/src/js/game/hud/parts/puzzle_complete_notification.js @@ -24,7 +24,7 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart { } createElements(parent) { - this.inputReciever = new InputReceiver("puzzle-complete"); + this.inputReceiver = new InputReceiver("puzzle-complete"); this.element = makeDiv(parent, "ingame_HUD_PuzzleCompleteNotification", ["noBlur"]); @@ -86,13 +86,13 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart { show() { this.root.soundProxy.playUi(SOUNDS.levelComplete); - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); this.visible = true; this.timeOfCompletion = this.root.time.now(); } cleanup() { - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); } isBlockingOverlay() { diff --git a/src/js/game/hud/parts/sandbox_controller.js b/src/js/game/hud/parts/sandbox_controller.js index 8d3d8e49..ce9f6221 100644 --- a/src/js/game/hud/parts/sandbox_controller.js +++ b/src/js/game/hud/parts/sandbox_controller.js @@ -139,7 +139,7 @@ export class HUDSandboxController extends BaseHUDPart { initialize() { // Allow toggling the controller overlay - this.root.gameState.inputReciever.keydown.add(key => { + this.root.gameState.inputReceiver.keydown.add(key => { if (key.keyCode === 117) { // F6 this.toggle(); diff --git a/src/js/game/hud/parts/settings_menu.js b/src/js/game/hud/parts/settings_menu.js index 5a915438..d144c680 100644 --- a/src/js/game/hud/parts/settings_menu.js +++ b/src/js/game/hud/parts/settings_menu.js @@ -1,11 +1,11 @@ -import { BaseHUDPart } from "../base_hud_part"; -import { makeDiv, formatBigNumberFull } from "../../../core/utils"; -import { DynamicDomAttach } from "../dynamic_dom_attach"; import { InputReceiver } from "../../../core/input_receiver"; -import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; +import { formatBigNumberFull, makeDiv } from "../../../core/utils"; import { T } from "../../../translations"; -import { StaticMapEntityComponent } from "../../components/static_map_entity"; import { BeltComponent } from "../../components/belt"; +import { StaticMapEntityComponent } from "../../components/static_map_entity"; +import { KEYMAPPINGS, KeyActionMapper } from "../../key_action_mapper"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; export class HUDSettingsMenu extends BaseHUDPart { createElements(parent) { @@ -83,8 +83,8 @@ export class HUDSettingsMenu extends BaseHUDPart { attachClass: "visible", }); - this.inputReciever = new InputReceiver("settingsmenu"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + this.inputReceiver = new InputReceiver("settingsmenu"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReceiver); this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); this.close(); @@ -92,7 +92,7 @@ export class HUDSettingsMenu extends BaseHUDPart { show() { this.visible = true; - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60); @@ -119,7 +119,7 @@ export class HUDSettingsMenu extends BaseHUDPart { close() { this.visible = false; - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); this.update(); } diff --git a/src/js/game/hud/parts/shape_viewer.js b/src/js/game/hud/parts/shape_viewer.js index 2eb4d642..61873a9d 100644 --- a/src/js/game/hud/parts/shape_viewer.js +++ b/src/js/game/hud/parts/shape_viewer.js @@ -1,7 +1,7 @@ import { InputReceiver } from "../../../core/input_receiver"; import { makeDiv, removeAllChildren } from "../../../core/utils"; import { T } from "../../../translations"; -import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; +import { KEYMAPPINGS, KeyActionMapper } from "../../key_action_mapper"; import { ShapeDefinition } from "../../shape_definition"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; @@ -38,8 +38,8 @@ export class HUDShapeViewer extends BaseHUDPart { this.currentShapeKey = null; - this.inputReciever = new InputReceiver("shape_viewer"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + this.inputReceiver = new InputReceiver("shape_viewer"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReceiver); this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); @@ -67,7 +67,7 @@ export class HUDShapeViewer extends BaseHUDPart { */ close() { this.visible = false; - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); this.update(); } @@ -77,7 +77,7 @@ export class HUDShapeViewer extends BaseHUDPart { */ renderForShape(definition) { this.visible = true; - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); removeAllChildren(this.renderArea); diff --git a/src/js/game/hud/parts/shop.js b/src/js/game/hud/parts/shop.js index 97dd1846..cb343541 100644 --- a/src/js/game/hud/parts/shop.js +++ b/src/js/game/hud/parts/shop.js @@ -3,7 +3,7 @@ import { InputReceiver } from "../../../core/input_receiver"; import { formatBigNumber, getRomanNumber, makeDiv } from "../../../core/utils"; import { SOUNDS } from "../../../platform/sound"; import { T } from "../../../translations"; -import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; +import { KEYMAPPINGS, KeyActionMapper } from "../../key_action_mapper"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; @@ -193,8 +193,8 @@ export class HUDShop extends BaseHUDPart { attachClass: "visible", }); - this.inputReciever = new InputReceiver("shop"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + this.inputReceiver = new InputReceiver("shop"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReceiver); this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuClose).add(this.close, this); @@ -224,13 +224,13 @@ export class HUDShop extends BaseHUDPart { show() { this.visible = true; - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); this.rerenderFull(); } close() { this.visible = false; - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); this.update(); } diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index 015e48b6..a1a24a94 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -1,11 +1,11 @@ import { InputReceiver } from "../../../core/input_receiver"; import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils"; -import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; +import { T } from "../../../translations"; +import { KEYMAPPINGS, KeyActionMapper } from "../../key_action_mapper"; import { enumAnalyticsDataSource } from "../../production_analytics"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { enumDisplayMode, HUDShapeStatisticsHandle, statisticsUnitsSeconds } from "./statistics_handle"; -import { T } from "../../../translations"; +import { HUDShapeStatisticsHandle, enumDisplayMode, statisticsUnitsSeconds } from "./statistics_handle"; /** * Capitalizes the first letter @@ -115,8 +115,8 @@ export class HUDStatistics extends BaseHUDPart { attachClass: "visible", }); - this.inputReciever = new InputReceiver("statistics"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + this.inputReceiver = new InputReceiver("statistics"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReceiver); this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuClose).add(this.close, this); @@ -157,14 +157,14 @@ export class HUDStatistics extends BaseHUDPart { show() { this.visible = true; - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); this.rerenderFull(); this.update(); } close() { this.visible = false; - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); this.update(); } diff --git a/src/js/game/hud/parts/tutorial_hints.js b/src/js/game/hud/parts/tutorial_hints.js index 9dc07977..9100808a 100644 --- a/src/js/game/hud/parts/tutorial_hints.js +++ b/src/js/game/hud/parts/tutorial_hints.js @@ -1,10 +1,10 @@ import { InputReceiver } from "../../../core/input_receiver"; import { TrackedState } from "../../../core/tracked_state"; import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { T } from "../../../translations"; const tutorialVideos = [3, 4, 5, 6, 7, 9, 10, 11]; @@ -46,8 +46,8 @@ export class HUDPartTutorialHints extends BaseHUDPart { this.videoAttach.update(false); this.enlarged = false; - this.inputReciever = new InputReceiver("tutorial_hints"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + this.inputReceiver = new InputReceiver("tutorial_hints"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReceiver); this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); this.domAttach = new DynamicDomAttach(this.root, this.element); @@ -71,14 +71,14 @@ export class HUDPartTutorialHints extends BaseHUDPart { close() { this.enlarged = false; this.element.classList.remove("enlarged", "noBlur"); - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); this.update(); } show() { this.element.classList.add("enlarged", "noBlur"); this.enlarged = true; - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); this.update(); this.videoElement.currentTime = 0; diff --git a/src/js/game/hud/parts/unlock_notification.js b/src/js/game/hud/parts/unlock_notification.js index affd0eeb..ce7df2e2 100644 --- a/src/js/game/hud/parts/unlock_notification.js +++ b/src/js/game/hud/parts/unlock_notification.js @@ -31,7 +31,7 @@ export class HUDUnlockNotification extends BaseHUDPart { } createElements(parent) { - this.inputReciever = new InputReceiver("unlock-notification"); + this.inputReceiver = new InputReceiver("unlock-notification"); this.element = makeDiv(parent, "ingame_HUD_UnlockNotification", ["noBlur"]); @@ -67,7 +67,7 @@ export class HUDUnlockNotification extends BaseHUDPart { return; } - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReceiver); this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace( "", ("" + level).padStart(2, "0") @@ -118,7 +118,7 @@ export class HUDUnlockNotification extends BaseHUDPart { } cleanup() { - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); if (this.buttonShowTimeout) { clearTimeout(this.buttonShowTimeout); this.buttonShowTimeout = null; @@ -158,7 +158,7 @@ export class HUDUnlockNotification extends BaseHUDPart { } close() { - this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.root.app.inputMgr.makeSureDetached(this.inputReceiver); if (this.buttonShowTimeout) { clearTimeout(this.buttonShowTimeout); this.buttonShowTimeout = null; diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 768f8cca..5fb04c6f 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -1,11 +1,11 @@ /* typehints:start */ -import { GameRoot } from "./root"; -import { InputReceiver } from "../core/input_receiver"; import { Application } from "../application"; +import { InputReceiver } from "../core/input_receiver"; +import { GameRoot } from "./root"; /* typehints:end */ -import { Signal, STOP_PROPAGATION } from "../core/signal"; import { IS_MOBILE } from "../core/config"; +import { Signal, STOP_PROPAGATION } from "../core/signal"; import { T } from "../translations"; export function keyToKeyCode(str) { @@ -353,9 +353,9 @@ export class Keybinding { get pressed() { // Check if the key is down if (this.app.inputMgr.keysDown.has(this.keyCode)) { - // Check if it is the top reciever - const reciever = this.keyMapper.inputReceiver; - return this.app.inputMgr.getTopReciever() === reciever; + // Check if it is the top receiver + const receiver = this.keyMapper.inputReceiver; + return this.app.inputMgr.getTopReceiver() === receiver; } return false; } @@ -412,14 +412,14 @@ export class KeyActionMapper { /** * * @param {GameRoot} root - * @param {InputReceiver} inputReciever + * @param {InputReceiver} inputReceiver */ - constructor(root, inputReciever) { + constructor(root, inputReceiver) { this.root = root; - this.inputReceiver = inputReciever; + this.inputReceiver = inputReceiver; - inputReciever.keydown.add(this.handleKeydown, this); - inputReciever.keyup.add(this.handleKeyup, this); + inputReceiver.keydown.add(this.handleKeydown, this); + inputReceiver.keyup.add(this.handleKeyup, this); /** @type {Object.} */ this.keybindings = {}; @@ -443,8 +443,8 @@ export class KeyActionMapper { } } - inputReciever.pageBlur.add(this.onPageBlur, this); - inputReciever.destroyed.add(this.cleanup, this); + inputReceiver.pageBlur.add(this.onPageBlur, this); + inputReceiver.destroyed.add(this.cleanup, this); } /** diff --git a/src/js/game/systems/underground_belt.js b/src/js/game/systems/underground_belt.js index c38603fb..38a204fd 100644 --- a/src/js/game/systems/underground_belt.js +++ b/src/js/game/systems/underground_belt.js @@ -11,7 +11,7 @@ import { enumDirectionToVector, enumInvertedDirections, } from "../../core/vector"; -import { enumUndergroundBeltMode, UndergroundBeltComponent } from "../components/underground_belt"; +import { UndergroundBeltComponent, enumUndergroundBeltMode } from "../components/underground_belt"; import { Entity } from "../entity"; import { GameSystemWithFilter } from "../game_system_with_filter"; @@ -243,7 +243,7 @@ export class UndergroundBeltSystem extends GameSystemWithFilter { * @param {Entity} entity * @returns {import("../components/underground_belt").LinkedUndergroundBelt} */ - findRecieverForSender(entity) { + findReceiverForSender(entity) { const staticComp = entity.components.StaticMapEntity; const undergroundComp = entity.components.UndergroundBelt; const searchDirection = staticComp.localDirectionToWorld(enumDirection.top); @@ -299,7 +299,7 @@ export class UndergroundBeltSystem extends GameSystemWithFilter { let cacheEntry = undergroundComp.cachedLinkedEntity; if (!cacheEntry) { // Need to recompute cache - cacheEntry = undergroundComp.cachedLinkedEntity = this.findRecieverForSender(entity); + cacheEntry = undergroundComp.cachedLinkedEntity = this.findReceiverForSender(entity); } if (!cacheEntry.entity) { diff --git a/src/js/states/keybindings.js b/src/js/states/keybindings.js index 7ff11016..98b23efa 100644 --- a/src/js/states/keybindings.js +++ b/src/js/states/keybindings.js @@ -93,7 +93,7 @@ export class KeybindingsState extends TextualGameState { type: "info", }); - dialog.inputReciever.keydown.add(({ keyCode, shift, alt, event }) => { + dialog.inputReceiver.keydown.add(({ keyCode, shift, alt, event }) => { if (keyCode === 27) { this.dialogs.closeDialog(dialog); return; @@ -125,7 +125,7 @@ export class KeybindingsState extends TextualGameState { this.updateKeybindings(); }); - dialog.inputReciever.backButton.add(() => {}); + dialog.inputReceiver.backButton.add(() => {}); this.dialogs.internalShowDialog(dialog); this.app.sound.playUiSound(SOUNDS.dialogOk); From 69ce8ffc176edbd638494610b0832ff6f530a843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sun, 21 Jul 2024 02:46:48 +0300 Subject: [PATCH 08/11] Basic JSX support in states Quite hacky considering the complex indirection and minor differences in the appearance of each state, as well as legacy code and need to support HTML strings for now. Some methods could be improved, refactored or deleted, but no major changes were made. Mods and puzzles menu states are broken in this commit. --- src/js/core/game_state.ts | 148 +++++++++++++++-------------- src/js/core/state_manager.js | 11 ++- src/js/core/textual_game_state.tsx | 95 ++++++++++-------- 3 files changed, 136 insertions(+), 118 deletions(-) diff --git a/src/js/core/game_state.ts b/src/js/core/game_state.ts index bea5f8d4..9d58c380 100644 --- a/src/js/core/game_state.ts +++ b/src/js/core/game_state.ts @@ -1,14 +1,11 @@ -/* typehints:start */ -import { Application } from "../application"; -import { StateManager } from "./state_manager"; -/* typehints:end */ - -import { MUSIC } from "../platform/sound"; +import { MUSIC } from "@/platform/sound"; +import type { Application } from "../application"; import { ClickDetector } from "./click_detector"; import { globalConfig } from "./config"; import { InputReceiver } from "./input_receiver"; import { createLogger, logSection } from "./logging"; import { RequestChannel } from "./request_channel"; +import type { StateManager } from "./state_manager"; import { waitNextFrame } from "./utils"; const logger = createLogger("game_state"); @@ -17,49 +14,41 @@ const logger = createLogger("game_state"); * Basic state of the game state machine. This is the base of the whole game */ export class GameState { + public app: Application = null; + public readonly key: string; + public inputReceiver: InputReceiver; + + /** A channel we can use to perform async ops */ + protected asyncChannel = new RequestChannel(); + protected clickDetectors: ClickDetector[] = []; + + /** @todo review this */ + protected htmlElement: HTMLElement | undefined; + + private stateManager: StateManager = null; + + /** Store if we are currently fading out */ + private fadingOut = false; + /** * Constructs a new state with the given id - * @param {string} key The id of the state. We use ids to refer to states because otherwise we get - * circular references + * @param key The id of the state. We use ids to refer to states because otherwise we get + * circular references */ - constructor(key) { + constructor(key: string) { this.key = key; - /** @type {StateManager} */ - this.stateManager = null; - - /** @type {Application} */ - this.app = null; - - // Store if we are currently fading out - this.fadingOut = false; - - /** @type {Array} */ - this.clickDetectors = []; - // Every state captures keyboard events by default this.inputReceiver = new InputReceiver("state-" + key); this.inputReceiver.backButton.add(this.onBackButton, this); - - // A channel we can use to perform async ops - this.asyncChannel = new RequestChannel(); } //// GETTERS / HELPER METHODS //// - /** - * Returns the states key - * @returns {string} - */ - getKey() { - return this.key; - } - /** * Returns the html element of the state - * @returns {HTMLElement} */ - getDivElement() { + getDivElement(): HTMLElement { return document.getElementById("state_" + this.key); } @@ -120,9 +109,9 @@ export class GameState { /** * Callback when entering the state, to be overriddemn - * @param {any} payload Arbitrary data passed from the state which we are transferring from + * @param payload Arbitrary data passed from the state which we are transferring from */ - onEnter(payload) {} + onEnter(payload: {}) {} /** * Callback when leaving the state @@ -141,22 +130,22 @@ export class GameState { /** * Render callback - * @param {number} dt Delta time in ms since last render + * @param dt Delta time in ms since last render */ - onRender(dt) {} + onRender(dt: number) {} /** * Background tick callback, called while the game is inactiev - * @param {number} dt Delta time in ms since last tick + * @param dt Delta time in ms since last tick */ - onBackgroundTick(dt) {} + onBackgroundTick(dt: number) {} /** * Called when the screen resized - * @param {number} w window/screen width - * @param {number} h window/screen height + * @param w window/screen width + * @param h window/screen height */ - onResized(w, h) {} + onResized(w: number, h: number) {} /** * Internal backbutton handler, called when the hardware back button is pressed or @@ -168,9 +157,9 @@ export class GameState { /** * Should return how many mulliseconds to fade in / out the state. Not recommended to override! - * @returns {number} Time in milliseconds to fade out + * @returns Time in milliseconds to fade out */ - getInOutFadeTime() { + getInOutFadeTime(): number { if (globalConfig.debug.noArtificialDelays) { return 0; } @@ -180,39 +169,43 @@ export class GameState { /** * Should return whether to fade in the game state. This will then apply the right css classes * for the fadein. - * @returns {boolean} */ - getHasFadeIn() { + getHasFadeIn(): boolean { return true; } /** * Should return whether to fade out the game state. This will then apply the right css classes * for the fadeout and wait the delay before moving states - * @returns {boolean} */ - getHasFadeOut() { + getHasFadeOut(): boolean { return true; } /** * Returns if this state should get paused if it does not have focus - * @returns {boolean} true to pause the updating of the game + * @returns true to pause the updating of the game */ - getPauseOnFocusLost() { + getPauseOnFocusLost(): boolean { return true; } /** * Should return the html code of the state. - * @returns {string} - * @abstract + * @deprecated use {@link getContentLayout} instead */ - getInnerHTML() { - abstract; + getInnerHTML(): string { return ""; } + /** + * Should return the element(s) to be displayed in the state. + * If null, {@link getInnerHTML} will be used instead. + */ + protected getContentLayout(): Node { + return null; + } + /** * Returns if the state has an unload confirmation, this is the * "Are you sure you want to leave the page" message. @@ -223,25 +216,22 @@ export class GameState { /** * Should return the theme music for this state - * @returns {string|null} */ - getThemeMusic() { + getThemeMusic(): string | null { return MUSIC.menu; } /** * Should return true if the player is currently ingame - * @returns {boolean} */ - getIsIngame() { + getIsIngame(): boolean { return false; } /** * Should return whether to clear the whole body content before entering the state. - * @returns {boolean} */ - getRemovePreviousContent() { + getRemovePreviousContent(): boolean { return true; } @@ -251,9 +241,8 @@ export class GameState { /** * Internal callback from the manager. Do not override! - * @param {StateManager} stateManager */ - internalRegisterCallback(stateManager, app) { + internalRegisterCallback(stateManager: StateManager, app: Application) { assert(stateManager, "No state manager"); assert(app, "No app"); this.stateManager = stateManager; @@ -262,10 +251,10 @@ export class GameState { /** * Internal callback when entering the state. Do not override! - * @param {any} payload Arbitrary data passed from the state which we are transferring from - * @param {boolean} callCallback Whether to call the onEnter callback + * @param payload Arbitrary data passed from the state which we are transferring from + * @param callCallback Whether to call the onEnter callback */ - internalEnterCallback(payload, callCallback = true) { + internalEnterCallback(payload: any, callCallback = true) { logSection(this.key, "#26a69a"); this.app.inputMgr.pushReceiver(this.inputReceiver); @@ -325,18 +314,33 @@ export class GameState { } /** - * Internal method to get the HTML of the game state. - * @returns {string} + * Internal method to get all elements of the game state. Can be + * called from subclasses to provide support for both HTMLElements + * and HTML strings. */ - internalGetFullHtml() { - return this.getInnerHTML(); + internalGetWrappedContent(): Node { + const elements = this.getContentLayout(); + if (elements instanceof Node) { + return elements; + } + + if (Array.isArray(elements)) { + const fragment = document.createDocumentFragment(); + fragment.append(...(elements as Node[])); + return fragment; + } + + // Fall back to deprecated HTML strings solution + const template = document.createElement("template"); + template.innerHTML = this.getInnerHTML(); + return template.content; } /** * Internal method to compute the time to fade in / out - * @returns {number} time to fade in / out in ms + * @returns time to fade in / out in ms */ - internalGetFadeInOutTime() { + internalGetFadeInOutTime(): number { if (G_IS_DEV && globalConfig.debug.fastGameEnter) { return 1; } diff --git a/src/js/core/state_manager.js b/src/js/core/state_manager.js index 001537cd..6045675f 100644 --- a/src/js/core/state_manager.js +++ b/src/js/core/state_manager.js @@ -2,10 +2,10 @@ import { Application } from "../application"; /* typehints:end*/ +import { MOD_SIGNALS } from "../mods/mod_signals"; import { GameState } from "./game_state"; import { createLogger } from "./logging"; -import { waitNextFrame, removeAllChildren } from "./utils"; -import { MOD_SIGNALS } from "../mods/mod_signals"; +import { removeAllChildren, waitNextFrame } from "./utils"; const logger = createLogger("state_manager"); @@ -34,7 +34,7 @@ export class StateManager { // Create a dummy to retrieve the key const dummy = new stateClass(); assert(dummy instanceof GameState, "Not a state!"); - const key = dummy.getKey(); + const key = dummy.key; assert(!this.stateClasses[key], `State '${key}' is already registered!`); this.stateClasses[key] = stateClass; } @@ -61,7 +61,7 @@ export class StateManager { } if (this.currentState) { - if (key === this.currentState.getKey()) { + if (key === this.currentState.key) { logger.error(`State '${key}' is already active!`); return false; } @@ -88,7 +88,8 @@ export class StateManager { document.body.id = "state_" + key; if (this.currentState.getRemovePreviousContent()) { - document.body.innerHTML = this.currentState.internalGetFullHtml(); + const content = this.currentState.internalGetWrappedContent(); + document.body.append(content); } const dialogParent = document.createElement("div"); diff --git a/src/js/core/textual_game_state.tsx b/src/js/core/textual_game_state.tsx index 52a1f946..95755223 100644 --- a/src/js/core/textual_game_state.tsx +++ b/src/js/core/textual_game_state.tsx @@ -1,40 +1,76 @@ import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; import { GameState } from "./game_state"; -import { T } from "../translations"; /** * Baseclass for all game states which are structured similary: A header with back button + some * scrollable content. */ -export class TextualGameState extends GameState { - ///// INTERFACE //// +export abstract class TextualGameState extends GameState { + private backToStateId: string | null = null; + private backToStatePayload: {} | null = null; + + protected headerElement: HTMLElement; + protected containerElement: HTMLElement; + protected dialogs: HUDModalDialogs; /** * Should return the states inner html. If not overriden, will create a scrollable container * with the content of getMainContentHTML() - * @returns {string} + * @deprecated */ - getInnerHTML() { - return ` -
- ${this.getMainContentHTML()} -
- `; + getInnerHTML(): string { + return ""; } /** * Should return the states HTML content. + * @deprecated */ - getMainContentHTML() { + getMainContentHTML(): string { return ""; } + protected override getContentLayout(): Node { + let content = this.getInitialContent(); + + if (content === null) { + // Fall back either to getMainContentHTML or getInnerHTML (if not "") + let html = this.getInnerHTML(); + if (html === "") { + html = ` +
+ ${this.getMainContentHTML()} +
+ `; + } + + const template = document.createElement("template"); + template.innerHTML = html; + content = template.content; + } + + return ( + <> +
+

+ + {this.getStateHeaderTitle() ?? ""} +

+
+
{content}
+ + ); + } + + protected getInitialContent(): Node { + return null; + } + /** * Should return the title of the game state. If null, no title and back button will * get created - * @returns {string|null} */ - getStateHeaderTitle() { + protected getStateHeaderTitle(): string | null { return null; } @@ -44,7 +80,7 @@ export class TextualGameState extends GameState { * Back button handler, can be overridden. Per default it goes back to the main menu, * or if coming from the game it moves back to the game again. */ - onBackButton() { + override onBackButton() { if (this.backToStateId) { this.moveToState(this.backToStateId, this.backToStatePayload); } else { @@ -61,9 +97,9 @@ export class TextualGameState extends GameState { /** * Goes to a new state, telling him to go back to this state later - * @param {string} stateId + * @param stateId */ - moveToStateAddGoBack(stateId) { + moveToStateAddGoBack(stateId: string) { this.moveToState(stateId, { backToStateId: this.key, backToStatePayload: { @@ -89,43 +125,20 @@ export class TextualGameState extends GameState { } } - /** - * Overrides the GameState implementation to provide our own html - */ - internalGetFullHtml() { - let headerHtml = ""; - if (this.getStateHeaderTitle()) { - headerHtml = ` -
- -

${this.getStateHeaderTitle()}

-
`; - } - - return ` - ${headerHtml} -
- ${this.getInnerHTML()} - -
- `; - } - //// INTERNALS ///// /** * Overrides the GameState leave callback to cleanup stuff */ - internalLeaveCallback() { + override internalLeaveCallback() { super.internalLeaveCallback(); this.dialogs.cleanup(); } /** * Overrides the GameState enter callback to setup required stuff - * @param {any} payload */ - internalEnterCallback(payload) { + override internalEnterCallback(payload: any) { super.internalEnterCallback(payload, false); if (payload.backToStateId) { this.backToStateId = payload.backToStateId; From 0fe28a7cd1ec5609816a6bcdc8922430c65c6fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sun, 21 Jul 2024 03:08:17 +0300 Subject: [PATCH 09/11] [TS] Rename ModsState --- src/js/states/{mods.js => mods.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/js/states/{mods.js => mods.tsx} (100%) diff --git a/src/js/states/mods.js b/src/js/states/mods.tsx similarity index 100% rename from src/js/states/mods.js rename to src/js/states/mods.tsx From ec8a6dec1871e56dac7bfead804a590870f739d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sun, 21 Jul 2024 03:56:50 +0300 Subject: [PATCH 10/11] Rewrite mods state Most existing mods aren't going to work in CE and the system will be reworked some time later, so this will work for now. --- src/css/states/mods.scss | 144 +++++---------------------------------- src/js/states/mods.tsx | 113 ++++++------------------------ 2 files changed, 39 insertions(+), 218 deletions(-) diff --git a/src/css/states/mods.scss b/src/css/states/mods.scss index 60912510..cc8c1fdd 100644 --- a/src/css/states/mods.scss +++ b/src/css/states/mods.scss @@ -1,144 +1,34 @@ #state_ModsState { - .mainContent { - display: flex; - flex-direction: column; - } - - > .headerBar { + .modsGrid { display: grid; - grid-template-columns: 1fr auto; - align-items: center; + grid-template-columns: repeat(3, 1fr); + gap: 0.5em; - > h1 { - justify-self: start; - } - - .openModsFolder { - background-color: $modsColor; + .noMods { + grid-template-columns: unset; + place-items: center; } } - .noModSupport { + .mod { display: flex; - align-items: center; - justify-content: center; - height: 100%; flex-direction: column; - text-align: center; - max-width: 80%; - align-self: center; - - .steamLink { - @include S(height, 50px); - @include S(width, 220px); - background: #171a23 center center / contain no-repeat; - overflow: hidden; - display: block; - text-indent: -999em; - cursor: pointer; - @include S(margin-top, 30px); - pointer-events: all; - transition: all 0.12s ease-in; - transition-property: opacity, transform; - - box-shadow: 0 D(3px) D(10px) rgba(96, 163, 136, 0.5); - @include S(border-radius, $globalBorderRadius); - - &:hover { - opacity: 0.9; - } - } - } - - .modsStats { + padding: 0.6em; + gap: 0.3em; + background-color: $mainBgColor; + @include S(border-radius, 0.75 * $globalBorderRadius); @include PlainText; - color: $accentColorDark; - &.noMods { - @include S(width, 400px); - align-self: center; - justify-self: center; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; + .title b { @include Text; - @include S(margin-top, 100px); - color: lighten($accentColorDark, 15); - - button { - @include S(margin-top, 10px); - @include S(padding, 10px, 20px); - } - - &::before { - @include S(margin-bottom, 15px); - content: ""; - @include S(width, 50px); - @include S(height, 50px); - background-position: center center; - background-size: contain; - opacity: 0.2; - } - &::before { - background-image: uiResource("res/ui/icons/mods.png") !important; - } } - } - .modsList { - @include S(margin-top, 10px); - overflow-y: scroll; - pointer-events: all; - @include S(padding-right, 5px); - flex-grow: 1; + .advanced { + @include SuperSmallText; + } - .mod { - @include S(border-radius, $globalBorderRadius); - background: #eeeff4; - @include S(margin-bottom, 4px); - @include S(padding, 7px, 10px); - @include S(grid-gap, 15px); - display: grid; - grid-template-columns: 1fr D(100px) D(80px) D(50px); - - @include DarkThemeOverride { - background: darken($darkModeControlsBackground, 5); - } - - .checkbox { - align-self: center; - justify-self: center; - } - - .mainInfo { - display: flex; - flex-direction: column; - - .description { - @include PlainText; - @include S(margin-top, 5px); - color: $accentColorDark; - } - .website { - text-transform: uppercase; - align-self: start; - @include PlainText; - @include S(margin-top, 5px); - } - } - - .version, - .author { - display: flex; - flex-direction: column; - align-self: center; - strong { - text-transform: uppercase; - color: $accentColorDark; - @include SuperSmallText; - } - } + @include DarkThemeOverride { + background-color: $darkModeGameBackground; } } } diff --git a/src/js/states/mods.tsx b/src/js/states/mods.tsx index 2cb41109..eff89c36 100644 --- a/src/js/states/mods.tsx +++ b/src/js/states/mods.tsx @@ -1,6 +1,6 @@ -import { THIRDPARTY_URLS } from "../core/config"; +import { Mod } from "@/mods/mod"; +import { MODS } from "@/mods/modloader"; import { TextualGameState } from "../core/textual_game_state"; -import { MODS } from "../mods/modloader"; import { T } from "../translations"; export class ModsState extends TextualGameState { @@ -12,104 +12,35 @@ export class ModsState extends TextualGameState { return T.mods.title; } - get modsSupported() { - return true; - } + protected getInitialContent() { + const modElements = MODS.mods.map(mod => this.getModElement(mod)); - internalGetFullHtml() { - let headerHtml = ` -
-

${this.getStateHeaderTitle()}

- -
- ${ - MODS.mods.length > 0 - ? `` - : "" - } - + return ( +
+
+ {MODS.anyModsActive() ? modElements : this.getNoModsMessage()}
- -
`; - - return ` - ${headerHtml} -
- ${this.getInnerHTML()}
- `; + ); } - getMainContentHTML() { - if (MODS.mods.length === 0) { - return ` - -
- ${T.mods.modsInfo} - - -
- - `; - } - - let modsHtml = ``; - - MODS.mods.forEach(mod => { - modsHtml += ` -
-
- ${mod.metadata.name} - ${mod.metadata.description} - ${T.mods.modWebsite} -
- ${T.mods.version}${mod.metadata.version} - ${T.mods.author}${mod.metadata.author} -
- -
- + private getModElement(mod: Mod): HTMLElement { + // TODO: Ensure proper design and localization once mods are reworked + return ( +
+
+ {mod.metadata.name} by {mod.metadata.author} +
+
{mod.metadata.description}
+
+ {mod.metadata.id} @ {mod.metadata.version}
- `; - }); - return ` - -
- ${T.mods.modsInfo}
- -
- ${modsHtml} -
- `; + ); } - onEnter() { - const openModsFolder = this.htmlElement.querySelector(".openModsFolder"); - if (openModsFolder) { - this.trackClicks(openModsFolder, this.openModsFolder); - } - const browseMods = this.htmlElement.querySelector(".browseMods"); - if (browseMods) { - this.trackClicks(browseMods, this.openBrowseMods); - } - - const checkboxes = this.htmlElement.querySelectorAll(".checkbox"); - Array.from(checkboxes).forEach(checkbox => { - this.trackClicks(checkbox, this.showModTogglingComingSoon); - }); - } - - showModTogglingComingSoon() { - this.dialogs.showWarning(T.mods.togglingComingSoon.title, T.mods.togglingComingSoon.description); - } - - openModsFolder() { - ipcRenderer.invoke("open-mods-folder"); - } - - openBrowseMods() { - this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.modBrowser); + private getNoModsMessage(): HTMLElement { + return
No mods are currently installed.
; } getDefaultPreviousState() { From 8955e37c78bad0b93ace6f5cad4189fe777ca412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=97=D0=BB=20=D0=93=D1=80=D0=B8?= =?UTF-8?q?=D0=B3=D0=BE=D1=80=27=D1=94=D0=B2?= Date: Sat, 21 Sep 2024 21:41:12 +0300 Subject: [PATCH 11/11] Simplify GameState legacy HTML string support Move the getInnerHTML fallback to the default implementation of getContentLayout; this allows for code reuse in TextualGameState too. --- src/js/core/game_state.ts | 14 +++++--------- src/js/core/textual_game_state.tsx | 9 ++++++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/js/core/game_state.ts b/src/js/core/game_state.ts index 9d58c380..1a452ca1 100644 --- a/src/js/core/game_state.ts +++ b/src/js/core/game_state.ts @@ -200,10 +200,12 @@ export class GameState { /** * Should return the element(s) to be displayed in the state. - * If null, {@link getInnerHTML} will be used instead. + * If not overridden, {@link getInnerHTML} will be used to provide the layout. */ protected getContentLayout(): Node { - return null; + const template = document.createElement("template"); + template.innerHTML = this.getInnerHTML(); + return template.content; } /** @@ -320,9 +322,6 @@ export class GameState { */ internalGetWrappedContent(): Node { const elements = this.getContentLayout(); - if (elements instanceof Node) { - return elements; - } if (Array.isArray(elements)) { const fragment = document.createDocumentFragment(); @@ -330,10 +329,7 @@ export class GameState { return fragment; } - // Fall back to deprecated HTML strings solution - const template = document.createElement("template"); - template.innerHTML = this.getInnerHTML(); - return template.content; + return elements; } /** diff --git a/src/js/core/textual_game_state.tsx b/src/js/core/textual_game_state.tsx index 95755223..7462769e 100644 --- a/src/js/core/textual_game_state.tsx +++ b/src/js/core/textual_game_state.tsx @@ -30,6 +30,11 @@ export abstract class TextualGameState extends GameState { return ""; } + /** + * Should return the element(s) to be displayed in the state. + * If not overridden, a default layout consisting of a back button, + * title, and content returned by {@link getInitialContent}. + */ protected override getContentLayout(): Node { let content = this.getInitialContent(); @@ -44,9 +49,7 @@ export abstract class TextualGameState extends GameState { `; } - const template = document.createElement("template"); - template.innerHTML = html; - content = template.content; + content = super.getContentLayout(); } return (