mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-11 09:11:50 +00:00
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.
This commit is contained in:
parent
a2b21cc6dd
commit
69ce8ffc17
@ -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<ClickDetector>} */
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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 `
|
||||
<div class="content mainContent">
|
||||
${this.getMainContentHTML()}
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div class="content mainContent">
|
||||
${this.getMainContentHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = html;
|
||||
content = template.content;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="headerBar">
|
||||
<h1>
|
||||
<button class="backButton"></button>
|
||||
{this.getStateHeaderTitle() ?? ""}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="container">{content}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="headerBar">
|
||||
|
||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
${headerHtml}
|
||||
<div class="container">
|
||||
${this.getInnerHTML()}
|
||||
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
//// 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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user