DLC: Next Puzzle button & Search functionality, other stuff

pull/1286/head
tobspr 3 years ago
parent 06d5c6a6dc
commit 8b5cd160b6

@ -137,16 +137,20 @@
button.continue { button.continue {
background: #555; background: #555;
@include S(margin-right, 10px);
} }
button.menu { button.menu {
background: #555;
}
button.nextPuzzle {
background-color: $colorGreenBright; background-color: $colorGreenBright;
} }
> button { > button {
@include S(min-width, 100px); @include S(min-width, 100px);
@include S(padding, 10px, 20px); @include S(padding, 8px, 16px);
@include S(margin, 0, 6px);
@include IncreasedClickArea(0px); @include IncreasedClickArea(0px);
} }
} }

@ -0,0 +1,41 @@
#ingame_HUD_PuzzleNextPuzzle {
position: absolute;
@include S(top, 17px);
@include S(right, 10px);
display: flex;
flex-direction: column;
align-items: flex-end;
backdrop-filter: blur(D(1px));
padding: D(3px);
> .button {
@include ButtonText;
@include IncreasedClickArea(0px);
pointer-events: all;
cursor: pointer;
position: relative;
color: #333438;
transition: all 0.12s ease-in-out;
text-transform: uppercase;
transition-property: opacity, transform;
@include PlainText;
@include S(padding-right, 25px);
opacity: 1;
@include DarkThemeInvert;
&:hover {
opacity: 0.9 !important;
}
&.pressed {
transform: scale(0.95) !important;
}
& {
/* @load-async */
background: uiResource("icons/state_next_button.png") right center / D(15px) no-repeat;
}
}
}

