1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-13 18:21:51 +00:00
tobspr_shapez.io/src/js/states/ingame.js
Даниїл Григор'єв aa49f063c3
Remove ads, analytics and Steam SSO, simplify HTML tasks (#21)
* Remove ad support, analytics and Wegame leftovers

The game may be somewhat broken in a few places, but it doesn't matter
for now. This is still not the end.

* Remove Steam SSO and demo stuff

Steam SSO is completely removed, a few things from demo like simplified
level sets are gone as well. Puzzle DLC on the other hand is now always
"owned" and will ask for a token to log in.

Removes

* Use shapez dialogs for Puzzle DLC token input

Yes, this sucks *a lot*. But it's a temporary measure, trust me :P

* Simplify HTML tasks

Removes the web (demo) index.html page and makes HTML tasks independent
of the build variant. This might not be the best solution, but it works
for now.
2024-04-16 10:25:16 +03:00

517 lines
15 KiB
JavaScript

import { GameState } from "../core/game_state";
import { logSection, createLogger } from "../core/logging";
import { waitNextFrame } from "../core/utils";
import { globalConfig } from "../core/config";
import { GameLoadingOverlay } from "../game/game_loading_overlay";
import { KeyActionMapper } from "../game/key_action_mapper";
import { Savegame } from "../savegame/savegame";
import { GameCore } from "../game/core";
import { MUSIC } from "../platform/sound";
import { enumGameModeIds } from "../game/game_mode";
import { MOD_SIGNALS } from "../mods/mod_signals";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { T } from "../translations";
const logger = createLogger("state/ingame");
// Different sub-states
export const GAME_LOADING_STATES = {
s3_createCore: "s3_createCore",
s4_A_initEmptyGame: "s4_A_initEmptyGame",
s4_B_resumeGame: "s4_B_resumeGame",
s5_firstUpdate: "s5_firstUpdate",
s6_postLoadHook: "s6_postLoadHook",
s7_warmup: "s7_warmup",
s10_gameRunning: "s10_gameRunning",
leaving: "leaving",
destroyed: "destroyed",
initFailed: "initFailed",
};
export const gameCreationAction = {
new: "new-game",
resume: "resume-game",
};
// Typehints
export class GameCreationPayload {
constructor() {
/** @type {boolean|undefined} */
this.fastEnter;
/** @type {string} */
this.gameModeId;
/** @type {Savegame} */
this.savegame;
/** @type {object|undefined} */
this.gameModeParameters;
}
}
export class InGameState extends GameState {
constructor() {
super("InGameState");
/** @type {GameCreationPayload} */
this.creationPayload = null;
// Stores current stage
this.stage = "";
/** @type {GameCore} */
this.core = null;
/** @type {KeyActionMapper} */
this.keyActionMapper = null;
/** @type {GameLoadingOverlay} */
this.loadingOverlay = null;
/** @type {Savegame} */
this.savegame = null;
this.boundInputFilter = this.filterInput.bind(this);
/**
* Whether we are currently saving the game
* @TODO: This doesn't realy fit here
*/
this.currentSavePromise = null;
}
get dialogs() {
return this.core.root.hud.parts.dialogs;
}
/**
* Switches the game into another sub-state
* @param {string} stage
*/
switchStage(stage) {
assert(stage, "Got empty stage");
if (stage !== this.stage) {
this.stage = stage;
logger.log(this.stage);
MOD_SIGNALS.gameLoadingStageEntered.dispatch(this, stage);
return true;
} else {
// log(this, "Re entering", stage);
return false;
}
}
// GameState implementation
getInnerHTML() {
return "";
}
onAppPause() {
// if (this.stage === stages.s10_gameRunning) {
// logger.log("Saving because app got paused");
// this.doSave();
// }
}
getHasFadeIn() {
return false;
}
getPauseOnFocusLost() {
return false;
}
getHasUnloadConfirmation() {
return true;
}
onLeave() {
if (this.core) {
this.stageDestroyed();
}
this.app.inputMgr.dismountFilter(this.boundInputFilter);
}
onResized(w, h) {
super.onResized(w, h);
if (this.stage === GAME_LOADING_STATES.s10_gameRunning) {
this.core.resize(w, h);
}
}
// ---- End of GameState implementation
/**
* Goes back to the menu state
*/
goBackToMenu() {
if ([enumGameModeIds.puzzleEdit, enumGameModeIds.puzzlePlay].includes(this.gameModeId)) {
this.saveThenGoToState("PuzzleMenuState");
} else {
this.saveThenGoToState("MainMenuState");
}
}
/**
* Goes back to the settings state
*/
goToSettings() {
this.saveThenGoToState("SettingsState", {
backToStateId: this.key,
backToStatePayload: this.creationPayload,
});
}
/**
* Goes back to the settings state
*/
goToKeybindings() {
this.saveThenGoToState("KeybindingsState", {
backToStateId: this.key,
backToStatePayload: this.creationPayload,
});
}
/**
* Moves to a state outside of the game
* @param {string} stateId
* @param {any=} payload
*/
saveThenGoToState(stateId, payload) {
if (this.stage === GAME_LOADING_STATES.leaving || this.stage === GAME_LOADING_STATES.destroyed) {
logger.warn(
"Tried to leave game twice or during destroy:",
this.stage,
"(attempted to move to",
stateId,
")"
);
return;
}
this.stageLeavingGame();
this.doSave().then(() => {
this.stageDestroyed();
this.moveToState(stateId, payload);
});
}
onBackButton() {
// do nothing
}
getIsIngame() {
return (
this.stage === GAME_LOADING_STATES.s10_gameRunning &&
this.core &&
!this.core.root.hud.shouldPauseGame()
);
}
/**
* Called when the game somehow failed to initialize. Resets everything to basic state and
* then goes to the main menu, showing the error
* @param {string} err
*/
onInitializationFailure(err) {
if (this.switchStage(GAME_LOADING_STATES.initFailed)) {
logger.error("Init failure:", err);
this.stageDestroyed();
this.moveToState("MainMenuState", { loadError: err });
}
}
// STAGES
/**
* Creates the game core instance, and thus the root
*/
stage3CreateCore() {
if (this.switchStage(GAME_LOADING_STATES.s3_createCore)) {
logger.log("Waiting for resources to load");
this.app.backgroundResourceLoader.resourceStateChangedSignal.add(({ progress }) => {
this.loadingOverlay.loadingIndicator.innerText = T.global.loadingResources.replace(
"<percentage>",
(progress * 100.0).toFixed(1)
);
});
this.app.backgroundResourceLoader.getIngamePromise().then(
() => {
if (
this.creationPayload.gameModeId &&
this.creationPayload.gameModeId.includes("puzzle")
) {
this.app.sound.playThemeMusic(MUSIC.puzzle);
} else {
this.app.sound.playThemeMusic(MUSIC.theme);
}
this.loadingOverlay.loadingIndicator.innerText = "";
this.app.backgroundResourceLoader.resourceStateChangedSignal.removeAll();
logger.log("Creating new game core");
this.core = new GameCore(this.app);
this.core.initializeRoot(this, this.savegame, this.gameModeId);
if (this.savegame.hasGameDump()) {
this.stage4bResumeGame();
} else {
this.stage4aInitEmptyGame();
}
},
err => {
logger.error("Failed to preload resources:", err);
const dialogs = new HUDModalDialogs(null, this.app);
const dialogsElement = document.createElement("div");
dialogsElement.id = "ingame_HUD_ModalDialogs";
dialogsElement.style.zIndex = "999999";
document.body.appendChild(dialogsElement);
dialogs.initializeToElement(dialogsElement);
this.app.backgroundResourceLoader.showLoaderError(dialogs, err);
}
);
}
}
/**
* Initializes a new empty game
*/
stage4aInitEmptyGame() {
if (this.switchStage(GAME_LOADING_STATES.s4_A_initEmptyGame)) {
this.core.initNewGame();
this.stage5FirstUpdate();
}
}
/**
* Resumes an existing game
*/
stage4bResumeGame() {
if (this.switchStage(GAME_LOADING_STATES.s4_B_resumeGame)) {
if (!this.core.initExistingGame()) {
this.onInitializationFailure("Savegame is corrupt and can not be restored.");
return;
}
this.stage5FirstUpdate();
}
}
/**
* Performs the first game update on the game which initializes most caches
*/
stage5FirstUpdate() {
if (this.switchStage(GAME_LOADING_STATES.s5_firstUpdate)) {
this.core.root.logicInitialized = true;
this.core.updateLogic();
this.stage6PostLoadHook();
}
}
/**
* Call the post load hook, this means that we have loaded the game, and all systems
* can operate and start to work now.
*/
stage6PostLoadHook() {
if (this.switchStage(GAME_LOADING_STATES.s6_postLoadHook)) {
logger.log("Post load hook");
this.core.postLoadHook();
this.stage7Warmup();
}
}
/**
* This makes the game idle and draw for a while, because we run most code this way
* the V8 engine can already start to optimize it. Also this makes sure the resources
* are in the VRAM and we have a smooth experience once we start.
*/
stage7Warmup() {
if (this.switchStage(GAME_LOADING_STATES.s7_warmup)) {
if (this.creationPayload.fastEnter) {
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
} else {
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
}
}
}
/**
* The final stage where this game is running and updating regulary.
*/
stage10GameRunning() {
if (this.switchStage(GAME_LOADING_STATES.s10_gameRunning)) {
this.core.root.signals.readyToRender.dispatch();
logSection("GAME STARTED", "#26a69a");
// Initial resize, might have changed during loading (this is possible)
this.core.resize(this.app.screenWidth, this.app.screenHeight);
MOD_SIGNALS.gameStarted.dispatch(this.core.root);
}
}
/**
* This stage destroys the whole game, used to cleanup
*/
stageDestroyed() {
if (this.switchStage(GAME_LOADING_STATES.destroyed)) {
// Cleanup all api calls
this.cancelAllAsyncOperations();
if (this.syncer) {
this.syncer.cancelSync();
this.syncer = null;
}
// Cleanup core
if (this.core) {
this.core.destruct();
this.core = null;
}
}
}
/**
* When leaving the game
*/
stageLeavingGame() {
if (this.switchStage(GAME_LOADING_STATES.leaving)) {
// ...
}
}
// END STAGES
/**
* Filters the input (keybindings)
*/
filterInput() {
return this.stage === GAME_LOADING_STATES.s10_gameRunning;
}
/**
* @param {GameCreationPayload} payload
*/
onEnter(payload) {
this.app.inputMgr.installFilter(this.boundInputFilter);
this.creationPayload = payload;
this.savegame = payload.savegame;
this.gameModeId = payload.gameModeId;
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
this.loadingOverlay.showBasic();
// Remove unneded default element
document.body.querySelector(".modalDialogParent").remove();
this.asyncChannel
.watch(waitNextFrame())
.then(() => this.stage3CreateCore())
.catch(ex => {
logger.error(ex);
throw ex;
});
}
/**
* Render callback
* @param {number} dt
*/
onRender(dt) {
if (window.APP_ERROR_OCCURED) {
// Application somehow crashed, do not do anything
return;
}
if (this.stage === GAME_LOADING_STATES.s7_warmup) {
this.core.draw();
this.warmupTimeSeconds -= dt / 1000.0;
if (this.warmupTimeSeconds < 0) {
logger.log("Warmup completed");
this.stage10GameRunning();
}
}
if (this.stage === GAME_LOADING_STATES.s10_gameRunning) {
this.core.tick(dt);
}
// If the stage is still active (This might not be the case if tick() moved us to game over)
if (this.stage === GAME_LOADING_STATES.s10_gameRunning) {
// Only draw if page visible
if (this.app.pageVisible) {
this.core.draw();
}
this.loadingOverlay.removeIfAttached();
} else {
if (!this.loadingOverlay.isAttached()) {
this.loadingOverlay.showBasic();
}
}
}
onBackgroundTick(dt) {
this.onRender(dt);
}
/**
* Saves the game
*/
doSave() {
if (!this.savegame || !this.savegame.isSaveable()) {
return Promise.resolve();
}
if (window.APP_ERROR_OCCURED) {
logger.warn("skipping save because application crashed");
return Promise.resolve();
}
if (
this.stage !== GAME_LOADING_STATES.s10_gameRunning &&
this.stage !== GAME_LOADING_STATES.s7_warmup &&
this.stage !== GAME_LOADING_STATES.leaving
) {
logger.warn("Skipping save because game is not ready");
return Promise.resolve();
}
if (this.currentSavePromise) {
logger.warn("Skipping double save and returning same promise");
return this.currentSavePromise;
}
if (!this.core.root.gameMode.getIsSaveable()) {
return Promise.resolve();
}
logger.log("Starting to save game ...");
this.savegame.updateData(this.core.root);
this.currentSavePromise = this.savegame
.writeSavegameAndMetadata()
.catch(err => {
// Catch errors
logger.warn("Failed to save:", err);
})
.then(() => {
// Clear promise
logger.log("Saved!");
this.core.root.signals.gameSaved.dispatch();
this.currentSavePromise = null;
});
return this.currentSavePromise;
}
}