mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-11 09:11:50 +00:00
Merge pull request #39 from tobspr-games/dengr1065/textual-game-state-work
Basic support for JSX/TSX in GameStates
This commit is contained in:
commit
7dac0baa6b
@ -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"],
|
||||
|
||||
@ -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"],
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<ClickDetector>} */
|
||||
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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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<T extends string = never, U extends unknown[] = []> {
|
||||
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<T extends string = never, U extends unknown[] = []> {
|
||||
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<T extends string = never, U extends unknown[] = []> {
|
||||
}
|
||||
|
||||
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<T extends string = never, U extends unknown[] = []> {
|
||||
});
|
||||
|
||||
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<T extends string = never, U extends unknown[] = []> {
|
||||
}
|
||||
|
||||
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<T extends string = never, U extends unknown[] = []> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -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,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 `
|
||||
<div class="content mainContent">
|
||||
${this.getMainContentHTML()}
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div class="content mainContent">
|
||||
${this.getMainContentHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
content = super.getContentLayout();
|
||||
}
|
||||
|
||||
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 +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 = `
|
||||
<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;
|
||||
@ -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]
|
||||
*/
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>",
|
||||
("" + 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;
|
||||
|
||||
@ -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.<string, Keybinding>} */
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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) {
|
||||
|
||||
8
src/js/globals.d.ts
vendored
8
src/js/globals.d.ts
vendored
@ -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<T extends Props> = {
|
||||
(props: T): Element;
|
||||
};
|
||||
|
||||
/**
|
||||
* A child of a JSX element.
|
||||
*/
|
||||
type Node = Element | string | boolean | null | undefined;
|
||||
type JSXNode = Node | string | boolean | null | undefined;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
function isDisplayed(node: JSX.Node): node is Exclude<JSX.Node, boolean | null | undefined> {
|
||||
function isDisplayed(node: JSX.JSXNode): node is Exclude<JSX.JSXNode, boolean | null | undefined> {
|
||||
return typeof node !== "boolean" && node != null;
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ function jsx<U extends JSX.Props>(
|
||||
}
|
||||
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<U extends JSX.Props>(
|
||||
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 };
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 = `
|
||||
<div class="headerBar">
|
||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||
|
||||
<div class="actions">
|
||||
${
|
||||
MODS.mods.length > 0
|
||||
? `<button class="styledButton browseMods">${T.mods.browseMods}</button>`
|
||||
: ""
|
||||
}
|
||||
<button class="styledButton openModsFolder">${T.mods.openFolder}</button>
|
||||
</div>
|
||||
|
||||
</div>`;
|
||||
|
||||
return `
|
||||
${headerHtml}
|
||||
<div class="container">
|
||||
${this.getInnerHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getMainContentHTML() {
|
||||
if (MODS.mods.length === 0) {
|
||||
return `
|
||||
|
||||
<div class="modsStats noMods">
|
||||
${T.mods.modsInfo}
|
||||
|
||||
<button class="styledButton browseMods">${T.mods.browseMods}</button>
|
||||
</div>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
let modsHtml = ``;
|
||||
|
||||
MODS.mods.forEach(mod => {
|
||||
modsHtml += `
|
||||
<div class="mod">
|
||||
<div class="mainInfo">
|
||||
<span class="name">${mod.metadata.name}</span>
|
||||
<span class="description">${mod.metadata.description}</span>
|
||||
<a class="website" href="${mod.metadata.website}" target="_blank">${T.mods.modWebsite}</a>
|
||||
</div>
|
||||
<span class="version"><strong>${T.mods.version}</strong>${mod.metadata.version}</span>
|
||||
<span class="author"><strong>${T.mods.author}</strong>${mod.metadata.author}</span>
|
||||
<div class="value checkbox checked">
|
||||
<span class="knob"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
return `
|
||||
|
||||
<div class="modsStats">
|
||||
${T.mods.modsInfo}
|
||||
</div>
|
||||
|
||||
<div class="modsList">
|
||||
${modsHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
49
src/js/states/mods.tsx
Normal file
49
src/js/states/mods.tsx
Normal file
@ -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 (
|
||||
<div class="content">
|
||||
<div class={`modsGrid${MODS.anyModsActive() ? "" : " noMods"}`}>
|
||||
{MODS.anyModsActive() ? modElements : this.getNoModsMessage()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getModElement(mod: Mod): HTMLElement {
|
||||
// TODO: Ensure proper design and localization once mods are reworked
|
||||
return (
|
||||
<div class="mod">
|
||||
<div class="title">
|
||||
<b>{mod.metadata.name}</b> by <i>{mod.metadata.author}</i>
|
||||
</div>
|
||||
<div class="description">{mod.metadata.description}</div>
|
||||
<div class="advanced">
|
||||
{mod.metadata.id} @ {mod.metadata.version}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getNoModsMessage(): HTMLElement {
|
||||
return <div class="noModsMessage">No mods are currently installed.</div>;
|
||||
}
|
||||
|
||||
getDefaultPreviousState() {
|
||||
return "SettingsState";
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user