diff --git a/gulp/webpack.config.js b/gulp/webpack.config.js index cac0161b..71b06a64 100644 --- a/gulp/webpack.config.js +++ b/gulp/webpack.config.js @@ -87,7 +87,7 @@ export default { fallback: { fs: false }, alias: { "global-compression": resolve("../src/js/core/lzstring.js"), - "root": resolve("../src/js/"), + "@": 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 4962843a..0cba84a3 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", @@ -96,7 +96,7 @@ export default { fallback: { fs: false }, alias: { "global-compression": resolve("../src/js/core/lzstring.js"), - "root": resolve("../src/js/"), + "@": resolve("../src/js/"), }, fullySpecified: false, extensions: [".ts", ".js", ".tsx", ".jsx"], 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/core/game_state.js b/src/js/core/game_state.ts similarity index 68% rename from src/js/core/game_state.js rename to src/js/core/game_state.ts index 6f276e99..1a452ca1 100644 --- a/src/js/core/game_state.js +++ b/src/js/core/game_state.ts @@ -1,15 +1,12 @@ -/* typehints:start */ -import { Application } from "../application"; -import { StateManager } from "./state_manager"; -/* typehints:end */ - -import { globalConfig } from "./config"; +import { MUSIC } from "@/platform/sound"; +import type { Application } from "../application"; import { ClickDetector } from "./click_detector"; -import { logSection, createLogger } from "./logging"; +import { globalConfig } from "./config"; import { InputReceiver } from "./input_receiver"; -import { waitNextFrame } from "./utils"; +import { createLogger, logSection } from "./logging"; import { RequestChannel } from "./request_channel"; -import { MUSIC } from "../platform/sound"; +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.inputReciever = new InputReceiver("state-" + key); - this.inputReciever.backButton.add(this.onBackButton, this); - - // A channel we can use to perform async ops - this.asyncChannel = new RequestChannel(); + this.inputReceiver = new InputReceiver("state-" + key); + this.inputReceiver.backButton.add(this.onBackButton, this); } //// 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,45 @@ 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 not overridden, {@link getInnerHTML} will be used to provide the layout. + */ + protected getContentLayout(): Node { + const template = document.createElement("template"); + template.innerHTML = this.getInnerHTML(); + return template.content; + } + /** * Returns if the state has an unload confirmation, this is the * "Are you sure you want to leave the page" message. @@ -223,25 +218,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 +243,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,12 +253,12 @@ 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.pushReciever(this.inputReciever); + this.app.inputMgr.pushReceiver(this.inputReceiver); this.htmlElement = this.getDivElement(); this.htmlElement.classList.add("active"); @@ -293,7 +284,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(); } @@ -325,18 +316,27 @@ 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 (Array.isArray(elements)) { + const fragment = document.createDocumentFragment(); + fragment.append(...(elements as Node[])); + return fragment; + } + + return elements; } /** * 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/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/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.js b/src/js/core/textual_game_state.tsx similarity index 64% rename from src/js/core/textual_game_state.js rename to src/js/core/textual_game_state.tsx index 52a1f946..7462769e 100644 --- a/src/js/core/textual_game_state.js +++ b/src/js/core/textual_game_state.tsx @@ -1,40 +1,79 @@ 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 ""; } + /** + * 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(); + + if (content === null) { + // Fall back either to getMainContentHTML or getInnerHTML (if not "") + let html = this.getInnerHTML(); + if (html === "") { + html = ` +
+ ${this.getMainContentHTML()} +
+ `; + } + + content = super.getContentLayout(); + } + + 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 +83,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 +100,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 +128,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; 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/globals.d.ts b/src/js/globals.d.ts index 7176807f..a6439358 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -149,8 +149,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; }; }; /** @@ -164,14 +164,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 }; 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"; 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); diff --git a/src/js/states/mods.js b/src/js/states/mods.js deleted file mode 100644 index 2cb41109..00000000 --- a/src/js/states/mods.js +++ /dev/null @@ -1,118 +0,0 @@ -import { THIRDPARTY_URLS } from "../core/config"; -import { TextualGameState } from "../core/textual_game_state"; -import { MODS } from "../mods/modloader"; -import { T } from "../translations"; - -export class ModsState extends TextualGameState { - constructor() { - super("ModsState"); - } - - getStateHeaderTitle() { - return T.mods.title; - } - - get modsSupported() { - return true; - } - - internalGetFullHtml() { - let headerHtml = ` -
-

${this.getStateHeaderTitle()}

- -
- ${ - MODS.mods.length > 0 - ? `` - : "" - } - -
- -
`; - - 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} -
- -
- -
- `; - }); - 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); - } - - getDefaultPreviousState() { - return "SettingsState"; - } -} diff --git a/src/js/states/mods.tsx b/src/js/states/mods.tsx new file mode 100644 index 00000000..eff89c36 --- /dev/null +++ b/src/js/states/mods.tsx @@ -0,0 +1,49 @@ +import { Mod } from "@/mods/mod"; +import { MODS } from "@/mods/modloader"; +import { TextualGameState } from "../core/textual_game_state"; +import { T } from "../translations"; + +export class ModsState extends TextualGameState { + constructor() { + super("ModsState"); + } + + getStateHeaderTitle() { + return T.mods.title; + } + + protected getInitialContent() { + const modElements = MODS.mods.map(mod => this.getModElement(mod)); + + return ( +
+
+ {MODS.anyModsActive() ? modElements : this.getNoModsMessage()} +
+
+ ); + } + + 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} +
+
+ ); + } + + private getNoModsMessage(): HTMLElement { + return
No mods are currently installed.
; + } + + getDefaultPreviousState() { + return "SettingsState"; + } +}