1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-02-12 10:59:23 +00:00

Add seed to GameCreationPayload

This commit is contained in:
Daan Breur 2021-11-13 14:41:04 +01:00
parent a7a2aad2b6
commit 5a1c24c9df

View File

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