@ -65,6 +65,7 @@
@import "ingame_hud/puzzle_play_settings"; @import "ingame_hud/puzzle_play_settings";
@import "ingame_hud/puzzle_play_metadata"; @import "ingame_hud/puzzle_play_metadata";
@import "ingame_hud/puzzle_complete_notification"; @import "ingame_hud/puzzle_complete_notification";
@import "ingame_hud/puzzle_next";
// prettier-ignore // prettier-ignore
$elements: $elements:
@ -83,6 +84,7 @@ ingame_HUD_PinnedShapes,
ingame_HUD_GameMenu, ingame_HUD_GameMenu,
ingame_HUD_KeybindingOverlay, ingame_HUD_KeybindingOverlay,
ingame_HUD_PuzzleBackToMenu, ingame_HUD_PuzzleBackToMenu,
ingame_HUD_PuzzleNextPuzzle,
ingame_HUD_PuzzleEditorReview, ingame_HUD_PuzzleEditorReview,
ingame_HUD_PuzzleEditorControls, ingame_HUD_PuzzleEditorControls,
ingame_HUD_PuzzleEditorTitle, ingame_HUD_PuzzleEditorTitle,
@ -134,6 +136,7 @@ body.uiHidden {
#ingame_HUD_GameMenu, #ingame_HUD_GameMenu,
#ingame_HUD_PinnedShapes, #ingame_HUD_PinnedShapes,
#ingame_HUD_PuzzleBackToMenu, #ingame_HUD_PuzzleBackToMenu,
#ingame_HUD_PuzzleNextPuzzle,
#ingame_HUD_PuzzleEditorReview, #ingame_HUD_PuzzleEditorReview,
#ingame_HUD_Notifications, #ingame_HUD_Notifications,
#ingame_HUD_TutorialHints, #ingame_HUD_TutorialHints,

@ -15,8 +15,73 @@
} }
> .container { > .container {
.searchForm {
display: flex;
align-items: center;
justify-content: center;
color: #333;
background: $accentColorBright;
@include S(padding, 5px);
@include S(border-radius, $globalBorderRadius);
flex-wrap: wrap;
input.search {
color: #333;
margin: 0;
display: inline-block;
flex-grow: 1;
@include S(padding, 5px, 10px);
@include S(min-width, 50px);
&::placeholder {
color: #aaa;
}
}
select {
color: #333;
border: 0;
@include S(padding, 5px);
@include S(border-radius, $globalBorderRadius);
@include S(padding, 7px, 10px);
@include S(margin-left, 5px);
@include PlainText;
}
.filterCompleted {
@include S(margin-left, 20px);
pointer-events: all;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
@include PlainText;
@include S(margin-right, 10px);
input {
@include S(width, 15px);
@include S(height, 15px);
@include S(margin-right, 5px);
@include S(border-radius, $globalBorderRadius);
border: 0;
}
}
button[type="submit"] {
@include S(padding, 7px, 10px, 5px);
@include S(margin-left, 20px);
@include S(margin-top, 4px);
@include S(margin-bottom, 4px);
margin-left: auto;
}
}
> .mainContent { > .mainContent {
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
> .categoryChooser { > .categoryChooser {
> .categories { > .categories {
@ -79,8 +144,8 @@
@include S(grid-gap, 7px); @include S(grid-gap, 7px);
@include S(margin-top, 10px); @include S(margin-top, 10px);
@include S(padding-right, 4px); @include S(padding-right, 4px);
@include S(height, 320px);
overflow-y: scroll; overflow-y: scroll;
flex-grow: 1;
pointer-events: all; pointer-events: all;
position: relative; position: relative;

@ -50,7 +50,8 @@
} }
button.categoryButton, button.categoryButton,
button.about { button.about,
button.privacy {
background-color: $colorCategoryButton; background-color: $colorCategoryButton;
color: #777a7f; color: #777a7f;
@ -68,6 +69,10 @@
} }
} }
button.privacy {
@include S(margin-top, 4px);
}
.versionbar { .versionbar {
@include S(margin-top, 10px); @include S(margin-top, 10px);
@ -180,7 +185,8 @@
.container .content { .container .content {
.sidebar { .sidebar {
button.categoryButton, button.categoryButton,
button.about { button.about,
button.privacy {
color: #ccc; color: #ccc;
background-color: darken($darkModeControlsBackground, 5); background-color: darken($darkModeControlsBackground, 5);

@ -17,6 +17,8 @@ export const THIRDPARTY_URLS = {
reddit: "https://www.reddit.com/r/shapezio", reddit: "https://www.reddit.com/r/shapezio",
shapeViewer: "https://viewer.shapez.io", shapeViewer: "https://viewer.shapez.io",
privacyPolicy: "https://tobspr.io/privacy.html",
standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/", standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/",
stanaloneCampaignLink: "https://get.shapez.io", stanaloneCampaignLink: "https://get.shapez.io",
puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC", puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC",

@ -0,0 +1,25 @@
import { makeDiv } from "../../../core/utils";
import { T } from "../../../translations";
import { PuzzlePlayGameMode } from "../../modes/puzzle_play";
import { BaseHUDPart } from "../base_hud_part";
export class HUDPuzzleNextPuzzle extends BaseHUDPart {
createElements(parent) {
this.element = makeDiv(parent, "ingame_HUD_PuzzleNextPuzzle");
this.button = document.createElement("button");
this.button.classList.add("button");
this.button.innerText = T.ingame.puzzleCompletion.nextPuzzle;
this.element.appendChild(this.button);
this.trackClicks(this.button, this.nextPuzzle);
}
initialize() {}
nextPuzzle() {
const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
this.root.gameState.moveToState("PuzzleMenuState", {
continueQueue: gameMode.nextPuzzles,
});
}
}

@ -6,13 +6,8 @@ import { InputReceiver } from "../../../core/input_receiver";
import { makeDiv } from "../../../core/utils"; import { makeDiv } from "../../../core/utils";
import { SOUNDS } from "../../../platform/sound"; import { SOUNDS } from "../../../platform/sound";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { enumColors } from "../../colors";
import { ColorItem } from "../../items/color_item";
import { finalGameShape, rocketShape } from "../../modes/regular";
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
import { ShapeItem } from "../../items/shape_item";
import { ShapeDefinition } from "../../shape_definition";
export class HUDPuzzleCompleteNotification extends BaseHUDPart { export class HUDPuzzleCompleteNotification extends BaseHUDPart {
initialize() { initialize() {
@ -68,10 +63,21 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
this.menuBtn.classList.add("menu", "styledButton"); this.menuBtn.classList.add("menu", "styledButton");
this.menuBtn.innerText = T.ingame.puzzleCompletion.menuBtn; this.menuBtn.innerText = T.ingame.puzzleCompletion.menuBtn;
buttonBar.appendChild(this.menuBtn); buttonBar.appendChild(this.menuBtn);
this.trackClicks(this.menuBtn, () => { this.trackClicks(this.menuBtn, () => {
this.close(true); this.close(true);
}); });
const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
if (gameMode.nextPuzzles.length > 0) {
this.nextPuzzleBtn = document.createElement("button");
this.nextPuzzleBtn.classList.add("nextPuzzle", "styledButton");
this.nextPuzzleBtn.innerText = T.ingame.puzzleCompletion.nextPuzzle;
buttonBar.appendChild(this.nextPuzzleBtn);
this.trackClicks(this.nextPuzzleBtn, () => {
this.nextPuzzle();
});
}
} }
updateState() { updateState() {
@ -93,6 +99,15 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
return this.visible; return this.visible;
} }
nextPuzzle() {
const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode);
gameMode.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion)).then(() => {
this.root.gameState.moveToState("PuzzleMenuState", {
continueQueue: gameMode.nextPuzzles,
});
});
}
close(toMenu) { close(toMenu) {
/** @type {PuzzlePlayGameMode} */ (this.root.gameMode) /** @type {PuzzlePlayGameMode} */ (this.root.gameMode)
.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion)) .trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion))

@ -30,6 +30,7 @@ import { HUDPuzzlePlaySettings } from "../hud/parts/puzzle_play_settings";
import { MetaBlockBuilding } from "../buildings/block"; import { MetaBlockBuilding } from "../buildings/block";
import { MetaBuilding } from "../meta_building"; import { MetaBuilding } from "../meta_building";
import { gMetaBuildingRegistry } from "../../core/global_registries"; import { gMetaBuildingRegistry } from "../../core/global_registries";
import { HUDPuzzleNextPuzzle } from "../hud/parts/HUDPuzzleNextPuzzle";
const logger = createLogger("puzzle-play"); const logger = createLogger("puzzle-play");
const copy = require("clipboard-copy"); const copy = require("clipboard-copy");
@ -43,8 +44,9 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
* @param {GameRoot} root * @param {GameRoot} root
* @param {object} payload * @param {object} payload
* @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle * @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle
* @param {Array<number> | undefined} payload.nextPuzzles
*/ */
constructor(root, { puzzle }) { constructor(root, { puzzle, nextPuzzles }) {
super(root); super(root);
/** @type {Array<typeof MetaBuilding>} */ /** @type {Array<typeof MetaBuilding>} */
@ -95,6 +97,15 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
root.signals.postLoadHook.add(this.loadPuzzle, this); root.signals.postLoadHook.add(this.loadPuzzle, this);
this.puzzle = puzzle; this.puzzle = puzzle;
/**
* @type {Array<number>}
*/
this.nextPuzzles = nextPuzzles || [];
if (this.nextPuzzles.length > 0) {
this.additionalHudParts.puzzleNext = HUDPuzzleNextPuzzle;
}
} }
loadPuzzle() { loadPuzzle() {

@ -143,6 +143,20 @@ export class ClientAPI {
return this._request("/v1/puzzles/list/" + category, {}); return this._request("/v1/puzzles/list/" + category, {});
} }
/**
* @param {{ searchTerm: string; difficulty: string; duration: string }} searchOptions
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>}
*/
apiSearchPuzzles(searchOptions) {
if (!this.isLoggedIn()) {
return Promise.reject("not-logged-in");
}
return this._request("/v1/puzzles/search", {
method: "POST",
body: searchOptions,
});
}
/** /**
* @param {number} puzzleId * @param {number} puzzleId
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>} * @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
@ -169,7 +183,7 @@ export class ClientAPI {
} }
/** /**
* @param {number} shortKey * @param {string} shortKey
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>} * @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
*/ */
apiDownloadPuzzleByKey(shortKey) { apiDownloadPuzzleByKey(shortKey) {

@ -64,6 +64,24 @@ export class Savegame extends ReadWriteProxy {
return savegameInterfaces[Savegame.getCurrentVersion()]; return savegameInterfaces[Savegame.getCurrentVersion()];
} }
/**
*
* @param {Application} app
* @returns
*/
static createPuzzleSavegame(app) {
return new Savegame(app, {
internalId: "puzzle",
metaDataRef: {
internalId: "puzzle",
lastUpdate: 0,
version: 0,
level: 0,
name: "puzzle",
},
});
}
/** /**
* @returns {number} * @returns {number}
*/ */

@ -13,17 +13,30 @@ const navigation = {
categories: ["official", "top-rated", "trending", "trending-weekly", "new"], categories: ["official", "top-rated", "trending", "trending-weekly", "new"],
difficulties: ["easy", "medium", "hard"], difficulties: ["easy", "medium", "hard"],
account: ["mine", "completed"], account: ["mine", "completed"],
search: ["search"],
}; };
const logger = createLogger("puzzle-menu"); const logger = createLogger("puzzle-menu");
let lastCategory = "official"; let lastCategory = "official";
let lastSearchOptions = {
searchTerm: "",
difficulty: "any",
duration: "any",
includeCompleted: false,
};
export class PuzzleMenuState extends TextualGameState { export class PuzzleMenuState extends TextualGameState {
constructor() { constructor() {
super("PuzzleMenuState"); super("PuzzleMenuState");
this.loading = false; this.loading = false;
this.activeCategory = ""; this.activeCategory = "";
/**
* @type {Array<import("../savegame/savegame_typedefs").PuzzleMetadata>}
*/
this.puzzles = [];
} }
getThemeMusic() { getThemeMusic() {
@ -99,13 +112,23 @@ export class PuzzleMenuState extends TextualGameState {
activeCategory.classList.remove("active"); activeCategory.classList.remove("active");
} }
this.htmlElement.querySelector(`[data-category="${category}"]`).classList.add("active"); const categoryElement = this.htmlElement.querySelector(`[data-category="${category}"]`);
if (categoryElement) {
categoryElement.classList.add("active");
}
const container = this.htmlElement.querySelector("#mainContainer"); const container = this.htmlElement.querySelector("#mainContainer");
while (container.firstChild) { while (container.firstChild) {
container.removeChild(container.firstChild); container.removeChild(container.firstChild);
} }
if (category === "search") {
this.loading = false;
this.startSearch();
return;
}
const loadingElement = document.createElement("div"); const loadingElement = document.createElement("div");
loadingElement.classList.add("loader"); loadingElement.classList.add("loader");
loadingElement.innerText = T.global.loading + "..."; loadingElement.innerText = T.global.loading + "...";
@ -160,23 +183,148 @@ export class PuzzleMenuState extends TextualGameState {
} }
const children = navigation[rootCategory]; const children = navigation[rootCategory];
for (const category of children) { if (children.length > 1) {
const button = document.createElement("button"); for (const category of children) {
button.setAttribute("data-category", category); const button = document.createElement("button");
button.classList.add("styledButton", "category", "child"); button.setAttribute("data-category", category);
button.innerText = T.puzzleMenu.categories[category]; button.classList.add("styledButton", "category", "child");
this.trackClicks(button, () => this.selectCategory(category)); button.innerText = T.puzzleMenu.categories[category];
subContainer.appendChild(button); this.trackClicks(button, () => this.selectCategory(category));
subContainer.appendChild(button);
}
}
if (rootCategory === "search") {
this.renderSearchForm(subContainer);
} }
this.selectCategory(subCategory); this.selectCategory(subCategory);
} }
renderSearchForm(parent) {
const container = document.createElement("form");
container.classList.add("searchForm");
// Search
const searchField = document.createElement("input");
searchField.value = lastSearchOptions.searchTerm;
searchField.classList.add("search");
searchField.setAttribute("type", "text");
searchField.setAttribute("placeholder", T.puzzleMenu.search.placeholder);
searchField.addEventListener("input", () => {
lastSearchOptions.searchTerm = searchField.value.trim();
});
container.appendChild(searchField);
// Difficulty
const difficultyFilter = document.createElement("select");
for (const difficulty of ["any", "easy", "medium", "hard"]) {
const option = document.createElement("option");
option.value = difficulty;
option.innerText = T.puzzleMenu.search.difficulties[difficulty];
if (option.value === lastSearchOptions.difficulty) {
option.setAttribute("selected", "selected");
}
difficultyFilter.appendChild(option);
}
difficultyFilter.addEventListener("change", () => {
const option = difficultyFilter.value;
lastSearchOptions.difficulty = option;
});
container.appendChild(difficultyFilter);
// Duration
const durationFilter = document.createElement("select");
for (const duration of ["any", "short", "medium", "long"]) {
const option = document.createElement("option");
option.value = duration;
option.innerText = T.puzzleMenu.search.durations[duration];
if (option.value === lastSearchOptions.duration) {
option.setAttribute("selected", "selected");
}
durationFilter.appendChild(option);
}
durationFilter.addEventListener("change", () => {
const option = durationFilter.value;
lastSearchOptions.duration = option;
});
container.appendChild(durationFilter);
// Include completed
const labelCompleted = document.createElement("label");
labelCompleted.classList.add("filterCompleted");
const inputCompleted = document.createElement("input");
inputCompleted.setAttribute("type", "checkbox");
if (lastSearchOptions.includeCompleted) {
inputCompleted.setAttribute("checked", "checked");
}
inputCompleted.addEventListener("change", () => {
lastSearchOptions.includeCompleted = inputCompleted.checked;
});
labelCompleted.appendChild(inputCompleted);
const text = document.createTextNode(T.puzzleMenu.search.includeCompleted);
labelCompleted.appendChild(text);
container.appendChild(labelCompleted);
// Submit
const submitButton = document.createElement("button");
submitButton.classList.add("styledButton");
submitButton.setAttribute("type", "submit");
submitButton.innerText = T.puzzleMenu.search.action;
container.appendChild(submitButton);
container.addEventListener("submit", event => {
event.preventDefault();
console.log("Search:", searchField.value.trim());
this.startSearch();
});
parent.appendChild(container);
}
startSearch() {
if (this.loading) {
return;
}
this.loading = true;
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.app.clientApi.apiSearchPuzzles(lastSearchOptions))
.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 * @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
*/ */
renderPuzzles(puzzles) { renderPuzzles(puzzles) {
this.puzzles = puzzles;
const container = this.htmlElement.querySelector("#mainContainer"); const container = this.htmlElement.querySelector("#mainContainer");
while (container.firstChild) { while (container.firstChild) {
container.removeChild(container.firstChild); container.removeChild(container.firstChild);
@ -223,15 +371,15 @@ export class PuzzleMenuState extends TextualGameState {
difficulty.innerText = completionPercentage + "%"; difficulty.innerText = completionPercentage + "%";
stats.appendChild(difficulty); stats.appendChild(difficulty);
if (completionPercentage < 40) { if (puzzle.difficulty < 0.2) {
difficulty.classList.add("stage--easy");
difficulty.innerText = T.puzzleMenu.difficulties.easy;
} else if (puzzle.difficulty > 0.6) {
difficulty.classList.add("stage--hard"); difficulty.classList.add("stage--hard");
difficulty.innerText = T.puzzleMenu.difficulties.hard; difficulty.innerText = T.puzzleMenu.difficulties.hard;
} else if (completionPercentage < 80) { } else {
difficulty.classList.add("stage--medium"); difficulty.classList.add("stage--medium");
difficulty.innerText = T.puzzleMenu.difficulties.medium; difficulty.innerText = T.puzzleMenu.difficulties.medium;
} else {
difficulty.classList.add("stage--easy");
difficulty.innerText = T.puzzleMenu.difficulties.easy;
} }
} }
@ -275,7 +423,7 @@ export class PuzzleMenuState extends TextualGameState {
container.appendChild(elem); container.appendChild(elem);
this.trackClicks(elem, () => this.playPuzzle(puzzle)); this.trackClicks(elem, () => this.playPuzzle(puzzle.id));
} }
if (puzzles.length === 0) { if (puzzles.length === 0) {
@ -328,20 +476,26 @@ export class PuzzleMenuState extends TextualGameState {
/** /**
* *
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata} puzzle * @param {number} puzzleId
* @param {Array<number>=} nextPuzzles
*/ */
playPuzzle(puzzle) { playPuzzle(puzzleId, nextPuzzles) {
const closeLoading = this.dialogs.showLoadingDialog(); const closeLoading = this.dialogs.showLoadingDialog();
this.app.clientApi.apiDownloadPuzzle(puzzle.id).then( this.asyncChannel.watch(this.app.clientApi.apiDownloadPuzzle(puzzleId)).then(
puzzleData => { puzzleData => {
closeLoading(); closeLoading();
logger.log("Got puzzle:", puzzleData);
this.startLoadedPuzzle(puzzleData); nextPuzzles =
nextPuzzles || this.puzzles.filter(puzzle => !puzzle.completed).map(puzzle => puzzle.id);
nextPuzzles = nextPuzzles.filter(id => id !== puzzleId);
logger.log("Got puzzle:", puzzleData, "next puzzles:", nextPuzzles);
this.startLoadedPuzzle(puzzleData, nextPuzzles);
}, },
err => { err => {
closeLoading(); closeLoading();
logger.error("Failed to download puzzle", puzzle.id, ":", err); logger.error("Failed to download puzzle", puzzleId, ":", err);
this.dialogs.showWarning( this.dialogs.showWarning(
T.dialogs.puzzleDownloadError.title, T.dialogs.puzzleDownloadError.title,
T.dialogs.puzzleDownloadError.desc + " " + err T.dialogs.puzzleDownloadError.desc + " " + err
@ -354,18 +508,24 @@ export class PuzzleMenuState extends TextualGameState {
* *
* @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle * @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle
*/ */
startLoadedPuzzle(puzzle) { startLoadedPuzzle(puzzle, nextPuzzles) {
const savegame = this.createEmptySavegame(); const savegame = Savegame.createPuzzleSavegame(this.app);
this.moveToState("InGameState", { this.moveToState("InGameState", {
gameModeId: enumGameModeIds.puzzlePlay, gameModeId: enumGameModeIds.puzzlePlay,
gameModeParameters: { gameModeParameters: {
puzzle, puzzle,
nextPuzzles,
}, },
savegame, savegame,
}); });
} }
onEnter(payload) { onEnter(payload) {
if (payload.continueQueue) {
logger.log("Continuing puzzle queue:", payload);
this.playPuzzle(payload.continueQueue[0], payload.continueQueue.slice(1));
}
// Find old category // Find old category
let rootCategory = "categories"; let rootCategory = "categories";
for (const [id, children] of Object.entries(navigation)) { for (const [id, children] of Object.entries(navigation)) {
@ -390,26 +550,13 @@ export class PuzzleMenuState extends TextualGameState {
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle()); this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
} }
createEmptySavegame() {
return new Savegame(this.app, {
internalId: "puzzle",
metaDataRef: {
internalId: "puzzle",
lastUpdate: 0,
version: 0,
level: 0,
name: "puzzle",
},
});
}
loadPuzzle() { loadPuzzle() {
const shortKeyInput = new FormElementInput({ const shortKeyInput = new FormElementInput({
id: "shortKey", id: "shortKey",
label: null, label: null,
placeholder: "", placeholder: "",
defaultValue: "", defaultValue: "",
validator: val => ShapeDefinition.isValidShortKey(val), validator: val => ShapeDefinition.isValidShortKey(val) || val.startsWith("/"),
}); });
const dialog = new DialogWithForm({ const dialog = new DialogWithForm({
@ -422,9 +569,16 @@ export class PuzzleMenuState extends TextualGameState {
this.dialogs.internalShowDialog(dialog); this.dialogs.internalShowDialog(dialog);
dialog.buttonSignals.ok.add(() => { dialog.buttonSignals.ok.add(() => {
const searchTerm = shortKeyInput.getValue();
if (searchTerm === "/apikey") {
alert("Your api key is: " + this.app.clientApi.token);
return;
}
const closeLoading = this.dialogs.showLoadingDialog(); const closeLoading = this.dialogs.showLoadingDialog();
this.app.clientApi.apiDownloadPuzzleByKey(shortKeyInput.getValue()).then( this.app.clientApi.apiDownloadPuzzleByKey(searchTerm).then(
puzzle => { puzzle => {
closeLoading(); closeLoading();
this.startLoadedPuzzle(puzzle); this.startLoadedPuzzle(puzzle);
@ -451,7 +605,7 @@ export class PuzzleMenuState extends TextualGameState {
return; return;
} }
const savegame = this.createEmptySavegame(); const savegame = Savegame.createPuzzleSavegame(this.app);
this.moveToState("InGameState", { this.moveToState("InGameState", {
gameModeId: enumGameModeIds.puzzleEdit, gameModeId: enumGameModeIds.puzzleEdit,
savegame, savegame,

@ -1,3 +1,4 @@
import { THIRDPARTY_URLS } from "../core/config";
import { TextualGameState } from "../core/textual_game_state"; import { TextualGameState } from "../core/textual_game_state";
import { formatSecondsToTimeAgo } from "../core/utils"; import { formatSecondsToTimeAgo } from "../core/utils";
import { allApplicationSettings, enumCategories } from "../profile/application_settings"; import { allApplicationSettings, enumCategories } from "../profile/application_settings";
@ -34,6 +35,8 @@ export class SettingsState extends TextualGameState {
? "" ? ""
: ` : `
<button class="styledButton about">${T.about.title}</button> <button class="styledButton about">${T.about.title}</button>
<button class="styledButton privacy">Privacy Policy</button>
` `
} }
<div class="versionbar"> <div class="versionbar">
@ -109,6 +112,9 @@ export class SettingsState extends TextualGameState {
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, { this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
preventDefault: false, preventDefault: false,
}); });
this.trackClicks(this.htmlElement.querySelector(".privacy"), this.onPrivacyClicked, {
preventDefault: false,
});
} }
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings"); const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
@ -180,6 +186,10 @@ export class SettingsState extends TextualGameState {
this.moveToStateAddGoBack("AboutState"); this.moveToStateAddGoBack("AboutState");
} }
onPrivacyClicked() {
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.privacyPolicy);
}
onKeybindingsClicked() { onKeybindingsClicked() {
this.moveToStateAddGoBack("KeybindingsState"); this.moveToStateAddGoBack("KeybindingsState");
} }

@ -155,6 +155,23 @@ puzzleMenu:
account: My Puzzles account: My Puzzles
search: Search search: Search
search:
action: Search
placeholder: Enter a puzzle or author name
includeCompleted: Include Completed
difficulties:
any: Any Difficulty
easy: Easy
medium: Medium
hard: Hard
durations:
any: Any Duration
short: Short (< 2 min)
medium: Normal
long: Long (> 10 min)
difficulties: difficulties:
easy: Easy easy: Easy
medium: Medium medium: Medium
@ -657,6 +674,7 @@ ingame:
continueBtn: Keep Playing continueBtn: Keep Playing
menuBtn: Menu menuBtn: Menu
nextPuzzle: Next Puzzle
puzzleMetadata: puzzleMetadata:
author: Author author: Author

Loading…
Cancel
Save