mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-06-13 13:04:03 +00:00
- Puzzle DLC browser improvements / new categories
- Fix errors not being thrown sometimes - Minor other stuff
This commit is contained in:
parent
978c07ccc4
commit
416f89bbf4
@ -15,15 +15,15 @@ $hardwareAcc: null;
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
/** Increased click area for this element, helpful on mobile */
|
/** Increased click area for this element, helpful on mobile */
|
||||||
@mixin IncreasedClickArea($size) {
|
@mixin IncreasedClickArea($size) {
|
||||||
&::after {
|
// &::after {
|
||||||
content: "";
|
// content: "";
|
||||||
position: absolute;
|
// position: absolute;
|
||||||
top: #{D(-$size)};
|
// top: #{D(-$size)};
|
||||||
bottom: #{D(-$size)};
|
// bottom: #{D(-$size)};
|
||||||
left: #{D(-$size)};
|
// left: #{D(-$size)};
|
||||||
right: #{D(-$size)};
|
// right: #{D(-$size)};
|
||||||
// background: rgba(255, 0, 0, 0.3);
|
// // background: rgba(255, 0, 0, 0.3);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
button,
|
button,
|
||||||
.increasedClickArea {
|
.increasedClickArea {
|
||||||
|
@ -19,13 +19,15 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
> .categoryChooser {
|
> .categoryChooser {
|
||||||
|
> .categories {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-columns: 1fr;
|
grid-auto-columns: 1fr;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
@include S(grid-gap, 2px);
|
@include S(grid-gap, 2px);
|
||||||
@include S(padding-right, 10px);
|
@include S(padding-right, 10px);
|
||||||
|
@include S(margin-bottom, 5px);
|
||||||
|
|
||||||
> .category {
|
.category {
|
||||||
background: $accentColorBright;
|
background: $accentColorBright;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
color: $accentColorDark;
|
color: $accentColorDark;
|
||||||
@ -57,17 +59,27 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.root {
|
||||||
|
@include S(padding-top, 10px);
|
||||||
|
@include S(padding-bottom, 10px);
|
||||||
|
@include Text;
|
||||||
|
}
|
||||||
|
&.child {
|
||||||
|
@include PlainText;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .puzzles {
|
> .puzzles {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(D(180px), 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(D(240px), 1fr));
|
||||||
@include S(grid-auto-rows, 65px);
|
@include S(grid-auto-rows, 65px);
|
||||||
@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, 360px);
|
@include S(height, 320px);
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -203,14 +215,11 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@include S(margin-right, 3px);
|
@include S(margin-right, 3px);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
&.stage--easy {
|
&.stage--easy {
|
||||||
color: $colorGreenBright;
|
color: $colorGreenBright;
|
||||||
}
|
}
|
||||||
&.stage--normal {
|
|
||||||
color: #000;
|
|
||||||
@include DarkThemeInvert;
|
|
||||||
}
|
|
||||||
&.stage--medium {
|
&.stage--medium {
|
||||||
color: $colorOrangeBright;
|
color: $colorOrangeBright;
|
||||||
}
|
}
|
||||||
|
@ -89,8 +89,13 @@ export class StateManager {
|
|||||||
const dialogParent = document.createElement("div");
|
const dialogParent = document.createElement("div");
|
||||||
dialogParent.classList.add("modalDialogParent");
|
dialogParent.classList.add("modalDialogParent");
|
||||||
document.body.appendChild(dialogParent);
|
document.body.appendChild(dialogParent);
|
||||||
|
try {
|
||||||
this.currentState.internalEnterCallback(payload);
|
this.currentState.internalEnterCallback(payload);
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
this.app.sound.playThemeMusic(this.currentState.getThemeMusic());
|
this.app.sound.playThemeMusic(this.currentState.getThemeMusic());
|
||||||
|
|
||||||
this.currentState.onResized(this.app.screenWidth, this.app.screenHeight);
|
this.currentState.onResized(this.app.screenWidth, this.app.screenHeight);
|
||||||
|
@ -95,7 +95,7 @@ export class MainMenuState extends GameState {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
${
|
${
|
||||||
!G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc
|
(!G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc) || G_IS_DEV
|
||||||
? `
|
? `
|
||||||
<div class="puzzleContainer">
|
<div class="puzzleContainer">
|
||||||
<img class="dlcLogo" src="${cachebust(
|
<img class="dlcLogo" src="${cachebust(
|
||||||
|
@ -10,48 +10,15 @@ import { MUSIC } from "../platform/sound";
|
|||||||
import { Savegame } from "../savegame/savegame";
|
import { Savegame } from "../savegame/savegame";
|
||||||
import { T } from "../translations";
|
import { T } from "../translations";
|
||||||
|
|
||||||
const categories = ["top-rated", "new", "easy", "short", "hard", "completed", "mine"];
|
const navigation = {
|
||||||
|
categories: ["official", "top-rated", "trending", "new"],
|
||||||
/**
|
difficulties: ["easy", "medium", "hard", "short"],
|
||||||
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata}
|
account: ["mine", "completed"],
|
||||||
*/
|
|
||||||
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");
|
const logger = createLogger("puzzle-menu");
|
||||||
|
|
||||||
let lastCategory = categories[0];
|
let lastCategory = "official";
|
||||||
|
|
||||||
export class PuzzleMenuState extends TextualGameState {
|
export class PuzzleMenuState extends TextualGameState {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -79,6 +46,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
|
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
|
||||||
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
|
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@ -91,18 +59,22 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
|
|
||||||
getMainContentHTML() {
|
getMainContentHTML() {
|
||||||
let html = `
|
let html = `
|
||||||
|
|
||||||
|
|
||||||
<div class="categoryChooser">
|
<div class="categoryChooser">
|
||||||
${categories
|
|
||||||
|
<div class="categories rootCategories">
|
||||||
|
${Object.keys(navigation)
|
||||||
.map(
|
.map(
|
||||||
category => `
|
rootCategory =>
|
||||||
<button data-category="${category}" class="styledButton category">${T.puzzleMenu.categories[category]}</button>
|
`<button data-root-category="${rootCategory}" class="styledButton category root">${T.puzzleMenu.categories[rootCategory]}</button>`
|
||||||
`
|
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="categories subCategories">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="puzzles" id="mainContainer"></div>
|
<div class="puzzles" id="mainContainer"></div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -154,6 +126,49 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
.then(() => (this.loading = false));
|
.then(() => (this.loading = false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a root category
|
||||||
|
* @param {string} rootCategory
|
||||||
|
* @param {string=} category
|
||||||
|
*/
|
||||||
|
selectRootCategory(rootCategory, category) {
|
||||||
|
const subCategory = category || navigation[rootCategory][0];
|
||||||
|
console.warn("Select root category", rootCategory, category, "->", subCategory);
|
||||||
|
|
||||||
|
if (this.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.activeCategory === subCategory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeCategory = this.htmlElement.querySelector(".active[data-root-category]");
|
||||||
|
if (activeCategory) {
|
||||||
|
activeCategory.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.htmlElement.querySelector(`[data-root-category="${rootCategory}"]`).classList.add("active");
|
||||||
|
|
||||||
|
// Rerender buttons
|
||||||
|
|
||||||
|
const subContainer = this.htmlElement.querySelector(".subCategories");
|
||||||
|
while (subContainer.firstChild) {
|
||||||
|
subContainer.removeChild(subContainer.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = navigation[rootCategory];
|
||||||
|
for (const category of children) {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.setAttribute("data-category", category);
|
||||||
|
button.classList.add("styledButton", "category", "child");
|
||||||
|
button.innerText = T.puzzleMenu.categories[category];
|
||||||
|
this.trackClicks(button, () => this.selectCategory(category));
|
||||||
|
subContainer.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectCategory(subCategory);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
|
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
|
||||||
@ -167,7 +182,10 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
for (const puzzle of puzzles) {
|
for (const puzzle of puzzles) {
|
||||||
const elem = document.createElement("div");
|
const elem = document.createElement("div");
|
||||||
elem.classList.add("puzzle");
|
elem.classList.add("puzzle");
|
||||||
|
|
||||||
|
if (this.activeCategory !== "mine") {
|
||||||
elem.classList.toggle("completed", puzzle.completed);
|
elem.classList.toggle("completed", puzzle.completed);
|
||||||
|
}
|
||||||
|
|
||||||
if (puzzle.title) {
|
if (puzzle.title) {
|
||||||
const title = document.createElement("div");
|
const title = document.createElement("div");
|
||||||
@ -176,7 +194,7 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
elem.appendChild(title);
|
elem.appendChild(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (puzzle.author) {
|
if (puzzle.author && !["official", "mine"].includes(this.activeCategory)) {
|
||||||
const author = document.createElement("div");
|
const author = document.createElement("div");
|
||||||
author.classList.add("author");
|
author.classList.add("author");
|
||||||
author.innerText = "by " + puzzle.author;
|
author.innerText = "by " + puzzle.author;
|
||||||
@ -187,7 +205,10 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
stats.classList.add("stats");
|
stats.classList.add("stats");
|
||||||
elem.appendChild(stats);
|
elem.appendChild(stats);
|
||||||
|
|
||||||
if (puzzle.downloads > 0) {
|
if (
|
||||||
|
puzzle.downloads > 0 &&
|
||||||
|
!["official", "easy", "medium", "hard"].includes(this.activeCategory)
|
||||||
|
) {
|
||||||
const difficulty = document.createElement("div");
|
const difficulty = document.createElement("div");
|
||||||
difficulty.classList.add("difficulty");
|
difficulty.classList.add("difficulty");
|
||||||
|
|
||||||
@ -198,14 +219,15 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
difficulty.innerText = completionPercentage + "%";
|
difficulty.innerText = completionPercentage + "%";
|
||||||
stats.appendChild(difficulty);
|
stats.appendChild(difficulty);
|
||||||
|
|
||||||
if (completionPercentage < 10) {
|
if (completionPercentage < 40) {
|
||||||
difficulty.classList.add("stage--hard");
|
difficulty.classList.add("stage--hard");
|
||||||
} else if (completionPercentage < 30) {
|
difficulty.innerText = T.puzzleMenu.difficulties.hard;
|
||||||
|
} else if (completionPercentage < 80) {
|
||||||
difficulty.classList.add("stage--medium");
|
difficulty.classList.add("stage--medium");
|
||||||
} else if (completionPercentage < 60) {
|
difficulty.innerText = T.puzzleMenu.difficulties.medium;
|
||||||
difficulty.classList.add("stage--normal");
|
|
||||||
} else {
|
} else {
|
||||||
difficulty.classList.add("stage--easy");
|
difficulty.classList.add("stage--easy");
|
||||||
|
difficulty.innerText = T.puzzleMenu.difficulties.easy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,10 +271,6 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>}
|
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>}
|
||||||
*/
|
*/
|
||||||
getPuzzlesForCategory(category) {
|
getPuzzlesForCategory(category) {
|
||||||
if (category === "levels") {
|
|
||||||
return Promise.resolve(BUILTIN_PUZZLES);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = this.app.clientApi.apiListPuzzles(category);
|
const result = this.app.clientApi.apiListPuzzles(category);
|
||||||
return result.catch(err => {
|
return result.catch(err => {
|
||||||
logger.error("Failed to get", category, ":", err);
|
logger.error("Failed to get", category, ":", err);
|
||||||
@ -300,24 +318,28 @@ export class PuzzleMenuState extends TextualGameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onEnter(payload) {
|
onEnter(payload) {
|
||||||
this.selectCategory(lastCategory);
|
// Find old category
|
||||||
|
let rootCategory = "categories";
|
||||||
|
for (const [id, children] of Object.entries(navigation)) {
|
||||||
|
if (children.includes(lastCategory)) {
|
||||||
|
rootCategory = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectRootCategory(rootCategory, lastCategory);
|
||||||
|
|
||||||
if (payload && payload.error) {
|
if (payload && payload.error) {
|
||||||
this.dialogs.showWarning(payload.error.title, payload.error.desc);
|
this.dialogs.showWarning(payload.error.title, payload.error.desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const category of categories) {
|
for (const rootCategory of Object.keys(navigation)) {
|
||||||
const button = this.htmlElement.querySelector(`[data-category="${category}"]`);
|
const button = this.htmlElement.querySelector(`[data-root-category="${rootCategory}"]`);
|
||||||
this.trackClicks(button, () => this.selectCategory(category));
|
this.trackClicks(button, () => this.selectRootCategory(rootCategory));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
|
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
|
||||||
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
|
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
|
||||||
|
|
||||||
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
|
|
||||||
// this.createNewPuzzle();
|
|
||||||
this.playPuzzle(SAMPLE_PUZZLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmptySavegame() {
|
createEmptySavegame() {
|
||||||
|
@ -140,11 +140,22 @@ puzzleMenu:
|
|||||||
levels: Levels
|
levels: Levels
|
||||||
new: New
|
new: New
|
||||||
top-rated: Top Rated
|
top-rated: Top Rated
|
||||||
mine: My Puzzles
|
mine: Created
|
||||||
short: Short
|
short: Short
|
||||||
easy: Easy
|
easy: Easy
|
||||||
|
medium: Medium
|
||||||
hard: Hard
|
hard: Hard
|
||||||
completed: Completed
|
completed: Completed
|
||||||
|
official: Official
|
||||||
|
trending: Trending today
|
||||||
|
categories: Categories
|
||||||
|
difficulties: By Difficulty
|
||||||
|
account: My Puzzles
|
||||||
|
|
||||||
|
difficulties:
|
||||||
|
easy: Easy
|
||||||
|
medium: Medium
|
||||||
|
hard: Hard
|
||||||
|
|
||||||
validation:
|
validation:
|
||||||
title: Invalid Puzzle
|
title: Invalid Puzzle
|
||||||
|
Loading…
Reference in New Issue
Block a user