mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Puzzle DLC (#1172)
* Puzzle mode (#1135) * Add mode button to main menu * [WIP] Add mode menu. Add factory-based gameMode creation * Add savefile migration, serialize, deserialize * Add hidden HUD elements, zone, and zoom, boundary constraints * Clean up lint issues * Add building, HUD exclusion, building exclusion, and refactor - [WIP] Add ConstantProducer building that combines ConstantSignal and ItemProducer functionality. Currently using temp assets. - Add pre-placement check to the zone - Use Rectangles for zone and boundary - Simplify zone drawing - Account for exclusion in savegame data - [WIP] Add puzzle play and edit buttons in puzzle mode menu * [WIP] Add building, component, and systems for producing and accepting user-specified items and checking goal criteria * Add ingame puzzle mode UI elements - Add minimal menus in puzzle mode for back, next navigation - Add lower menu for changing zone dimenensions Co-authored-by: Greg Considine <gconsidine@users.noreply.github.com> * Performance optimizations (#1154) * 1.3.1 preparations * Minor fixes, update translations * Fix achievements not working * Lots of belt optimizations, ~15% performance boost * Puzzle mode, part 1 * Puzzle mode, part 2 * Fix missing import * Puzzle mode, part 3 * Fix typo * Puzzle mode, part 4 * Puzzle Mode fixes: Correct zone restrictions and more (#1155) * Hide Puzzle Editor Controls in regular game mode, fix typo * Disallow shrinking zone if there are buildings * Fix multi-tile buildings for shrinking * Puzzle mode, Refactor hud * Puzzle mode * Fixed typo in latest puzzle commit (#1156) * Allow completing puzzles * Puzzle mode, almost done * Bump version to 1.4.0 * Fixes * [puzzle] Prevent pipette cheats (miners, emitters) (#1158) * Puzzle mode, almost done * Allow clearing belts with 'B' * Multiple users for the puzzle dlc * Bump api key * Minor adjustments * Update * Minor fixes * Fix throughput * Fix belts * Minor puzzle adjustments * New difficulty * Minor puzzle improvements * Fix belt path * Update translations * Added a button to return to the menu after a puzzle is completed (#1170) * added another button to return to the menu * improved menu return * fixed continue button to not go back to menu * [Puzzle] Added ability to lock buildings in the puzzle editor! (#1164) * initial test * tried to get it to work * added icon * added test exclusion * reverted css * completed flow for building locking * added lock option * finalized look and changed locked building to same sprite * removed unused art * added clearing every goal acceptor on lock to prevent creating impossible puzzles * heavily improved validation and prevented autocompletion * validation only checks every 100 ticks to improve performance * validation only checks every 100 ticks to improve performance * removed clearing goal acceptors as it isn't needed because of validation * Add soundtrack, puzzle dlc fixes Co-authored-by: Greg Considine <gconsidine@users.noreply.github.com> Co-authored-by: dengr1065 <dengr1065@gmail.com> Co-authored-by: Sense101 <67970865+Sense101@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@ 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";
|
||||
|
||||
const logger = createLogger("state/ingame");
|
||||
|
||||
@@ -39,8 +40,14 @@ export class GameCreationPayload {
|
||||
/** @type {boolean|undefined} */
|
||||
this.fastEnter;
|
||||
|
||||
/** @type {string} */
|
||||
this.gameModeId;
|
||||
|
||||
/** @type {Savegame} */
|
||||
this.savegame;
|
||||
|
||||
/** @type {object|undefined} */
|
||||
this.gameModeParameters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +104,9 @@ export class InGameState extends GameState {
|
||||
}
|
||||
|
||||
getThemeMusic() {
|
||||
if (this.creationPayload.gameModeId && this.creationPayload.gameModeId.includes("puzzle")) {
|
||||
return MUSIC.puzzle;
|
||||
}
|
||||
return MUSIC.theme;
|
||||
}
|
||||
|
||||
@@ -147,7 +157,11 @@ export class InGameState extends GameState {
|
||||
* Goes back to the menu state
|
||||
*/
|
||||
goBackToMenu() {
|
||||
this.saveThenGoToState("MainMenuState");
|
||||
if ([enumGameModeIds.puzzleEdit, enumGameModeIds.puzzlePlay].includes(this.gameModeId)) {
|
||||
this.saveThenGoToState("PuzzleMenuState");
|
||||
} else {
|
||||
this.saveThenGoToState("MainMenuState");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +234,7 @@ export class InGameState extends GameState {
|
||||
logger.log("Creating new game core");
|
||||
this.core = new GameCore(this.app);
|
||||
|
||||
this.core.initializeRoot(this, this.savegame);
|
||||
this.core.initializeRoot(this, this.savegame, this.gameModeId);
|
||||
|
||||
if (this.savegame.hasGameDump()) {
|
||||
this.stage4bResumeGame();
|
||||
@@ -354,6 +368,7 @@ export class InGameState extends GameState {
|
||||
|
||||
this.creationPayload = payload;
|
||||
this.savegame = payload.savegame;
|
||||
this.gameModeId = payload.gameModeId;
|
||||
|
||||
this.loadingOverlay = new GameLoadingOverlay(this.app, this.getDivElement());
|
||||
this.loadingOverlay.showBasic();
|
||||
@@ -361,7 +376,13 @@ export class InGameState extends GameState {
|
||||
// Remove unneded default element
|
||||
document.body.querySelector(".modalDialogParent").remove();
|
||||
|
||||
this.asyncChannel.watch(waitNextFrame()).then(() => this.stage3CreateCore());
|
||||
this.asyncChannel
|
||||
.watch(waitNextFrame())
|
||||
.then(() => this.stage3CreateCore())
|
||||
.catch(ex => {
|
||||
logger.error(ex);
|
||||
throw ex;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,6 +454,11 @@ export class InGameState extends GameState {
|
||||
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);
|
||||
|
||||
|
||||
102
src/js/states/login.js
Normal file
102
src/js/states/login.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { GameState } from "../core/game_state";
|
||||
import { getRandomHint } from "../game/hints";
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { T } from "../translations";
|
||||
|
||||
export class LoginState extends GameState {
|
||||
constructor() {
|
||||
super("LoginState");
|
||||
}
|
||||
|
||||
getInnerHTML() {
|
||||
return `
|
||||
<div class="loadingImage"></div>
|
||||
<div class="loadingStatus">
|
||||
<span class="desc">${T.global.loggingIn}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="prefab_GameHint"></span>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} payload
|
||||
* @param {string} payload.nextStateId
|
||||
*/
|
||||
onEnter(payload) {
|
||||
this.payload = payload;
|
||||
if (!this.payload.nextStateId) {
|
||||
throw new Error("No next state id");
|
||||
}
|
||||
|
||||
if (this.app.clientApi.isLoggedIn()) {
|
||||
this.finishLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialogs = new HUDModalDialogs(null, this.app);
|
||||
const dialogsElement = document.body.querySelector(".modalDialogParent");
|
||||
this.dialogs.initializeToElement(dialogsElement);
|
||||
|
||||
this.htmlElement.classList.add("prefab_LoadingState");
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.hintsText = this.htmlElement.querySelector(".prefab_GameHint");
|
||||
this.lastHintShown = -1000;
|
||||
this.nextHintDuration = 0;
|
||||
|
||||
this.tryLogin();
|
||||
}
|
||||
|
||||
tryLogin() {
|
||||
this.app.clientApi.tryLogin().then(success => {
|
||||
console.log("Logged in:", success);
|
||||
|
||||
if (!success) {
|
||||
const signals = this.dialogs.showWarning(
|
||||
T.dialogs.offlineMode.title,
|
||||
T.dialogs.offlineMode.desc,
|
||||
["retry", "playOffline:bad"]
|
||||
);
|
||||
signals.retry.add(() => setTimeout(() => this.tryLogin(), 2000), this);
|
||||
signals.playOffline.add(this.finishLoading, this);
|
||||
} else {
|
||||
this.finishLoading();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
finishLoading() {
|
||||
this.moveToState(this.payload.nextStateId);
|
||||
}
|
||||
|
||||
getDefaultPreviousState() {
|
||||
return "MainMenuState";
|
||||
}
|
||||
|
||||
update() {
|
||||
const now = performance.now();
|
||||
if (now - this.lastHintShown > this.nextHintDuration) {
|
||||
this.lastHintShown = now;
|
||||
const hintText = getRandomHint();
|
||||
|
||||
this.hintsText.innerHTML = hintText;
|
||||
|
||||
/**
|
||||
* Compute how long the user will need to read the hint.
|
||||
* We calculate with 130 words per minute, with an average of 5 chars
|
||||
* that is 650 characters / minute
|
||||
*/
|
||||
this.nextHintDuration = Math.max(2500, (hintText.length / 650) * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
onRender() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
onBackgroundTick() {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export class MainMenuState extends GameState {
|
||||
<img src="${cachebust(
|
||||
G_CHINA_VERSION ? "res/logo_cn.png" : "res/logo.png"
|
||||
)}" alt="shapez.io Logo">
|
||||
<span class="updateLabel">v${G_BUILD_VERSION} - Achievements!</span>
|
||||
<span class="updateLabel">v${G_BUILD_VERSION} - Puzzle DLC!</span>
|
||||
</div>
|
||||
|
||||
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">
|
||||
@@ -82,6 +82,19 @@ export class MainMenuState extends GameState {
|
||||
}
|
||||
<div class="buttons"></div>
|
||||
</div>
|
||||
|
||||
${
|
||||
// @TODO: Only display if DLC is owned, otherwise show ad for store page
|
||||
showDemoBadges
|
||||
? ""
|
||||
: `
|
||||
<div class="puzzleContainer">
|
||||
<img class="dlcLogo" src="${cachebust(
|
||||
"res/puzzle_dlc_logo.png"
|
||||
)}" alt="shapez.io Logo">
|
||||
<button class="styledButton puzzleDlcPlayButton">Play</button>
|
||||
</div>`
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="footer ${G_CHINA_VERSION ? "china" : ""}">
|
||||
@@ -203,6 +216,11 @@ export class MainMenuState extends GameState {
|
||||
|
||||
const qs = this.htmlElement.querySelector.bind(this.htmlElement);
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
||||
this.onPuzzleModeButtonClicked(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
|
||||
const games = this.app.savegameMgr.getSavegamesMetaData();
|
||||
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
|
||||
@@ -304,6 +322,34 @@ export class MainMenuState extends GameState {
|
||||
this.trackClicks(playBtn, this.onPlayButtonClicked);
|
||||
buttonContainer.appendChild(importButtonElement);
|
||||
}
|
||||
|
||||
const puzzleModeButton = this.htmlElement.querySelector(".puzzleDlcPlayButton");
|
||||
if (puzzleModeButton) {
|
||||
this.trackClicks(puzzleModeButton, () => this.onPuzzleModeButtonClicked());
|
||||
}
|
||||
}
|
||||
|
||||
onPuzzleModeButtonClicked(force = false) {
|
||||
const hasUnlockedBlueprints = this.app.savegameMgr.getSavegamesMetaData().some(s => s.level >= 12);
|
||||
console.log(hasUnlockedBlueprints);
|
||||
if (!force && !hasUnlockedBlueprints) {
|
||||
const { ok } = this.dialogs.showWarning(
|
||||
T.dialogs.puzzlePlayRegularRecommendation.title,
|
||||
T.dialogs.puzzlePlayRegularRecommendation.desc,
|
||||
["cancel:good", "ok:bad:timeout"]
|
||||
);
|
||||
ok.add(() => this.onPuzzleModeButtonClicked(true));
|
||||
return;
|
||||
}
|
||||
|
||||
this.moveToState("LoginState", {
|
||||
nextStateId: "PuzzleMenuState",
|
||||
});
|
||||
}
|
||||
|
||||
onBackButtonClicked() {
|
||||
this.renderMainMenu();
|
||||
this.renderSavegames();
|
||||
}
|
||||
|
||||
onSteamLinkClicked() {
|
||||
|
||||
@@ -57,8 +57,6 @@ export class PreloadState extends GameState {
|
||||
this.lastHintShown = -1000;
|
||||
this.nextHintDuration = 0;
|
||||
|
||||
this.currentStatus = "booting";
|
||||
|
||||
this.startLoading();
|
||||
}
|
||||
|
||||
|
||||
385
src/js/states/puzzle_menu.js
Normal file
385
src/js/states/puzzle_menu.js
Normal file
@@ -0,0 +1,385 @@
|
||||
import { globalConfig } from "../core/config";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { DialogWithForm } from "../core/modal_dialog_elements";
|
||||
import { FormElementInput } from "../core/modal_dialog_forms";
|
||||
import { TextualGameState } from "../core/textual_game_state";
|
||||
import { formatBigNumberFull } from "../core/utils";
|
||||
import { enumGameModeIds } from "../game/game_mode";
|
||||
import { ShapeDefinition } from "../game/shape_definition";
|
||||
import { Savegame } from "../savegame/savegame";
|
||||
import { T } from "../translations";
|
||||
|
||||
const categories = ["top-rated", "new", "easy", "short", "hard", "completed", "mine"];
|
||||
|
||||
/**
|
||||
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata}
|
||||
*/
|
||||
const SAMPLE_PUZZLE = {
|
||||
id: 1,
|
||||
shortKey: "CuCuCuCu",
|
||||
downloads: 0,
|
||||
likes: 0,
|
||||
averageTime: 1,
|
||||
completions: 1,
|
||||
difficulty: null,
|
||||
title: "Level 1",
|
||||
author: "verylongsteamnamewhichbreaks",
|
||||
completed: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata[]}
|
||||
*/
|
||||
const BUILTIN_PUZZLES = G_IS_DEV
|
||||
? [
|
||||
// { ...SAMPLE_PUZZLE, completed: true },
|
||||
// { ...SAMPLE_PUZZLE, completed: true },
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
// SAMPLE_PUZZLE,
|
||||
]
|
||||
: [];
|
||||
|
||||
const logger = createLogger("puzzle-menu");
|
||||
|
||||
let lastCategory = categories[0];
|
||||
|
||||
export class PuzzleMenuState extends TextualGameState {
|
||||
constructor() {
|
||||
super("PuzzleMenuState");
|
||||
this.loading = false;
|
||||
this.activeCategory = "";
|
||||
}
|
||||
|
||||
getStateHeaderTitle() {
|
||||
return T.puzzleMenu.title;
|
||||
}
|
||||
/**
|
||||
* Overrides the GameState implementation to provide our own html
|
||||
*/
|
||||
internalGetFullHtml() {
|
||||
let headerHtml = `
|
||||
<div class="headerBar">
|
||||
<h1><button class="backButton"></button> ${this.getStateHeaderTitle()}</h1>
|
||||
|
||||
<div class="actions">
|
||||
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
|
||||
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
return `
|
||||
${headerHtml}
|
||||
<div class="container">
|
||||
${this.getInnerHTML()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getMainContentHTML() {
|
||||
let html = `
|
||||
|
||||
|
||||
<div class="categoryChooser">
|
||||
${categories
|
||||
.map(
|
||||
category => `
|
||||
<button data-category="${category}" class="styledButton category">${T.puzzleMenu.categories[category]}</button>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
|
||||
<div class="puzzles" id="mainContainer"></div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
selectCategory(category) {
|
||||
lastCategory = category;
|
||||
if (category === this.activeCategory) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.activeCategory = category;
|
||||
|
||||
const activeCategory = this.htmlElement.querySelector(".active[data-category]");
|
||||
if (activeCategory) {
|
||||
activeCategory.classList.remove("active");
|
||||
}
|
||||
|
||||
this.htmlElement.querySelector(`[data-category="${category}"]`).classList.add("active");
|
||||
|
||||
const container = this.htmlElement.querySelector("#mainContainer");
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
|
||||
const loadingElement = document.createElement("div");
|
||||
loadingElement.classList.add("loader");
|
||||
loadingElement.innerText = T.global.loading + "...";
|
||||
container.appendChild(loadingElement);
|
||||
|
||||
this.asyncChannel
|
||||
.watch(this.getPuzzlesForCategory(category))
|
||||
.then(
|
||||
puzzles => this.renderPuzzles(puzzles),
|
||||
error => {
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.puzzleLoadFailed.title,
|
||||
T.dialogs.puzzleLoadFailed.desc + " " + error
|
||||
);
|
||||
this.renderPuzzles([]);
|
||||
}
|
||||
)
|
||||
.then(() => (this.loading = false));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
|
||||
*/
|
||||
renderPuzzles(puzzles) {
|
||||
const container = this.htmlElement.querySelector("#mainContainer");
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
|
||||
for (const puzzle of puzzles) {
|
||||
const elem = document.createElement("div");
|
||||
elem.classList.add("puzzle");
|
||||
elem.classList.toggle("completed", puzzle.completed);
|
||||
|
||||
if (puzzle.title) {
|
||||
const title = document.createElement("div");
|
||||
title.classList.add("title");
|
||||
title.innerText = puzzle.title;
|
||||
elem.appendChild(title);
|
||||
}
|
||||
|
||||
if (puzzle.author) {
|
||||
const author = document.createElement("div");
|
||||
author.classList.add("author");
|
||||
author.innerText = "by " + puzzle.author;
|
||||
elem.appendChild(author);
|
||||
}
|
||||
|
||||
const stats = document.createElement("div");
|
||||
stats.classList.add("stats");
|
||||
elem.appendChild(stats);
|
||||
|
||||
if (puzzle.downloads > 0) {
|
||||
const difficulty = document.createElement("div");
|
||||
difficulty.classList.add("difficulty");
|
||||
|
||||
const completionPercentage = Math.max(
|
||||
0,
|
||||
Math.min(100, Math.round((puzzle.completions / puzzle.downloads) * 100.0))
|
||||
);
|
||||
difficulty.innerText = completionPercentage + "%";
|
||||
stats.appendChild(difficulty);
|
||||
|
||||
if (completionPercentage < 10) {
|
||||
difficulty.classList.add("stage--hard");
|
||||
} else if (completionPercentage < 30) {
|
||||
difficulty.classList.add("stage--medium");
|
||||
} else if (completionPercentage < 60) {
|
||||
difficulty.classList.add("stage--normal");
|
||||
} else {
|
||||
difficulty.classList.add("stage--easy");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.activeCategory === "mine") {
|
||||
const downloads = document.createElement("div");
|
||||
downloads.classList.add("downloads");
|
||||
downloads.innerText = String(puzzle.downloads);
|
||||
stats.appendChild(downloads);
|
||||
stats.classList.add("withDownloads");
|
||||
}
|
||||
|
||||
const likes = document.createElement("div");
|
||||
likes.classList.add("likes");
|
||||
likes.innerText = formatBigNumberFull(puzzle.likes);
|
||||
stats.appendChild(likes);
|
||||
|
||||
const definition = ShapeDefinition.fromShortKey(puzzle.shortKey);
|
||||
const canvas = definition.generateAsCanvas(100 * this.app.getEffectiveUiScale());
|
||||
|
||||
const icon = document.createElement("div");
|
||||
icon.classList.add("icon");
|
||||
icon.appendChild(canvas);
|
||||
elem.appendChild(icon);
|
||||
|
||||
container.appendChild(elem);
|
||||
|
||||
this.trackClicks(elem, () => this.playPuzzle(puzzle));
|
||||
}
|
||||
|
||||
if (puzzles.length === 0) {
|
||||
const elem = document.createElement("div");
|
||||
elem.classList.add("empty");
|
||||
elem.innerText = T.puzzleMenu.noPuzzles;
|
||||
container.appendChild(elem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} category
|
||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]}
|
||||
*/
|
||||
getPuzzlesForCategory(category) {
|
||||
if (category === "levels") {
|
||||
return Promise.resolve(BUILTIN_PUZZLES);
|
||||
}
|
||||
|
||||
const result = this.app.clientApi.apiListPuzzles(category);
|
||||
return result.catch(err => {
|
||||
logger.error("Failed to get", category, ":", err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata} puzzle
|
||||
*/
|
||||
playPuzzle(puzzle) {
|
||||
const closeLoading = this.dialogs.showLoadingDialog();
|
||||
|
||||
this.app.clientApi.apiDownloadPuzzle(puzzle.id).then(
|
||||
puzzleData => {
|
||||
closeLoading();
|
||||
logger.log("Got puzzle:", puzzleData);
|
||||
this.startLoadedPuzzle(puzzleData);
|
||||
},
|
||||
err => {
|
||||
closeLoading();
|
||||
logger.error("Failed to download puzzle", puzzle.id, ":", err);
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.puzzleDownloadError.title,
|
||||
T.dialogs.puzzleDownloadError.desc + " " + err
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle
|
||||
*/
|
||||
startLoadedPuzzle(puzzle) {
|
||||
const savegame = this.createEmptySavegame();
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzlePlay,
|
||||
gameModeParameters: {
|
||||
puzzle,
|
||||
},
|
||||
savegame,
|
||||
});
|
||||
}
|
||||
|
||||
onEnter(payload) {
|
||||
this.selectCategory(lastCategory);
|
||||
|
||||
if (payload && payload.error) {
|
||||
this.dialogs.showWarning(payload.error.title, payload.error.desc);
|
||||
}
|
||||
|
||||
for (const category of categories) {
|
||||
const button = this.htmlElement.querySelector(`[data-category="${category}"]`);
|
||||
this.trackClicks(button, () => this.selectCategory(category));
|
||||
}
|
||||
|
||||
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
|
||||
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
|
||||
|
||||
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
||||
// this.createNewPuzzle();
|
||||
this.playPuzzle(SAMPLE_PUZZLE);
|
||||
}
|
||||
}
|
||||
|
||||
createEmptySavegame() {
|
||||
return new Savegame(this.app, {
|
||||
internalId: "puzzle",
|
||||
metaDataRef: {
|
||||
internalId: "puzzle",
|
||||
lastUpdate: 0,
|
||||
version: 0,
|
||||
level: 0,
|
||||
name: "puzzle",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
loadPuzzle() {
|
||||
const shortKeyInput = new FormElementInput({
|
||||
id: "shortKey",
|
||||
label: null,
|
||||
placeholder: "",
|
||||
defaultValue: "",
|
||||
validator: val => ShapeDefinition.isValidShortKey(val),
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
app: this.app,
|
||||
title: T.dialogs.puzzleLoadShortKey.title,
|
||||
desc: T.dialogs.puzzleLoadShortKey.desc,
|
||||
formElements: [shortKeyInput],
|
||||
buttons: ["ok:good:enter"],
|
||||
});
|
||||
this.dialogs.internalShowDialog(dialog);
|
||||
|
||||
dialog.buttonSignals.ok.add(() => {
|
||||
const closeLoading = this.dialogs.showLoadingDialog();
|
||||
|
||||
this.app.clientApi.apiDownloadPuzzleByKey(shortKeyInput.getValue()).then(
|
||||
puzzle => {
|
||||
closeLoading();
|
||||
this.startLoadedPuzzle(puzzle);
|
||||
},
|
||||
err => {
|
||||
closeLoading();
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.puzzleDownloadError.title,
|
||||
T.dialogs.puzzleDownloadError.desc + " " + err
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
createNewPuzzle(force = false) {
|
||||
if (!force && !this.app.clientApi.isLoggedIn()) {
|
||||
const signals = this.dialogs.showWarning(
|
||||
T.dialogs.puzzleCreateOffline.title,
|
||||
T.dialogs.puzzleCreateOffline.desc,
|
||||
["cancel:good", "continue:bad"]
|
||||
);
|
||||
signals.continue.add(() => this.createNewPuzzle(true));
|
||||
return;
|
||||
}
|
||||
|
||||
const savegame = this.createEmptySavegame();
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzleEdit,
|
||||
savegame,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user