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:
203
src/js/platform/api.js
Normal file
203
src/js/platform/api.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/* typehints:start */
|
||||
import { Application } from "../application";
|
||||
/* typehints:end */
|
||||
import { createLogger } from "../core/logging";
|
||||
import { compressX64 } from "../core/lzstring";
|
||||
import { T } from "../translations";
|
||||
|
||||
const logger = createLogger("puzzle-api");
|
||||
const rusha = require("rusha");
|
||||
|
||||
export class ClientAPI {
|
||||
/**
|
||||
*
|
||||
* @param {Application} app
|
||||
*/
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
/**
|
||||
* The current users session token
|
||||
* @type {string|null}
|
||||
*/
|
||||
this.token = null;
|
||||
|
||||
this.syncToken = window.localStorage.getItem("tmp.syncToken");
|
||||
if (!this.syncToken) {
|
||||
this.syncToken = rusha
|
||||
.createHash()
|
||||
.update(new Date().getTime() + "=" + Math.random())
|
||||
.digest("hex");
|
||||
window.localStorage.setItem("tmp.syncToken", this.syncToken);
|
||||
}
|
||||
}
|
||||
|
||||
getEndpoint() {
|
||||
if (G_IS_DEV) {
|
||||
return "http://localhost:15001";
|
||||
}
|
||||
if (window.location.host === "beta.shapez.io") {
|
||||
return "https://api-staging.shapez.io";
|
||||
}
|
||||
return "https://api.shapez.io";
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
return Boolean(this.token);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} endpoint
|
||||
* @param {object} options
|
||||
* @param {"GET"|"POST"=} options.method
|
||||
* @param {any=} options.body
|
||||
*/
|
||||
_request(endpoint, options) {
|
||||
const headers = {
|
||||
"x-api-key": "d5c54aaa491f200709afff082c153ef2",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
if (this.token) {
|
||||
headers["x-token"] = this.token;
|
||||
}
|
||||
|
||||
return Promise.race([
|
||||
fetch(this.getEndpoint() + endpoint, {
|
||||
cache: "no-cache",
|
||||
mode: "cors",
|
||||
headers,
|
||||
method: options.method || "GET",
|
||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw "bad-status: " + res.status + " / " + res.statusText;
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.then(res => res.json()),
|
||||
new Promise((resolve, reject) => setTimeout(() => reject("timeout"), 15000)),
|
||||
])
|
||||
.then(data => {
|
||||
if (data && data.error) {
|
||||
logger.warn("Got error from api:", data);
|
||||
throw T.backendErrors[data.error] || data.error;
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.catch(err => {
|
||||
logger.warn("Failure:", endpoint, ":", err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
tryLogin() {
|
||||
return this.apiTryLogin()
|
||||
.then(({ token }) => {
|
||||
this.token = token;
|
||||
return true;
|
||||
})
|
||||
.catch(err => {
|
||||
logger.warn("Failed to login:", err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<{token: string}>}
|
||||
*/
|
||||
apiTryLogin() {
|
||||
return this._request("/v1/public/login", {
|
||||
method: "POST",
|
||||
body: {
|
||||
token: this.syncToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {"new"|"top-rated"|"mine"} category
|
||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>}
|
||||
*/
|
||||
apiListPuzzles(category) {
|
||||
if (!this.isLoggedIn()) {
|
||||
return Promise.reject("not-logged-in");
|
||||
}
|
||||
return this._request("/v1/puzzles/list/" + category, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} puzzleId
|
||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
||||
*/
|
||||
apiDownloadPuzzle(puzzleId) {
|
||||
if (!this.isLoggedIn()) {
|
||||
return Promise.reject("not-logged-in");
|
||||
}
|
||||
return this._request("/v1/puzzles/download/" + puzzleId, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} shortKey
|
||||
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleFullData>}
|
||||
*/
|
||||
apiDownloadPuzzleByKey(shortKey) {
|
||||
if (!this.isLoggedIn()) {
|
||||
return Promise.reject("not-logged-in");
|
||||
}
|
||||
return this._request("/v1/puzzles/download/" + shortKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} puzzleId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
apiReportPuzzle(puzzleId, reason) {
|
||||
if (!this.isLoggedIn()) {
|
||||
return Promise.reject("not-logged-in");
|
||||
}
|
||||
return this._request("/v1/puzzles/report/" + puzzleId, {
|
||||
method: "POST",
|
||||
body: { reason },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} puzzleId
|
||||
* @param {object} payload
|
||||
* @param {number} payload.time
|
||||
* @param {boolean} payload.liked
|
||||
* @returns {Promise<{ success: true }>}
|
||||
*/
|
||||
apiCompletePuzzle(puzzleId, payload) {
|
||||
if (!this.isLoggedIn()) {
|
||||
return Promise.reject("not-logged-in");
|
||||
}
|
||||
return this._request("/v1/puzzles/complete/" + puzzleId, {
|
||||
method: "POST",
|
||||
body: payload,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} payload
|
||||
* @param {string} payload.title
|
||||
* @param {string} payload.shortKey
|
||||
* @param {import("../savegame/savegame_typedefs").PuzzleGameData} payload.data
|
||||
* @returns {Promise<{ success: true }>}
|
||||
*/
|
||||
apiSubmitPuzzle(payload) {
|
||||
if (!this.isLoggedIn()) {
|
||||
return Promise.reject("not-logged-in");
|
||||
}
|
||||
return this._request("/v1/puzzles/submit", {
|
||||
method: "POST",
|
||||
body: {
|
||||
...payload,
|
||||
data: compressX64(JSON.stringify(payload.data)),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { createLogger } from "../../core/logging";
|
||||
import { queryParamOptions } from "../../core/query_parameters";
|
||||
import { BeltComponent } from "../../game/components/belt";
|
||||
import { StaticMapEntityComponent } from "../../game/components/static_map_entity";
|
||||
import { RegularGameMode } from "../../game/modes/regular";
|
||||
import { GameRoot } from "../../game/root";
|
||||
import { InGameState } from "../../states/ingame";
|
||||
import { GameAnalyticsInterface } from "../game_analytics";
|
||||
@@ -163,6 +164,10 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.gameMode instanceof RegularGameMode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("Sending event", category, value);
|
||||
|
||||
this.sendToApi("/v1/game-event", {
|
||||
|
||||
@@ -35,6 +35,10 @@ export const MUSIC = {
|
||||
menu: "menu",
|
||||
};
|
||||
|
||||
if (G_IS_STANDALONE || G_IS_DEV) {
|
||||
MUSIC.puzzle = "puzzle-full";
|
||||
}
|
||||
|
||||
export class SoundInstanceInterface {
|
||||
constructor(key, url) {
|
||||
this.key = key;
|
||||
|
||||
Reference in New Issue
Block a user