mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-16 11:41:50 +00:00
Merge dfe90426ce into a7a2aad2b6
This commit is contained in:
commit
35aba7606a
@ -214,6 +214,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
@include S(margin-right, 7.5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsFormElem {
|
||||||
|
> .object {
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
> summary {
|
||||||
|
transition: opacity 0.1s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: all;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> div {
|
||||||
|
@include S(margin-left, 4px);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .buttons {
|
> .buttons {
|
||||||
|
|||||||
@ -47,13 +47,67 @@ export class FormElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FormElementDetails extends FormElement {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object} param0
|
||||||
|
* @param {string} param0.id
|
||||||
|
* @param {string} param0.label
|
||||||
|
* @param {Array<FormElement>} param0.formElements
|
||||||
|
*/
|
||||||
|
constructor({ id, label, formElements }) {
|
||||||
|
super(id, label);
|
||||||
|
this.formElements = formElements;
|
||||||
|
|
||||||
|
this.element = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHtml() {
|
||||||
|
return `<div class="formElement detailsFormElem"><details class='object'>
|
||||||
|
${this.label ? `<summary>${this.label}</summary>` : ""}
|
||||||
|
<div class="content">
|
||||||
|
${this.formElements.map(e => e.getHtml()).join("")}
|
||||||
|
</div></details></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents(parent, clickTrackers) {
|
||||||
|
this.element = this.getFormElement(parent);
|
||||||
|
|
||||||
|
for (let i = 0; i < this.formElements.length; ++i) {
|
||||||
|
const elem = this.formElements[i];
|
||||||
|
elem.bindEvents(parent, clickTrackers);
|
||||||
|
elem.valueChosen.add(this.valueChosen.dispatch, this.valueChosen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue() {
|
||||||
|
let formElementValues = {};
|
||||||
|
for (let i = 0; i < this.formElements.length; ++i) {
|
||||||
|
const elem = this.formElements[i];
|
||||||
|
formElementValues[elem.id] = elem.getValue();
|
||||||
|
}
|
||||||
|
return formElementValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {}
|
||||||
|
}
|
||||||
|
|
||||||
export class FormElementInput extends FormElement {
|
export class FormElementInput extends FormElement {
|
||||||
constructor({ id, label = null, placeholder, defaultValue = "", inputType = "text", validator = null }) {
|
constructor({
|
||||||
|
id,
|
||||||
|
label = null,
|
||||||
|
placeholder,
|
||||||
|
defaultValue = "",
|
||||||
|
inputType = "text",
|
||||||
|
validator = null,
|
||||||
|
inline = false,
|
||||||
|
}) {
|
||||||
super(id, label);
|
super(id, label);
|
||||||
this.placeholder = placeholder;
|
this.placeholder = placeholder;
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
this.inputType = inputType;
|
this.inputType = inputType;
|
||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
|
this.inline = inline;
|
||||||
|
|
||||||
this.element = null;
|
this.element = null;
|
||||||
}
|
}
|
||||||
@ -83,7 +137,7 @@ export class FormElementInput extends FormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="formElement input">
|
<div class="formElement input ${this.inline ? "inline" : ""}">
|
||||||
${this.label ? `<label>${this.label}</label>` : ""}
|
${this.label ? `<label>${this.label}</label>` : ""}
|
||||||
<input
|
<input
|
||||||
type="${inputType}"
|
type="${inputType}"
|
||||||
@ -143,21 +197,22 @@ export class FormElementInput extends FormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FormElementCheckbox extends FormElement {
|
export class FormElementCheckbox extends FormElement {
|
||||||
constructor({ id, label, defaultValue = true }) {
|
constructor({ id, label, defaultValue = true, inline = false }) {
|
||||||
super(id, label);
|
super(id, label);
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
this.value = this.defaultValue;
|
this.value = this.defaultValue;
|
||||||
|
this.inline = inline;
|
||||||
|
|
||||||
this.element = null;
|
this.element = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHtml() {
|
getHtml() {
|
||||||
return `
|
return `
|
||||||
<div class="formElement checkBoxFormElem">
|
<div class="formElement checkBoxFormElem ${this.inline ? "inline" : ""}">
|
||||||
${this.label ? `<label>${this.label}</label>` : ""}
|
${this.label ? `<label>${this.label}</label>` : ""}
|
||||||
<div class="checkbox ${this.defaultValue ? "checked" : ""}" data-formId='${this.id}'>
|
<div class="checkbox ${this.defaultValue ? "checked" : ""}" data-formId='${this.id}'>
|
||||||
<span class="knob"></span >
|
<span class="knob"></span>
|
||||||
</div >
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -181,7 +236,7 @@ export class FormElementCheckbox extends FormElement {
|
|||||||
this.element.classList.toggle("checked", this.value);
|
this.element.classList.toggle("checked", this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
focus(parent) {}
|
focus() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormElementItemChooser extends FormElement {
|
export class FormElementItemChooser extends FormElement {
|
||||||
|
|||||||
1158
src/js/game/core.js
1158
src/js/game/core.js
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,9 @@ export class BaseMap extends BasicSerializableObject {
|
|||||||
static getSchema() {
|
static getSchema() {
|
||||||
return {
|
return {
|
||||||
seed: types.uint,
|
seed: types.uint,
|
||||||
|
allowNonPrimaryColors: types.bool,
|
||||||
|
fullShapePercentage: types.uint,
|
||||||
|
wierdShapePercentage: types.uint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +30,9 @@ export class BaseMap extends BasicSerializableObject {
|
|||||||
this.root = root;
|
this.root = root;
|
||||||
|
|
||||||
this.seed = 0;
|
this.seed = 0;
|
||||||
|
this.allowNonPrimaryColors = false;
|
||||||
|
this.fullShapePercentage = 0;
|
||||||
|
this.wierdShapePercentage = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of 'X|Y' to chunk
|
* Mapping of 'X|Y' to chunk
|
||||||
|
|||||||
@ -168,6 +168,11 @@ export class MapChunk {
|
|||||||
let availableColors = [enumColors.red, enumColors.green];
|
let availableColors = [enumColors.red, enumColors.green];
|
||||||
if (distanceToOriginInChunks > 2) {
|
if (distanceToOriginInChunks > 2) {
|
||||||
availableColors.push(enumColors.blue);
|
availableColors.push(enumColors.blue);
|
||||||
|
if (this.root.map.allowNonPrimaryColors) {
|
||||||
|
availableColors.push(enumColors.yellow);
|
||||||
|
availableColors.push(enumColors.purple);
|
||||||
|
availableColors.push(enumColors.cyan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.internalGeneratePatch(rng, colorPatchSize, COLOR_ITEM_SINGLETONS[rng.choice(availableColors)]);
|
this.internalGeneratePatch(rng, colorPatchSize, COLOR_ITEM_SINGLETONS[rng.choice(availableColors)]);
|
||||||
}
|
}
|
||||||
@ -198,7 +203,19 @@ export class MapChunk {
|
|||||||
weights[enumSubShape.windmill] = 0;
|
weights[enumSubShape.windmill] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distanceToOriginInChunks < 10) {
|
if (rng.nextRange(0, 100) <= this.root.map.fullShapePercentage) {
|
||||||
|
// Spawn full shape based on percentage.
|
||||||
|
const subShape = this.internalGenerateRandomSubShape(rng, weights);
|
||||||
|
subShapes = [subShape, subShape, subShape, subShape];
|
||||||
|
} else if (rng.nextRange(0, 100) <= this.root.map.wierdShapePercentage) {
|
||||||
|
// Spawn wierd shape based on percentage.
|
||||||
|
subShapes = [
|
||||||
|
this.internalGenerateRandomSubShape(rng, weights),
|
||||||
|
this.internalGenerateRandomSubShape(rng, weights),
|
||||||
|
this.internalGenerateRandomSubShape(rng, weights),
|
||||||
|
this.internalGenerateRandomSubShape(rng, weights),
|
||||||
|
];
|
||||||
|
} else if (distanceToOriginInChunks < 10) {
|
||||||
// Initial chunk patches always have the same shape
|
// Initial chunk patches always have the same shape
|
||||||
const subShape = this.internalGenerateRandomSubShape(rng, weights);
|
const subShape = this.internalGenerateRandomSubShape(rng, weights);
|
||||||
subShapes = [subShape, subShape, subShape, subShape];
|
subShapes = [subShape, subShape, subShape, subShape];
|
||||||
|
|||||||
@ -28,7 +28,10 @@
|
|||||||
"shape": "#5d5f6a",
|
"shape": "#5d5f6a",
|
||||||
"red": "#854f56",
|
"red": "#854f56",
|
||||||
"green": "#667964",
|
"green": "#667964",
|
||||||
"blue": "#5e7ca4"
|
"blue": "#5e7ca4",
|
||||||
|
"purple": "#8776bc",
|
||||||
|
"yellow": "#cab57d",
|
||||||
|
"cyan": "#00b5b8"
|
||||||
},
|
},
|
||||||
"chunkOverview": {
|
"chunkOverview": {
|
||||||
"empty": "#444856",
|
"empty": "#444856",
|
||||||
|
|||||||
@ -28,7 +28,10 @@
|
|||||||
"shape": "#eaebec",
|
"shape": "#eaebec",
|
||||||
"red": "#ffbfc1",
|
"red": "#ffbfc1",
|
||||||
"green": "#cbffc4",
|
"green": "#cbffc4",
|
||||||
"blue": "#bfdaff"
|
"blue": "#bfdaff",
|
||||||
|
"purple": "#ecb3fc",
|
||||||
|
"yellow": "#fcf99c",
|
||||||
|
"cyan": "#85fdff"
|
||||||
},
|
},
|
||||||
|
|
||||||
"chunkOverview": {
|
"chunkOverview": {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { SavegameInterface_V1006 } from "./schemas/1006";
|
|||||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
import { SavegameInterface_V1009 } from "./schemas/1009";
|
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||||
|
import { SavegameInterface_V1010 } from "./schemas/1010";
|
||||||
|
|
||||||
const logger = createLogger("savegame");
|
const logger = createLogger("savegame");
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static getCurrentVersion() {
|
static getCurrentVersion() {
|
||||||
return 1009;
|
return 1010;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,6 +161,11 @@ export class Savegame extends ReadWriteProxy {
|
|||||||
data.version = 1009;
|
data.version = 1009;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.version === 1009) {
|
||||||
|
SavegameInterface_V1010.migrate1009to1010(data);
|
||||||
|
data.version = 1010;
|
||||||
|
}
|
||||||
|
|
||||||
return ExplainedResult.good();
|
return ExplainedResult.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { SavegameInterface_V1006 } from "./schemas/1006";
|
|||||||
import { SavegameInterface_V1007 } from "./schemas/1007";
|
import { SavegameInterface_V1007 } from "./schemas/1007";
|
||||||
import { SavegameInterface_V1008 } from "./schemas/1008";
|
import { SavegameInterface_V1008 } from "./schemas/1008";
|
||||||
import { SavegameInterface_V1009 } from "./schemas/1009";
|
import { SavegameInterface_V1009 } from "./schemas/1009";
|
||||||
|
import { SavegameInterface_V1010 } from "./schemas/1010";
|
||||||
|
|
||||||
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
/** @type {Object.<number, typeof BaseSavegameInterface>} */
|
||||||
export const savegameInterfaces = {
|
export const savegameInterfaces = {
|
||||||
@ -23,6 +24,7 @@ export const savegameInterfaces = {
|
|||||||
1007: SavegameInterface_V1007,
|
1007: SavegameInterface_V1007,
|
||||||
1008: SavegameInterface_V1008,
|
1008: SavegameInterface_V1008,
|
||||||
1009: SavegameInterface_V1009,
|
1009: SavegameInterface_V1009,
|
||||||
|
1010: SavegameInterface_V1010,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger("savegame_interface_registry");
|
const logger = createLogger("savegame_interface_registry");
|
||||||
|
|||||||
38
src/js/savegame/schemas/1010.js
Normal file
38
src/js/savegame/schemas/1010.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { createLogger } from "../../core/logging.js";
|
||||||
|
import { SavegameInterface_V1009 } from "./1009.js";
|
||||||
|
|
||||||
|
const schema = require("./1010.json");
|
||||||
|
const logger = createLogger("savegame_interface/1010");
|
||||||
|
|
||||||
|
export class SavegameInterface_V1010 extends SavegameInterface_V1009 {
|
||||||
|
getVersion() {
|
||||||
|
return 1010;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchemaUncached() {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../savegame_typedefs.js").SavegameData} data
|
||||||
|
*/
|
||||||
|
static migrate1009to1010(data) {
|
||||||
|
logger.log("Migrating 1009 to 1010");
|
||||||
|
const dump = data.dump;
|
||||||
|
if (!dump) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dump.map.hasOwnProperty("allowNonPrimaryColors")) {
|
||||||
|
dump.map.allowNonPrimaryColors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dump.map.hasOwnProperty("fullShapePercentage")) {
|
||||||
|
dump.map.fullShapePercentage = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dump.map.hasOwnProperty("wierdShapePercentage")) {
|
||||||
|
dump.map.wierdShapePercentage = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/js/savegame/schemas/1010.json
Normal file
5
src/js/savegame/schemas/1010.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [],
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
@ -1,480 +1,513 @@
|
|||||||
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() {
|
/** @type {boolean} */
|
||||||
super("InGameState");
|
this.allowNonPrimaryColors;
|
||||||
|
|
||||||
/** @type {GameCreationPayload} */
|
/** @type {number} */
|
||||||
this.creationPayload = null;
|
this.fullShapePercentage;
|
||||||
|
|
||||||
// Stores current stage
|
/** @type {number} */
|
||||||
this.stage = "";
|
this.wierdShapePercentage;
|
||||||
|
}
|
||||||
/** @type {GameCore} */
|
}
|
||||||
this.core = null;
|
|
||||||
|
export class InGameState extends GameState {
|
||||||
/** @type {KeyActionMapper} */
|
constructor() {
|
||||||
this.keyActionMapper = null;
|
super("InGameState");
|
||||||
|
|
||||||
/** @type {GameLoadingOverlay} */
|
/** @type {GameCreationPayload} */
|
||||||
this.loadingOverlay = null;
|
this.creationPayload = null;
|
||||||
|
|
||||||
/** @type {Savegame} */
|
// Stores current stage
|
||||||
this.savegame = null;
|
this.stage = "";
|
||||||
|
|
||||||
this.boundInputFilter = this.filterInput.bind(this);
|
/** @type {GameCore} */
|
||||||
|
this.core = null;
|
||||||
/**
|
|
||||||
* Whether we are currently saving the game
|
/** @type {KeyActionMapper} */
|
||||||
* @TODO: This doesn't realy fit here
|
this.keyActionMapper = null;
|
||||||
*/
|
|
||||||
this.currentSavePromise = null;
|
/** @type {GameLoadingOverlay} */
|
||||||
}
|
this.loadingOverlay = null;
|
||||||
|
|
||||||
/**
|
/** @type {Savegame} */
|
||||||
* Switches the game into another sub-state
|
this.savegame = null;
|
||||||
* @param {string} stage
|
|
||||||
*/
|
/** @type {number} */
|
||||||
switchStage(stage) {
|
this.seed = null;
|
||||||
assert(stage, "Got empty stage");
|
|
||||||
if (stage !== this.stage) {
|
/** @type {boolean} */
|
||||||
this.stage = stage;
|
this.allowNonPrimaryColors = null;
|
||||||
logger.log(this.stage);
|
|
||||||
return true;
|
/** @type {number} */
|
||||||
} else {
|
this.fullShapePercentage = null;
|
||||||
// log(this, "Re entering", stage);
|
|
||||||
return false;
|
/** @type {number} */
|
||||||
}
|
this.wierdShapePercentage = null;
|
||||||
}
|
|
||||||
|
this.boundInputFilter = this.filterInput.bind(this);
|
||||||
// GameState implementation
|
|
||||||
getInnerHTML() {
|
/**
|
||||||
return "";
|
* Whether we are currently saving the game
|
||||||
}
|
* @TODO: This doesn't realy fit here
|
||||||
|
*/
|
||||||
getThemeMusic() {
|
this.currentSavePromise = null;
|
||||||
if (this.creationPayload.gameModeId && this.creationPayload.gameModeId.includes("puzzle")) {
|
}
|
||||||
return MUSIC.puzzle;
|
|
||||||
}
|
/**
|
||||||
return MUSIC.theme;
|
* Switches the game into another sub-state
|
||||||
}
|
* @param {string} stage
|
||||||
|
*/
|
||||||
onBeforeExit() {
|
switchStage(stage) {
|
||||||
// logger.log("Saving before quitting");
|
assert(stage, "Got empty stage");
|
||||||
// return this.doSave().then(() => {
|
if (stage !== this.stage) {
|
||||||
// logger.log(this, "Successfully saved");
|
this.stage = stage;
|
||||||
// // this.stageDestroyed();
|
logger.log(this.stage);
|
||||||
// });
|
return true;
|
||||||
}
|
} else {
|
||||||
|
// log(this, "Re entering", stage);
|
||||||
onAppPause() {
|
return false;
|
||||||
// if (this.stage === stages.s10_gameRunning) {
|
}
|
||||||
// logger.log("Saving because app got paused");
|
}
|
||||||
// this.doSave();
|
|
||||||
// }
|
// GameState implementation
|
||||||
}
|
getInnerHTML() {
|
||||||
|
return "";
|
||||||
getHasFadeIn() {
|
}
|
||||||
return false;
|
|
||||||
}
|
getThemeMusic() {
|
||||||
|
if (this.creationPayload.gameModeId && this.creationPayload.gameModeId.includes("puzzle")) {
|
||||||
getPauseOnFocusLost() {
|
return MUSIC.puzzle;
|
||||||
return false;
|
}
|
||||||
}
|
return MUSIC.theme;
|
||||||
|
}
|
||||||
getHasUnloadConfirmation() {
|
|
||||||
return true;
|
onBeforeExit() {
|
||||||
}
|
// logger.log("Saving before quitting");
|
||||||
|
// return this.doSave().then(() => {
|
||||||
onLeave() {
|
// logger.log(this, "Successfully saved");
|
||||||
if (this.core) {
|
// // this.stageDestroyed();
|
||||||
this.stageDestroyed();
|
// });
|
||||||
}
|
}
|
||||||
this.app.inputMgr.dismountFilter(this.boundInputFilter);
|
|
||||||
}
|
onAppPause() {
|
||||||
|
// if (this.stage === stages.s10_gameRunning) {
|
||||||
onResized(w, h) {
|
// logger.log("Saving because app got paused");
|
||||||
super.onResized(w, h);
|
// this.doSave();
|
||||||
if (this.stage === stages.s10_gameRunning) {
|
// }
|
||||||
this.core.resize(w, h);
|
}
|
||||||
}
|
|
||||||
}
|
getHasFadeIn() {
|
||||||
|
return false;
|
||||||
// ---- End of GameState implementation
|
}
|
||||||
|
|
||||||
/**
|
getPauseOnFocusLost() {
|
||||||
* Goes back to the menu state
|
return false;
|
||||||
*/
|
}
|
||||||
goBackToMenu() {
|
|
||||||
if ([enumGameModeIds.puzzleEdit, enumGameModeIds.puzzlePlay].includes(this.gameModeId)) {
|
getHasUnloadConfirmation() {
|
||||||
this.saveThenGoToState("PuzzleMenuState");
|
return true;
|
||||||
} else {
|
}
|
||||||
this.saveThenGoToState("MainMenuState");
|
|
||||||
}
|
onLeave() {
|
||||||
}
|
if (this.core) {
|
||||||
|
this.stageDestroyed();
|
||||||
/**
|
}
|
||||||
* Goes back to the settings state
|
this.app.inputMgr.dismountFilter(this.boundInputFilter);
|
||||||
*/
|
}
|
||||||
goToSettings() {
|
|
||||||
this.saveThenGoToState("SettingsState", {
|
onResized(w, h) {
|
||||||
backToStateId: this.key,
|
super.onResized(w, h);
|
||||||
backToStatePayload: this.creationPayload,
|
if (this.stage === stages.s10_gameRunning) {
|
||||||
});
|
this.core.resize(w, h);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Goes back to the settings state
|
// ---- End of GameState implementation
|
||||||
*/
|
|
||||||
goToKeybindings() {
|
/**
|
||||||
this.saveThenGoToState("KeybindingsState", {
|
* Goes back to the menu state
|
||||||
backToStateId: this.key,
|
*/
|
||||||
backToStatePayload: this.creationPayload,
|
goBackToMenu() {
|
||||||
});
|
if ([enumGameModeIds.puzzleEdit, enumGameModeIds.puzzlePlay].includes(this.gameModeId)) {
|
||||||
}
|
this.saveThenGoToState("PuzzleMenuState");
|
||||||
|
} else {
|
||||||
/**
|
this.saveThenGoToState("MainMenuState");
|
||||||
* Moves to a state outside of the game
|
}
|
||||||
* @param {string} stateId
|
}
|
||||||
* @param {any=} payload
|
|
||||||
*/
|
/**
|
||||||
saveThenGoToState(stateId, payload) {
|
* Goes back to the settings state
|
||||||
if (this.stage === stages.leaving || this.stage === stages.destroyed) {
|
*/
|
||||||
logger.warn(
|
goToSettings() {
|
||||||
"Tried to leave game twice or during destroy:",
|
this.saveThenGoToState("SettingsState", {
|
||||||
this.stage,
|
backToStateId: this.key,
|
||||||
"(attempted to move to",
|
backToStatePayload: this.creationPayload,
|
||||||
stateId,
|
});
|
||||||
")"
|
}
|
||||||
);
|
|
||||||
return;
|
/**
|
||||||
}
|
* Goes back to the settings state
|
||||||
this.stageLeavingGame();
|
*/
|
||||||
this.doSave().then(() => {
|
goToKeybindings() {
|
||||||
this.stageDestroyed();
|
this.saveThenGoToState("KeybindingsState", {
|
||||||
this.moveToState(stateId, payload);
|
backToStateId: this.key,
|
||||||
});
|
backToStatePayload: this.creationPayload,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
onBackButton() {
|
|
||||||
// do nothing
|
/**
|
||||||
}
|
* Moves to a state outside of the game
|
||||||
|
* @param {string} stateId
|
||||||
/**
|
* @param {any=} payload
|
||||||
* Called when the game somehow failed to initialize. Resets everything to basic state and
|
*/
|
||||||
* then goes to the main menu, showing the error
|
saveThenGoToState(stateId, payload) {
|
||||||
* @param {string} err
|
if (this.stage === stages.leaving || this.stage === stages.destroyed) {
|
||||||
*/
|
logger.warn(
|
||||||
onInitializationFailure(err) {
|
"Tried to leave game twice or during destroy:",
|
||||||
if (this.switchStage(stages.initFailed)) {
|
this.stage,
|
||||||
logger.error("Init failure:", err);
|
"(attempted to move to",
|
||||||
this.stageDestroyed();
|
stateId,
|
||||||
this.moveToState("MainMenuState", { loadError: err });
|
")"
|
||||||
}
|
);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
// STAGES
|
this.stageLeavingGame();
|
||||||
|
this.doSave().then(() => {
|
||||||
/**
|
this.stageDestroyed();
|
||||||
* Creates the game core instance, and thus the root
|
this.moveToState(stateId, payload);
|
||||||
*/
|
});
|
||||||
stage3CreateCore() {
|
}
|
||||||
if (this.switchStage(stages.s3_createCore)) {
|
|
||||||
logger.log("Creating new game core");
|
onBackButton() {
|
||||||
this.core = new GameCore(this.app);
|
// do nothing
|
||||||
|
}
|
||||||
this.core.initializeRoot(this, this.savegame, this.gameModeId);
|
|
||||||
|
/**
|
||||||
if (this.savegame.hasGameDump()) {
|
* Called when the game somehow failed to initialize. Resets everything to basic state and
|
||||||
this.stage4bResumeGame();
|
* then goes to the main menu, showing the error
|
||||||
} else {
|
* @param {string} err
|
||||||
this.app.gameAnalytics.handleGameStarted();
|
*/
|
||||||
this.stage4aInitEmptyGame();
|
onInitializationFailure(err) {
|
||||||
}
|
if (this.switchStage(stages.initFailed)) {
|
||||||
}
|
logger.error("Init failure:", err);
|
||||||
}
|
this.stageDestroyed();
|
||||||
|
this.moveToState("MainMenuState", { loadError: err });
|
||||||
/**
|
}
|
||||||
* Initializes a new empty game
|
}
|
||||||
*/
|
|
||||||
stage4aInitEmptyGame() {
|
// STAGES
|
||||||
if (this.switchStage(stages.s4_A_initEmptyGame)) {
|
|
||||||
this.core.initNewGame();
|
/**
|
||||||
this.stage5FirstUpdate();
|
* Creates the game core instance, and thus the root
|
||||||
}
|
*/
|
||||||
}
|
stage3CreateCore() {
|
||||||
|
if (this.switchStage(stages.s3_createCore)) {
|
||||||
/**
|
logger.log("Creating new game core");
|
||||||
* Resumes an existing game
|
this.core = new GameCore(this.app);
|
||||||
*/
|
|
||||||
stage4bResumeGame() {
|
this.core.initializeRoot(this, this.savegame, this.gameModeId);
|
||||||
if (this.switchStage(stages.s4_B_resumeGame)) {
|
|
||||||
if (!this.core.initExistingGame()) {
|
if (this.savegame.hasGameDump()) {
|
||||||
this.onInitializationFailure("Savegame is corrupt and can not be restored.");
|
this.stage4bResumeGame();
|
||||||
return;
|
} else {
|
||||||
}
|
this.app.gameAnalytics.handleGameStarted();
|
||||||
this.app.gameAnalytics.handleGameResumed();
|
this.stage4aInitEmptyGame();
|
||||||
this.stage5FirstUpdate();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the first game update on the game which initializes most caches
|
* Initializes a new empty game
|
||||||
*/
|
*/
|
||||||
stage5FirstUpdate() {
|
stage4aInitEmptyGame() {
|
||||||
if (this.switchStage(stages.s5_firstUpdate)) {
|
if (this.switchStage(stages.s4_A_initEmptyGame)) {
|
||||||
this.core.root.logicInitialized = true;
|
this.core.initNewGame({
|
||||||
this.core.updateLogic();
|
seed: this.seed,
|
||||||
this.stage6PostLoadHook();
|
allowNonPrimaryColors: this.allowNonPrimaryColors,
|
||||||
}
|
fullShapePercentage: this.fullShapePercentage,
|
||||||
}
|
wierdShapePercentage: this.wierdShapePercentage,
|
||||||
|
});
|
||||||
/**
|
this.stage5FirstUpdate();
|
||||||
* 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(stages.s6_postLoadHook)) {
|
* Resumes an existing game
|
||||||
logger.log("Post load hook");
|
*/
|
||||||
this.core.postLoadHook();
|
stage4bResumeGame() {
|
||||||
this.stage7Warmup();
|
if (this.switchStage(stages.s4_B_resumeGame)) {
|
||||||
}
|
if (!this.core.initExistingGame()) {
|
||||||
}
|
this.onInitializationFailure("Savegame is corrupt and can not be restored.");
|
||||||
|
return;
|
||||||
/**
|
}
|
||||||
* This makes the game idle and draw for a while, because we run most code this way
|
this.app.gameAnalytics.handleGameResumed();
|
||||||
* the V8 engine can already start to optimize it. Also this makes sure the resources
|
this.stage5FirstUpdate();
|
||||||
* are in the VRAM and we have a smooth experience once we start.
|
}
|
||||||
*/
|
}
|
||||||
stage7Warmup() {
|
|
||||||
if (this.switchStage(stages.s7_warmup)) {
|
/**
|
||||||
if (this.creationPayload.fastEnter) {
|
* Performs the first game update on the game which initializes most caches
|
||||||
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
|
*/
|
||||||
} else {
|
stage5FirstUpdate() {
|
||||||
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
|
if (this.switchStage(stages.s5_firstUpdate)) {
|
||||||
}
|
this.core.root.logicInitialized = true;
|
||||||
}
|
this.core.updateLogic();
|
||||||
}
|
this.stage6PostLoadHook();
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* The final stage where this game is running and updating regulary.
|
|
||||||
*/
|
/**
|
||||||
stage10GameRunning() {
|
* Call the post load hook, this means that we have loaded the game, and all systems
|
||||||
if (this.switchStage(stages.s10_gameRunning)) {
|
* can operate and start to work now.
|
||||||
this.core.root.signals.readyToRender.dispatch();
|
*/
|
||||||
|
stage6PostLoadHook() {
|
||||||
logSection("GAME STARTED", "#26a69a");
|
if (this.switchStage(stages.s6_postLoadHook)) {
|
||||||
|
logger.log("Post load hook");
|
||||||
// Initial resize, might have changed during loading (this is possible)
|
this.core.postLoadHook();
|
||||||
this.core.resize(this.app.screenWidth, this.app.screenHeight);
|
this.stage7Warmup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This stage destroys the whole game, used to cleanup
|
* 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
|
||||||
stageDestroyed() {
|
* are in the VRAM and we have a smooth experience once we start.
|
||||||
if (this.switchStage(stages.destroyed)) {
|
*/
|
||||||
// Cleanup all api calls
|
stage7Warmup() {
|
||||||
this.cancelAllAsyncOperations();
|
if (this.switchStage(stages.s7_warmup)) {
|
||||||
|
if (this.creationPayload.fastEnter) {
|
||||||
if (this.syncer) {
|
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsFast;
|
||||||
this.syncer.cancelSync();
|
} else {
|
||||||
this.syncer = null;
|
this.warmupTimeSeconds = globalConfig.warmupTimeSecondsRegular;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Cleanup core
|
}
|
||||||
if (this.core) {
|
|
||||||
this.core.destruct();
|
/**
|
||||||
this.core = null;
|
* The final stage where this game is running and updating regulary.
|
||||||
}
|
*/
|
||||||
}
|
stage10GameRunning() {
|
||||||
}
|
if (this.switchStage(stages.s10_gameRunning)) {
|
||||||
|
this.core.root.signals.readyToRender.dispatch();
|
||||||
/**
|
|
||||||
* When leaving the game
|
logSection("GAME STARTED", "#26a69a");
|
||||||
*/
|
|
||||||
stageLeavingGame() {
|
// Initial resize, might have changed during loading (this is possible)
|
||||||
if (this.switchStage(stages.leaving)) {
|
this.core.resize(this.app.screenWidth, this.app.screenHeight);
|
||||||
// ...
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
// END STAGES
|
* This stage destroys the whole game, used to cleanup
|
||||||
|
*/
|
||||||
/**
|
stageDestroyed() {
|
||||||
* Filters the input (keybindings)
|
if (this.switchStage(stages.destroyed)) {
|
||||||
*/
|
// Cleanup all api calls
|
||||||
filterInput() {
|
this.cancelAllAsyncOperations();
|
||||||
return this.stage === stages.s10_gameRunning;
|
|
||||||
}
|
if (this.syncer) {
|
||||||
|
this.syncer.cancelSync();
|
||||||
/**
|
this.syncer = null;
|
||||||
* @param {GameCreationPayload} payload
|
}
|
||||||
*/
|
|
||||||
onEnter(payload) {
|
// Cleanup core
|
||||||
this.app.inputMgr.installFilter(this.boundInputFilter);
|
if (this.core) {
|
||||||
|
this.core.destruct();
|
||||||
this.creationPayload = payload;
|
this.core = null;
|
||||||
this.savegame = payload.savegame;
|
}
|
||||||
this.gameModeId = payload.gameModeId;
|
}
|
||||||
|
}
|
||||||
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
|
||||||
this.loadingOverlay.showBasic();
|
/**
|
||||||
|
* When leaving the game
|
||||||
// Remove unneded default element
|
*/
|
||||||
document.body.querySelector(".modalDialogParent").remove();
|
stageLeavingGame() {
|
||||||
|
if (this.switchStage(stages.leaving)) {
|
||||||
this.asyncChannel
|
// ...
|
||||||
.watch(waitNextFrame())
|
}
|
||||||
.then(() => this.stage3CreateCore())
|
}
|
||||||
.catch(ex => {
|
|
||||||
logger.error(ex);
|
// END STAGES
|
||||||
throw ex;
|
|
||||||
});
|
/**
|
||||||
}
|
* Filters the input (keybindings)
|
||||||
|
*/
|
||||||
/**
|
filterInput() {
|
||||||
* Render callback
|
return this.stage === stages.s10_gameRunning;
|
||||||
* @param {number} dt
|
}
|
||||||
*/
|
|
||||||
onRender(dt) {
|
/**
|
||||||
if (APPLICATION_ERROR_OCCURED) {
|
* @param {GameCreationPayload} payload
|
||||||
// Application somehow crashed, do not do anything
|
*/
|
||||||
return;
|
onEnter(payload) {
|
||||||
}
|
this.app.inputMgr.installFilter(this.boundInputFilter);
|
||||||
|
|
||||||
if (this.stage === stages.s7_warmup) {
|
this.creationPayload = payload;
|
||||||
this.core.draw();
|
this.savegame = payload.savegame;
|
||||||
this.warmupTimeSeconds -= dt / 1000.0;
|
this.gameModeId = payload.gameModeId;
|
||||||
if (this.warmupTimeSeconds < 0) {
|
this.seed = payload.seed;
|
||||||
logger.log("Warmup completed");
|
this.allowNonPrimaryColors = payload.allowNonPrimaryColors;
|
||||||
this.stage10GameRunning();
|
this.fullShapePercentage = payload.fullShapePercentage;
|
||||||
}
|
this.wierdShapePercentage = payload.wierdShapePercentage;
|
||||||
}
|
|
||||||
|
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
||||||
if (this.stage === stages.s10_gameRunning) {
|
this.loadingOverlay.showBasic();
|
||||||
this.core.tick(dt);
|
|
||||||
}
|
// Remove unneded default element
|
||||||
|
document.body.querySelector(".modalDialogParent").remove();
|
||||||
// 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) {
|
this.asyncChannel
|
||||||
// Only draw if page visible
|
.watch(waitNextFrame())
|
||||||
if (this.app.pageVisible) {
|
.then(() => this.stage3CreateCore())
|
||||||
this.core.draw();
|
.catch(ex => {
|
||||||
}
|
logger.error(ex);
|
||||||
|
throw ex;
|
||||||
this.loadingOverlay.removeIfAttached();
|
});
|
||||||
} else {
|
}
|
||||||
if (!this.loadingOverlay.isAttached()) {
|
|
||||||
this.loadingOverlay.showBasic();
|
/**
|
||||||
}
|
* Render callback
|
||||||
}
|
* @param {number} dt
|
||||||
}
|
*/
|
||||||
|
onRender(dt) {
|
||||||
onBackgroundTick(dt) {
|
if (APPLICATION_ERROR_OCCURED) {
|
||||||
this.onRender(dt);
|
// Application somehow crashed, do not do anything
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Saves the game
|
if (this.stage === stages.s7_warmup) {
|
||||||
*/
|
this.core.draw();
|
||||||
|
this.warmupTimeSeconds -= dt / 1000.0;
|
||||||
doSave() {
|
if (this.warmupTimeSeconds < 0) {
|
||||||
if (!this.savegame || !this.savegame.isSaveable()) {
|
logger.log("Warmup completed");
|
||||||
return Promise.resolve();
|
this.stage10GameRunning();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (APPLICATION_ERROR_OCCURED) {
|
|
||||||
logger.warn("skipping save because application crashed");
|
if (this.stage === stages.s10_gameRunning) {
|
||||||
return Promise.resolve();
|
this.core.tick(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// If the stage is still active (This might not be the case if tick() moved us to game over)
|
||||||
this.stage !== stages.s10_gameRunning &&
|
if (this.stage === stages.s10_gameRunning) {
|
||||||
this.stage !== stages.s7_warmup &&
|
// Only draw if page visible
|
||||||
this.stage !== stages.leaving
|
if (this.app.pageVisible) {
|
||||||
) {
|
this.core.draw();
|
||||||
logger.warn("Skipping save because game is not ready");
|
}
|
||||||
return Promise.resolve();
|
|
||||||
}
|
this.loadingOverlay.removeIfAttached();
|
||||||
|
} else {
|
||||||
if (this.currentSavePromise) {
|
if (!this.loadingOverlay.isAttached()) {
|
||||||
logger.warn("Skipping double save and returning same promise");
|
this.loadingOverlay.showBasic();
|
||||||
return this.currentSavePromise;
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!this.core.root.gameMode.getIsSaveable()) {
|
|
||||||
return Promise.resolve();
|
onBackgroundTick(dt) {
|
||||||
}
|
this.onRender(dt);
|
||||||
|
}
|
||||||
logger.log("Starting to save game ...");
|
|
||||||
this.savegame.updateData(this.core.root);
|
/**
|
||||||
|
* Saves the game
|
||||||
this.currentSavePromise = this.savegame
|
*/
|
||||||
.writeSavegameAndMetadata()
|
|
||||||
.catch(err => {
|
doSave() {
|
||||||
// Catch errors
|
if (!this.savegame || !this.savegame.isSaveable()) {
|
||||||
logger.warn("Failed to save:", err);
|
return Promise.resolve();
|
||||||
})
|
}
|
||||||
.then(() => {
|
|
||||||
// Clear promise
|
if (APPLICATION_ERROR_OCCURED) {
|
||||||
logger.log("Saved!");
|
logger.warn("skipping save because application crashed");
|
||||||
this.core.root.signals.gameSaved.dispatch();
|
return Promise.resolve();
|
||||||
this.currentSavePromise = null;
|
}
|
||||||
});
|
|
||||||
|
if (
|
||||||
return this.currentSavePromise;
|
this.stage !== stages.s10_gameRunning &&
|
||||||
}
|
this.stage !== stages.s7_warmup &&
|
||||||
}
|
this.stage !== stages.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { cachebust } from "../core/cachebust";
|
|||||||
import { A_B_TESTING_LINK_TYPE, globalConfig, THIRDPARTY_URLS } from "../core/config";
|
import { A_B_TESTING_LINK_TYPE, globalConfig, THIRDPARTY_URLS } from "../core/config";
|
||||||
import { GameState } from "../core/game_state";
|
import { GameState } from "../core/game_state";
|
||||||
import { DialogWithForm } from "../core/modal_dialog_elements";
|
import { DialogWithForm } from "../core/modal_dialog_elements";
|
||||||
import { FormElementInput } from "../core/modal_dialog_forms";
|
import { FormElementCheckbox, FormElementDetails, FormElementInput } from "../core/modal_dialog_forms";
|
||||||
import { ReadWriteProxy } from "../core/read_write_proxy";
|
import { ReadWriteProxy } from "../core/read_write_proxy";
|
||||||
import {
|
import {
|
||||||
formatSecondsToTimeAgo,
|
formatSecondsToTimeAgo,
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
makeButton,
|
makeButton,
|
||||||
makeButtonElement,
|
makeButtonElement,
|
||||||
makeDiv,
|
makeDiv,
|
||||||
|
randomInt,
|
||||||
removeAllChildren,
|
removeAllChildren,
|
||||||
startFileChoose,
|
startFileChoose,
|
||||||
waitNextFrame,
|
waitNextFrame,
|
||||||
@ -672,14 +673,96 @@ export class MainMenuState extends GameState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.app.analytics.trackUiClick("startgame");
|
const regex = /^[a-zA-Z0-9_\- ]{1,20}$/;
|
||||||
this.app.adProvider.showVideoAd().then(() => {
|
|
||||||
const savegame = this.app.savegameMgr.createNewSavegame();
|
|
||||||
|
|
||||||
this.moveToState("InGameState", {
|
const nameInput = new FormElementInput({
|
||||||
savegame,
|
id: "nameInput",
|
||||||
|
// @TODO: Add translation (T.dialogs.newSavegame.nameInputLabel)
|
||||||
|
label: "Name:",
|
||||||
|
placeholder: "",
|
||||||
|
defaultValue: "Unnamed",
|
||||||
|
validator: val => val.match(regex) && trim(val).length > 0,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const seedInput = new FormElementInput({
|
||||||
|
id: "seedInput",
|
||||||
|
// @TODO: Add translation (T.dialogs.newSavegame.seedInputLabel)
|
||||||
|
label: "Seed:",
|
||||||
|
placeholder: "",
|
||||||
|
defaultValue: randomInt(0, 100000).toString(),
|
||||||
|
validator: val => Number.isInteger(Number(val)) && Number(val) >= 0 && Number(val) <= 100000,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allowColorsCheckbox = new FormElementCheckbox({
|
||||||
|
id: "allowColorsCheckbox",
|
||||||
|
// @TODO: Add translation (T.dialogs.newSavegame.allowColorsCheckboxLabel)
|
||||||
|
label: "Allow non-primarycolors: ",
|
||||||
|
defaultValue: false,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fullShapePercentageInput = new FormElementInput({
|
||||||
|
id: "fullShapePercentageInput",
|
||||||
|
label: "fullShape %:",
|
||||||
|
placeholder: "",
|
||||||
|
defaultValue: Number(0).toString(),
|
||||||
|
validator: val => Number.isInteger(Number(val)) && Number(val) >= 0 && Number(val) <= 100,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wierdShapePercentageInput = new FormElementInput({
|
||||||
|
id: "wierdShapePercentageInput",
|
||||||
|
label: "wierdShape %:",
|
||||||
|
placeholder: "",
|
||||||
|
defaultValue: Number(0).toString(),
|
||||||
|
validator: val => Number.isInteger(Number(val)) && Number(val) >= 0 && Number(val) <= 100,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const advancedContainer = new FormElementDetails({
|
||||||
|
id: "advancedContainer",
|
||||||
|
// @TODO Add translation (T.dialogs.newSavegame.advanced)
|
||||||
|
label: "Advanced Options",
|
||||||
|
formElements: [
|
||||||
|
seedInput,
|
||||||
|
allowColorsCheckbox,
|
||||||
|
fullShapePercentageInput,
|
||||||
|
wierdShapePercentageInput,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialog = new DialogWithForm({
|
||||||
|
app: this.app,
|
||||||
|
// @TODO: Add translation (T.dialogs.newSavegame.title)
|
||||||
|
title: "New Game Options",
|
||||||
|
// @TODO: Add translation (T.dialogs.newSavegame.desc)
|
||||||
|
desc: "Configure your new savegame",
|
||||||
|
formElements: [nameInput, advancedContainer],
|
||||||
|
buttons: ["ok:good:enter"],
|
||||||
|
});
|
||||||
|
this.dialogs.internalShowDialog(dialog);
|
||||||
|
|
||||||
|
dialog.buttonSignals.ok.add(() => {
|
||||||
|
this.app.analytics.trackUiClick("startgame");
|
||||||
|
this.app.adProvider.showVideoAd().then(async () => {
|
||||||
|
const savegame = this.app.savegameMgr.createNewSavegame();
|
||||||
|
const savegameMetadata = this.app.savegameMgr.getGameMetaDataByInternalId(
|
||||||
|
savegame.internalId
|
||||||
|
);
|
||||||
|
savegameMetadata.name = trim(nameInput.getValue());
|
||||||
|
await this.app.savegameMgr.writeAsync();
|
||||||
|
|
||||||
|
this.moveToState("InGameState", {
|
||||||
|
savegame,
|
||||||
|
seed: Number(seedInput.getValue()),
|
||||||
|
allowNonPrimaryColors: allowColorsCheckbox.getValue(),
|
||||||
|
fullShapePercentage: Number(fullShapePercentageInput.getValue()),
|
||||||
|
wierdShapePercentage: Number(wierdShapePercentageInput.getValue()),
|
||||||
|
});
|
||||||
|
this.app.analytics.trackUiClick("startgame_adcomplete");
|
||||||
});
|
});
|
||||||
this.app.analytics.trackUiClick("startgame_adcomplete");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user