mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
DLC: Next Puzzle button & Search functionality, other stuff
This commit is contained in:
parent
06d5c6a6dc
commit
8b5cd160b6
@ -137,16 +137,20 @@
|
||||
|
||||
button.continue {
|
||||
background: #555;
|
||||
@include S(margin-right, 10px);
|
||||
}
|
||||
|
||||
button.menu {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
button.nextPuzzle {
|
||||
background-color: $colorGreenBright;
|
||||
}
|
||||
|
||||
> button {
|
||||
@include S(min-width, 100px);
|
||||
@include S(padding, 10px, 20px);
|
||||
@include S(padding, 8px, 16px);
|
||||
@include S(margin, 0, 6px);
|
||||
@include IncreasedClickArea(0px);
|
||||
}
|
||||
}
|
||||
|
41
src/css/ingame_hud/puzzle_next.scss
Normal file
41
src/css/ingame_hud/puzzle_next.scss
Normal file
@ -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_metadata";
|
||||
@import "ingame_hud/puzzle_complete_notification";
|
||||
@import "ingame_hud/puzzle_next";
|
||||
|
||||
// prettier-ignore
|
||||
$elements:
|
||||
@ -83,6 +84,7 @@ ingame_HUD_PinnedShapes,
|
||||
ingame_HUD_GameMenu,
|
||||
ingame_HUD_KeybindingOverlay,
|
||||
ingame_HUD_PuzzleBackToMenu,
|
||||
ingame_HUD_PuzzleNextPuzzle,
|
||||
ingame_HUD_PuzzleEditorReview,
|
||||
ingame_HUD_PuzzleEditorControls,
|
||||
ingame_HUD_PuzzleEditorTitle,
|
||||
@ -134,6 +136,7 @@ body.uiHidden {
|
||||
#ingame_HUD_GameMenu,
|
||||
#ingame_HUD_PinnedShapes,
|
||||
#ingame_HUD_PuzzleBackToMenu,
|
||||
#ingame_HUD_PuzzleNextPuzzle,
|
||||
#ingame_HUD_PuzzleEditorReview,
|
||||
#ingame_HUD_Notifications,
|
||||
#ingame_HUD_TutorialHints,
|
||||
|
@ -15,8 +15,73 @@
|
||||
}
|
||||
|
||||
> .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 {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .categoryChooser {
|
||||
> .categories {
|
||||
@ -79,8 +144,8 @@
|
||||
@include S(grid-gap, 7px);
|
||||
@include S(margin-top, 10px);
|
||||
@include S(padding-right, 4px);
|
||||
@include S(height, 320px);
|
||||
overflow-y: scroll;
|
||||
flex-grow: 1;
|
||||
pointer-events: all;
|
||||
position: relative;
|
||||
|
||||
|
@ -50,7 +50,8 @@
|
||||
}
|
||||
|
||||
button.categoryButton,
|
||||
button.about {
|
||||
button.about,
|
||||
button.privacy {
|
||||
background-color: $colorCategoryButton;
|
||||
color: #777a7f;
|
||||
|
||||
@ -68,6 +69,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
button.privacy {
|
||||
@include S(margin-top, 4px);
|
||||
}
|
||||
|
||||
.versionbar {
|
||||
@include S(margin-top, 10px);
|
||||
|
||||
@ -180,7 +185,8 @@
|
||||
.container .content {
|
||||
.sidebar {
|
||||
button.categoryButton,
|
||||
button.about {
|
||||
button.about,
|
||||
button.privacy {
|
||||
color: #ccc;
|
||||
background-color: darken($darkModeControlsBackground, 5);
|
||||
|
||||
|
@ -17,6 +17,8 @@ export const THIRDPARTY_URLS = {
|
||||
reddit: "https://www.reddit.com/r/shapezio",
|
||||
shapeViewer: "https://viewer.shapez.io",
|
||||
|
||||
privacyPolicy: "https://tobspr.io/privacy.html",
|
||||
|
||||
standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/",
|
||||
stanaloneCampaignLink: "https://get.shapez.io",
|
||||
puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC",
|
||||
|
25
src/js/game/hud/parts/HUDPuzzleNextPuzzle.js
Normal file
25
src/js/game/hud/parts/HUDPuzzleNextPuzzle.js
Normal file
@ -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 { SOUNDS } from "../../../platform/sound";
|
||||
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 { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { ShapeItem } from "../../items/shape_item";
|
||||
import { ShapeDefinition } from "../../shape_definition";
|
||||
|
||||
export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
||||
initialize() {
|
||||
@ -68,10 +63,21 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
||||
this.menuBtn.classList.add("menu", "styledButton");
|
||||
this.menuBtn.innerText = T.ingame.puzzleCompletion.menuBtn;
|
||||
buttonBar.appendChild(this.menuBtn);
|
||||
|
||||
this.trackClicks(this.menuBtn, () => {
|
||||
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() {
|
||||
@ -93,6 +99,15 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart {
|
||||
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) {
|
||||
/** @type {PuzzlePlayGameMode} */ (this.root.gameMode)
|
||||
.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 { MetaBuilding } from "../meta_building";
|
||||
import { gMetaBuildingRegistry } from "../../core/global_registries";
|
||||
import { HUDPuzzleNextPuzzle } from "../hud/parts/HUDPuzzleNextPuzzle";
|
||||
|
||||
const logger = createLogger("puzzle-play");
|
||||
const copy = require("clipboard-copy");
|
||||
@ -43,8 +44,9 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
|
||||
* @param {GameRoot} root
|
||||
* @param {object} payload
|
||||
* @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle
|
||||
* @param {Array<number> | undefined} payload.nextPuzzles
|
||||
*/
|
||||
constructor(root, { puzzle }) {
|
||||
constructor(root, { puzzle, nextPuzzles }) {
|
||||
super(root);
|
||||
|
||||
/** @type {Array<typeof MetaBuilding>} */
|
||||
@ -95,6 +97,15 @@ export class PuzzlePlayGameMode extends PuzzleGameMode {
|
||||
root.signals.postLoadHook.add(this.loadPuzzle, this);
|
||||
|
||||
this.puzzle = puzzle;
|
||||
|
||||
/**
|
||||
* @type {Array<number>}
|
||||
*/
|
||||
this.nextPuzzles = nextPuzzles || [];
|
||||
|
||||
if (this.nextPuzzles.length > 0) {
|
||||
this.additionalHudParts.puzzleNext = HUDPuzzleNextPuzzle;
|
||||
}
|
||||
}
|
||||
|
||||
loadPuzzle() {
|
||||
|
@ -143,6 +143,20 @@ export class ClientAPI {
|
||||
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
|
||||
* @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>}
|
||||
*/
|
||||
apiDownloadPuzzleByKey(shortKey) {
|
||||
|
@ -64,6 +64,24 @@ export class Savegame extends ReadWriteProxy {
|
||||
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}
|
||||
*/
|
||||
|
@ -13,17 +13,30 @@ const navigation = {
|
||||
categories: ["official", "top-rated", "trending", "trending-weekly", "new"],
|
||||
difficulties: ["easy", "medium", "hard"],
|
||||
account: ["mine", "completed"],
|
||||
search: ["search"],
|
||||
};
|
||||
|
||||
const logger = createLogger("puzzle-menu");
|
||||
|
||||
let lastCategory = "official";
|
||||
|
||||
let lastSearchOptions = {
|
||||
searchTerm: "",
|
||||
difficulty: "any",
|
||||
duration: "any",
|
||||
includeCompleted: false,
|
||||
};
|
||||
|
||||
export class PuzzleMenuState extends TextualGameState {
|
||||
constructor() {
|
||||
super("PuzzleMenuState");
|
||||
this.loading = false;
|
||||
this.activeCategory = "";
|
||||
|
||||
/**
|
||||
* @type {Array<import("../savegame/savegame_typedefs").PuzzleMetadata>}
|
||||
*/
|
||||
this.puzzles = [];
|
||||
}
|
||||
|
||||
getThemeMusic() {
|
||||
@ -99,13 +112,23 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
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");
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
|
||||
if (category === "search") {
|
||||
this.loading = false;
|
||||
|
||||
this.startSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
const loadingElement = document.createElement("div");
|
||||
loadingElement.classList.add("loader");
|
||||
loadingElement.innerText = T.global.loading + "...";
|
||||
@ -160,6 +183,7 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
}
|
||||
|
||||
const children = navigation[rootCategory];
|
||||
if (children.length > 1) {
|
||||
for (const category of children) {
|
||||
const button = document.createElement("button");
|
||||
button.setAttribute("data-category", category);
|
||||
@ -168,15 +192,139 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
this.trackClicks(button, () => this.selectCategory(category));
|
||||
subContainer.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
if (rootCategory === "search") {
|
||||
this.renderSearchForm(subContainer);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
renderPuzzles(puzzles) {
|
||||
this.puzzles = puzzles;
|
||||
|
||||
const container = this.htmlElement.querySelector("#mainContainer");
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
@ -223,15 +371,15 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
difficulty.innerText = completionPercentage + "%";
|
||||
stats.appendChild(difficulty);
|
||||
|
||||
if (completionPercentage < 40) {
|
||||
difficulty.classList.add("stage--hard");
|
||||
difficulty.innerText = T.puzzleMenu.difficulties.hard;
|
||||
} else if (completionPercentage < 80) {
|
||||
difficulty.classList.add("stage--medium");
|
||||
difficulty.innerText = T.puzzleMenu.difficulties.medium;
|
||||
} else {
|
||||
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.innerText = T.puzzleMenu.difficulties.hard;
|
||||
} else {
|
||||
difficulty.classList.add("stage--medium");
|
||||
difficulty.innerText = T.puzzleMenu.difficulties.medium;
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,7 +423,7 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
|
||||
container.appendChild(elem);
|
||||
|
||||
this.trackClicks(elem, () => this.playPuzzle(puzzle));
|
||||
this.trackClicks(elem, () => this.playPuzzle(puzzle.id));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
this.app.clientApi.apiDownloadPuzzle(puzzle.id).then(
|
||||
this.asyncChannel.watch(this.app.clientApi.apiDownloadPuzzle(puzzleId)).then(
|
||||
puzzleData => {
|
||||
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 => {
|
||||
closeLoading();
|
||||
logger.error("Failed to download puzzle", puzzle.id, ":", err);
|
||||
logger.error("Failed to download puzzle", puzzleId, ":", err);
|
||||
this.dialogs.showWarning(
|
||||
T.dialogs.puzzleDownloadError.title,
|
||||
T.dialogs.puzzleDownloadError.desc + " " + err
|
||||
@ -354,18 +508,24 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
*
|
||||
* @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle
|
||||
*/
|
||||
startLoadedPuzzle(puzzle) {
|
||||
const savegame = this.createEmptySavegame();
|
||||
startLoadedPuzzle(puzzle, nextPuzzles) {
|
||||
const savegame = Savegame.createPuzzleSavegame(this.app);
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzlePlay,
|
||||
gameModeParameters: {
|
||||
puzzle,
|
||||
nextPuzzles,
|
||||
},
|
||||
savegame,
|
||||
});
|
||||
}
|
||||
|
||||
onEnter(payload) {
|
||||
if (payload.continueQueue) {
|
||||
logger.log("Continuing puzzle queue:", payload);
|
||||
this.playPuzzle(payload.continueQueue[0], payload.continueQueue.slice(1));
|
||||
}
|
||||
|
||||
// Find old category
|
||||
let rootCategory = "categories";
|
||||
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());
|
||||
}
|
||||
|
||||
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),
|
||||
validator: val => ShapeDefinition.isValidShortKey(val) || val.startsWith("/"),
|
||||
});
|
||||
|
||||
const dialog = new DialogWithForm({
|
||||
@ -422,9 +569,16 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
this.dialogs.internalShowDialog(dialog);
|
||||
|
||||
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();
|
||||
|
||||
this.app.clientApi.apiDownloadPuzzleByKey(shortKeyInput.getValue()).then(
|
||||
this.app.clientApi.apiDownloadPuzzleByKey(searchTerm).then(
|
||||
puzzle => {
|
||||
closeLoading();
|
||||
this.startLoadedPuzzle(puzzle);
|
||||
@ -451,7 +605,7 @@ export class PuzzleMenuState extends TextualGameState {
|
||||
return;
|
||||
}
|
||||
|
||||
const savegame = this.createEmptySavegame();
|
||||
const savegame = Savegame.createPuzzleSavegame(this.app);
|
||||
this.moveToState("InGameState", {
|
||||
gameModeId: enumGameModeIds.puzzleEdit,
|
||||
savegame,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { THIRDPARTY_URLS } from "../core/config";
|
||||
import { TextualGameState } from "../core/textual_game_state";
|
||||
import { formatSecondsToTimeAgo } from "../core/utils";
|
||||
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 privacy">Privacy Policy</button>
|
||||
|
||||
`
|
||||
}
|
||||
<div class="versionbar">
|
||||
@ -109,6 +112,9 @@ export class SettingsState extends TextualGameState {
|
||||
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
|
||||
preventDefault: false,
|
||||
});
|
||||
this.trackClicks(this.htmlElement.querySelector(".privacy"), this.onPrivacyClicked, {
|
||||
preventDefault: false,
|
||||
});
|
||||
}
|
||||
|
||||
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
|
||||
@ -180,6 +186,10 @@ export class SettingsState extends TextualGameState {
|
||||
this.moveToStateAddGoBack("AboutState");
|
||||
}
|
||||
|
||||
onPrivacyClicked() {
|
||||
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.privacyPolicy);
|
||||
}
|
||||
|
||||
onKeybindingsClicked() {
|
||||
this.moveToStateAddGoBack("KeybindingsState");
|
||||
}
|
||||
|
@ -155,6 +155,23 @@ puzzleMenu:
|
||||
account: My Puzzles
|
||||
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:
|
||||
easy: Easy
|
||||
medium: Medium
|
||||
@ -657,6 +674,7 @@ ingame:
|
||||
|
||||
continueBtn: Keep Playing
|
||||
menuBtn: Menu
|
||||
nextPuzzle: Next Puzzle
|
||||
|
||||
puzzleMetadata:
|
||||
author: Author
|
||||
|
Loading…
Reference in New Issue
Block a user