mirror of
https://github.com/tobspr/shapez.io.git
synced 2025-12-15 19:21:49 +00:00
Add helper state from yorg3
This commit is contained in:
parent
87139872f7
commit
91ff34c4c5
@ -45,6 +45,8 @@ module.exports = ({ watch = false, standalone = false }) => {
|
|||||||
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
|
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
|
||||||
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
|
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
|
||||||
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
|
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
|
||||||
|
// TODO: Get API endpoint from tobspr
|
||||||
|
G_API_ENDPOINT: JSON.stringify(lzString.compressToEncodedURIComponent("http://localhost:8000")),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new CircularDependencyPlugin({
|
new CircularDependencyPlugin({
|
||||||
|
|||||||
@ -38,6 +38,8 @@ module.exports = ({
|
|||||||
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
|
G_BUILD_COMMIT_HASH: JSON.stringify(utils.getRevision()),
|
||||||
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
|
G_BUILD_VERSION: JSON.stringify(utils.getVersion()),
|
||||||
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
|
G_ALL_UI_IMAGES: JSON.stringify(utils.getAllResourceImages()),
|
||||||
|
// TODO: Get API endpoint from tobspr
|
||||||
|
G_API_ENDPOINT: JSON.stringify(lzString.compressToEncodedURIComponent("https://api.shapez.io/v1")),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
@import "states/about";
|
@import "states/about";
|
||||||
@import "states/mobile_warning";
|
@import "states/mobile_warning";
|
||||||
@import "states/changelog";
|
@import "states/changelog";
|
||||||
|
@import "states/mods";
|
||||||
|
|
||||||
@import "ingame_hud/buildings_toolbar";
|
@import "ingame_hud/buildings_toolbar";
|
||||||
@import "ingame_hud/building_placer";
|
@import "ingame_hud/building_placer";
|
||||||
|
|||||||
109
src/css/states/mods.scss
Normal file
109
src/css/states/mods.scss
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#state_ModsState {
|
||||||
|
|
||||||
|
.mainContent {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devHint {
|
||||||
|
font-size: calc(13px*var(--ui-scale));
|
||||||
|
line-height: calc(17px*var(--ui-scale));
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
color: #aaa;
|
||||||
|
|
||||||
|
.moddingDocsLink {
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #26c6da;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod {
|
||||||
|
padding: calc(14px*var(--ui-scale));
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto 1fr auto;
|
||||||
|
grid-gap: calc(5px*var(--ui-scale));
|
||||||
|
grid-column-gap: calc(15px*var(--ui-scale));
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: calc(19px*var(--ui-scale));
|
||||||
|
line-height: calc(21px*var(--ui-scale));
|
||||||
|
font-weight: 400;
|
||||||
|
grid-column: 1 / 4;
|
||||||
|
grid-row: 1 / 2;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
|
||||||
|
.version {
|
||||||
|
font-size: calc(13px*var(--ui-scale));
|
||||||
|
line-height: calc(17px*var(--ui-scale));
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
grid-column: 1 / 2;
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.website {
|
||||||
|
font-size: calc(13px*var(--ui-scale));
|
||||||
|
line-height: calc(17px*var(--ui-scale));
|
||||||
|
font-weight: 400;
|
||||||
|
grid-column: 2 / 3;
|
||||||
|
grid-row: 2 / 3;
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #26c6da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
grid-column: 1 / 5;
|
||||||
|
grid-row: 3 / 4;
|
||||||
|
border-top: 1px solid;
|
||||||
|
border-top-width: calc(1px*var(--ui-scale));
|
||||||
|
padding-top: calc(5px*var(--ui-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.installedText, button.installMod, button.uninstallMod {
|
||||||
|
grid-column: 4 / 5;
|
||||||
|
grid-row: 1 / 3;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author, .gameChangingHint {
|
||||||
|
font-size: calc(13px*var(--ui-scale));
|
||||||
|
line-height: calc(17px*var(--ui-scale));
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gameChangingHint {
|
||||||
|
grid-column: 1 / 5;
|
||||||
|
grid-row: 4 / 5;
|
||||||
|
color: #ef5072;
|
||||||
|
border-top: 1px solid;
|
||||||
|
border-top-width: calc(1px*var(--ui-scale));
|
||||||
|
padding-top: calc(5px*var(--ui-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installCount, .installedText {
|
||||||
|
font-size: calc(13px*var(--ui-scale));
|
||||||
|
line-height: calc(17px*var(--ui-scale));
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installedText {
|
||||||
|
text-transform: uppercase;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,6 +38,7 @@ import { PreloadState } from "./states/preload";
|
|||||||
import { SettingsState } from "./states/settings";
|
import { SettingsState } from "./states/settings";
|
||||||
import { ModsState } from "./states/mods";
|
import { ModsState } from "./states/mods";
|
||||||
import { ModManager } from "./core/mod_manager";
|
import { ModManager } from "./core/mod_manager";
|
||||||
|
import { BackendAPI } from "./core/backend";
|
||||||
|
|
||||||
const logger = createLogger("application");
|
const logger = createLogger("application");
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ export class Application {
|
|||||||
this.savegameMgr = new SavegameManager(this);
|
this.savegameMgr = new SavegameManager(this);
|
||||||
this.inputMgr = new InputDistributor(this);
|
this.inputMgr = new InputDistributor(this);
|
||||||
this.backgroundResourceLoader = new BackgroundResourcesLoader(this);
|
this.backgroundResourceLoader = new BackgroundResourcesLoader(this);
|
||||||
|
this.api = new BackendAPI(this);
|
||||||
this.modManager = new ModManager(this);
|
this.modManager = new ModManager(this);
|
||||||
|
|
||||||
// Platform dependent stuff
|
// Platform dependent stuff
|
||||||
|
|||||||
193
src/js/core/backend.js
Normal file
193
src/js/core/backend.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/* typehints:start */
|
||||||
|
import { Application } from "../application";
|
||||||
|
/* typehints:end */
|
||||||
|
|
||||||
|
import { JSON_stringify } from "./builtins";
|
||||||
|
import { globalError, globalLog, globalWarn } from "./logging";
|
||||||
|
import { decodeHashedString } from "./sensitive_utils.encrypt";
|
||||||
|
import { globalConfig } from "./config";
|
||||||
|
import { BACKEND_ERRORS } from "./backend_errors";
|
||||||
|
import { RequestChannel } from "./request_channel";
|
||||||
|
|
||||||
|
export class BackendAPI {
|
||||||
|
constructor(app) {
|
||||||
|
/** @type {Application} */
|
||||||
|
this.app = app;
|
||||||
|
this.url = decodeHashedString(G_API_ENDPOINT);
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
if (G_IS_DEV && window.location.hostname !== "localhost") {
|
||||||
|
this.url = "http://172.0.0.1:8000";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.runningRequest = null;
|
||||||
|
this.requestChannel = new RequestChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the list of mods
|
||||||
|
*
|
||||||
|
* Route: /mods/gallery
|
||||||
|
* @returns {Promise<Array<any>>}
|
||||||
|
*/
|
||||||
|
fetchModGallery() {
|
||||||
|
return this.performRequest("GET", "/mods/gallery").then(res => {
|
||||||
|
if (!res.mods) {
|
||||||
|
throw BACKEND_ERRORS.badResponse;
|
||||||
|
}
|
||||||
|
return res.mods;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the list of mods
|
||||||
|
*
|
||||||
|
* Route: /mods/track-download/:id
|
||||||
|
* @returns {Promise<Array<any>>}
|
||||||
|
*/
|
||||||
|
trackModDownload(id) {
|
||||||
|
return this.performRequest("POST", "/mods/track-download/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an endpoint like '/user/profile' to a full url
|
||||||
|
* @param {string} endpoint
|
||||||
|
*/
|
||||||
|
getEndpointUrl(endpoint) {
|
||||||
|
assertAlways(endpoint.startsWith("/"), "Endpoint must start with '/'");
|
||||||
|
return this.url + endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal fetch helper
|
||||||
|
* @param {string} method
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {Object.<string, any>} parameters
|
||||||
|
*/
|
||||||
|
internalFetch(method, endpoint, parameters) {
|
||||||
|
if (G_IS_DEV && globalConfig.debug.alwaysOffline) {
|
||||||
|
return Promise.reject("offline");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
fetch(this.getEndpointUrl(endpoint), {
|
||||||
|
method: method,
|
||||||
|
mode: "cors",
|
||||||
|
cache: "no-cache",
|
||||||
|
referrer: "no-referrer",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"X-App-Version": G_BUILD_VERSION,
|
||||||
|
},
|
||||||
|
credentials: "omit",
|
||||||
|
body: method === "POST" ? JSON_stringify(parameters) : undefined,
|
||||||
|
})
|
||||||
|
// Catch network errors / bad status codes
|
||||||
|
.catch(err => {
|
||||||
|
globalLog(this, "Network error:", err);
|
||||||
|
throw BACKEND_ERRORS.networkError;
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if the response was good
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return response.json().then(
|
||||||
|
data => {
|
||||||
|
if (data.error) {
|
||||||
|
globalWarn(this, "API request error:", data);
|
||||||
|
throw data.error;
|
||||||
|
}
|
||||||
|
globalWarn(this, "API response has no error payload:", data);
|
||||||
|
throw BACKEND_ERRORS.unknownError;
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
globalError(this, "API bad json:", err);
|
||||||
|
throw BACKEND_ERRORS.badResponse;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.resolve(response);
|
||||||
|
})
|
||||||
|
|
||||||
|
// JSON parsing
|
||||||
|
.then(response => {
|
||||||
|
try {
|
||||||
|
return response.json();
|
||||||
|
} catch (err) {
|
||||||
|
globalError(this, "API response json parsing error:", err);
|
||||||
|
throw BACKEND_ERRORS.badResponse;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if error flag is set
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) {
|
||||||
|
globalWarn(this, "API sent error:", data.error);
|
||||||
|
const str = new String(data.error);
|
||||||
|
// @ts-ignore
|
||||||
|
str.originalError = data;
|
||||||
|
throw str;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a request
|
||||||
|
* @param {string} method
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {Object.<string, any>=} parameters
|
||||||
|
*/
|
||||||
|
performRequest(method, endpoint, parameters = null) {
|
||||||
|
if (this.runningRequest) {
|
||||||
|
globalWarn(
|
||||||
|
this,
|
||||||
|
"Request to",
|
||||||
|
endpoint,
|
||||||
|
"queried while request to",
|
||||||
|
this.runningRequest,
|
||||||
|
"did not finish yet!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assertAlways(method === "POST" || method === "GET", "Invalid mode");
|
||||||
|
|
||||||
|
globalLog(this, "🔗 " + method + " " + endpoint);
|
||||||
|
this.runningRequest = endpoint;
|
||||||
|
|
||||||
|
return this.requestChannel.watch(
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
let timedOut = false;
|
||||||
|
let timeout = setTimeout(() => {
|
||||||
|
globalWarn("api", "Request to", endpoint, "timed out");
|
||||||
|
timedOut = true;
|
||||||
|
this.runningRequest = null;
|
||||||
|
reject(BACKEND_ERRORS.networkError);
|
||||||
|
}, 30000);
|
||||||
|
const request = this.internalFetch(method, endpoint, parameters);
|
||||||
|
|
||||||
|
request.then(
|
||||||
|
res => {
|
||||||
|
if (timedOut) {
|
||||||
|
globalWarn("api", "Request finished but already timed out");
|
||||||
|
} else {
|
||||||
|
this.runningRequest = null;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (timedOut) {
|
||||||
|
globalWarn("api", "Request finished with error but already timed out");
|
||||||
|
} else {
|
||||||
|
this.runningRequest = null;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/js/core/backend_errors.js
Normal file
37
src/js/core/backend_errors.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { T } from "../translations";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates error codes like "invalid-auth-key" into their translations (E.g. "Invalid Auth Key").
|
||||||
|
* If the code is not known or an arbitrary string, returns the code
|
||||||
|
* @param {string} err The error code
|
||||||
|
* @returns {string} Translated code
|
||||||
|
*/
|
||||||
|
export function tryTranslateBackendError(err) {
|
||||||
|
// TODO: Add errors to base-en.yaml
|
||||||
|
// return "<div class='backendError'>" + (T.backend.errors[err] || err) + "</div>";
|
||||||
|
return "<div class='backendError'> ☹" + err + "</div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BACKEND_ERRORS = {
|
||||||
|
// Frontend errors
|
||||||
|
networkError: "network-error",
|
||||||
|
offline: "offline",
|
||||||
|
badResponse: "bad-response",
|
||||||
|
rateLimited: "rate-limited",
|
||||||
|
|
||||||
|
// Backend errors
|
||||||
|
unsupportedVersion: "unsupported-version",
|
||||||
|
unknownError: "unknown-error",
|
||||||
|
unauthenticated: "unauthenticated",
|
||||||
|
invalidRequestSchema: "invalid-request-schema",
|
||||||
|
internalServerError: "internal-server-error",
|
||||||
|
databaseError: "database-error",
|
||||||
|
notFound: "not-found",
|
||||||
|
serverOverloaded: "server-overloaded",
|
||||||
|
|
||||||
|
// Binary checksum stuff
|
||||||
|
failedToDecompress: "failed-to-decompress",
|
||||||
|
checksumMissing: "checksum-missing",
|
||||||
|
checksumMismatch: "checksum-mismatch",
|
||||||
|
failedToParse: "failed-to-parse",
|
||||||
|
};
|
||||||
@ -109,6 +109,7 @@ export const globalConfig = {
|
|||||||
// instantBelts: true,
|
// instantBelts: true,
|
||||||
// instantProcessors: true,
|
// instantProcessors: true,
|
||||||
// instantMiners: true,
|
// instantMiners: true,
|
||||||
|
// alwaysOffline: true,
|
||||||
|
|
||||||
// renderForTrailer: true,
|
// renderForTrailer: true,
|
||||||
/* dev:end */
|
/* dev:end */
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ReadWriteProxy } from "./read_write_proxy";
|
import { ReadWriteProxy } from "./read_write_proxy";
|
||||||
import { ExplainedResult } from "./explained_result";
|
import { ExplainedResult } from "./explained_result";
|
||||||
import { globalError, globalLog, globalWarn } from "./logging";
|
import { globalError, globalWarn } from "./logging";
|
||||||
import { ModApi } from "./mod_api";
|
import { ModApi } from "./mod_api";
|
||||||
import { queryParamOptions } from "./query_parameters";
|
import { queryParamOptions } from "./query_parameters";
|
||||||
|
|
||||||
@ -272,10 +272,13 @@ export class ModManager extends ReadWriteProxy {
|
|||||||
|
|
||||||
this.currentData.mods.push(mod);
|
this.currentData.mods.push(mod);
|
||||||
|
|
||||||
// TODO: Check if this use of trackUiClick is okay.
|
|
||||||
// Track download in the background
|
// Track download in the background
|
||||||
return this.writeAsync()
|
return this.writeAsync()
|
||||||
.then(() => this.app.analytics.trackUiClick("mod/" + id))
|
.then(() =>
|
||||||
|
this.app.api
|
||||||
|
.trackModDownload(id)
|
||||||
|
.catch(err => globalWarn(this, "Failed to track mod download:", err))
|
||||||
|
)
|
||||||
.then(() => (this.needsRestart = true))
|
.then(() => (this.needsRestart = true))
|
||||||
.then(() => null);
|
.then(() => null);
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/js/globals.d.ts
vendored
1
src/js/globals.d.ts
vendored
@ -7,6 +7,7 @@ declare function assertAlways(condition: boolean | object | string, ...errorMess
|
|||||||
|
|
||||||
declare const abstract: void;
|
declare const abstract: void;
|
||||||
|
|
||||||
|
declare const G_API_ENDPOINT: string;
|
||||||
declare const G_APP_ENVIRONMENT: string;
|
declare const G_APP_ENVIRONMENT: string;
|
||||||
declare const G_HAVE_ASSERT: boolean;
|
declare const G_HAVE_ASSERT: boolean;
|
||||||
declare const G_BUILD_TIME: number;
|
declare const G_BUILD_TIME: number;
|
||||||
|
|||||||
@ -37,10 +37,10 @@ export class ModsState extends TextualGameState {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
<strong class="category_label">${T.mods.installedMods}</strong>
|
<strong class="categoryLabel">${T.mods.installedMods}</strong>
|
||||||
<div class="installedMods"></div>
|
<div class="installedMods"></div>
|
||||||
|
|
||||||
<strong class="category_label">${T.mods.modsBrowser}</strong>
|
<strong class="categoryLabel">${T.mods.modsBrowser}</strong>
|
||||||
<div class="modGallery"></div>
|
<div class="modGallery"></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { GameState } from "../core/game_state";
|
import { GameState } from "../core/game_state";
|
||||||
import { createLogger } from "../core/logging";
|
import { createLogger, globalError } from "../core/logging";
|
||||||
import { findNiceValue, waitNextFrame } from "../core/utils";
|
import { findNiceValue, waitNextFrame } from "../core/utils";
|
||||||
import { cachebust } from "../core/cachebust";
|
import { cachebust } from "../core/cachebust";
|
||||||
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
|
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
|
||||||
@ -173,6 +173,21 @@ export class PreloadState extends GameState {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.then(() => this.setStatus("Initializing mods"))
|
||||||
|
.then(() => {
|
||||||
|
return this.app.modManager.initialize().catch(err => {
|
||||||
|
globalError(this, "Failed to initialize mods:", err);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { ok } = this.dialogs.showWarning(
|
||||||
|
T.global.error,
|
||||||
|
T.dialogs.modLoadFailDialog.content + "<br><br><b>" + err + "</b>",
|
||||||
|
["ok:good"]
|
||||||
|
);
|
||||||
|
ok.add(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
.then(() => this.setStatus("Downloading resources"))
|
.then(() => this.setStatus("Downloading resources"))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this.app.backgroundResourceLoader.getPromiseForBareGame();
|
return this.app.backgroundResourceLoader.getPromiseForBareGame();
|
||||||
|
|||||||
121
src/js/states/sync_savegame_mods.js
Normal file
121
src/js/states/sync_savegame_mods.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { TextualGameState } from "../core/textual_game_state";
|
||||||
|
import { T } from "../translations";
|
||||||
|
import { Savegame } from "../savegame/savegame";
|
||||||
|
|
||||||
|
export class SyncSavegameModsState extends TextualGameState {
|
||||||
|
constructor() {
|
||||||
|
super("SyncSavegameModsState");
|
||||||
|
}
|
||||||
|
|
||||||
|
getMainContentHTML() {
|
||||||
|
return `
|
||||||
|
|
||||||
|
|
||||||
|
<div class="comparisonDiv">
|
||||||
|
<span class="desc">${T.syncSavegameMods.desc}</span>
|
||||||
|
|
||||||
|
<div class="modsComparison">
|
||||||
|
|
||||||
|
<div class="savegameMods modsGroup">
|
||||||
|
<strong>${T.syncSavegameMods.savegameMods}</strong>
|
||||||
|
<div class="modsList">
|
||||||
|
<span>Mymod@1.0.0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="installedMods modsGroup">
|
||||||
|
<strong>${T.syncSavegameMods.yourMods}</strong>
|
||||||
|
<div class="modsList">
|
||||||
|
<span>Mymod@1.0.1</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttonDiv">
|
||||||
|
<button class="styledButton cancel">${T.dialogs.buttons.cancel}</button>
|
||||||
|
<button class="styledButton syncMods">${T.syncSavegameMods.syncMods}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShowDiamonds() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnter(payload) {
|
||||||
|
/** @type {Savegame} */
|
||||||
|
const savegame = payload.savegame;
|
||||||
|
const nextStateId = payload.nextStateId;
|
||||||
|
const nextStatePayload = payload.nextStatePayload;
|
||||||
|
|
||||||
|
this.targetMods = savegame.getInstalledMods();
|
||||||
|
|
||||||
|
if (this.app.modManager.needsRestart) {
|
||||||
|
this.containerElement.classList.add("loading");
|
||||||
|
const { cancel, restart } = this.dialogs.showWarning(
|
||||||
|
T.syncSavegameMods.dialog.needs_restart.title,
|
||||||
|
T.syncSavegameMods.dialog.needs_restart.desc,
|
||||||
|
this.app.platformWrapper.getSupportsRestart()
|
||||||
|
? ["cancel:bad", "restart:misc"]
|
||||||
|
: ["cancel:bad"]
|
||||||
|
);
|
||||||
|
cancel.add(() => this.moveToState("MainMenuState"));
|
||||||
|
if (restart) {
|
||||||
|
restart.add(() => this.app.platformWrapper.performRestart());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderMods = (mods, targetId) => {
|
||||||
|
const targetElement = this.containerElement.querySelector(targetId);
|
||||||
|
|
||||||
|
const filteredMods = mods.filter(mod => mod.is_game_changing);
|
||||||
|
|
||||||
|
if (filteredMods.length > 0) {
|
||||||
|
targetElement.innerHTML = filteredMods
|
||||||
|
.map(m => "<strong>" + m.name + "@" + m.version + "</strong>")
|
||||||
|
.join("");
|
||||||
|
} else {
|
||||||
|
targetElement.innerHTML = `<span class='noMods'>${T.syncSavegameMods.no_game_changing_mods}</span>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderMods(this.app.modManager.getMods(), ".installedMods .modsList");
|
||||||
|
renderMods(savegame.getInstalledMods(), ".savegameMods .modsList");
|
||||||
|
|
||||||
|
this.trackClicks(this.htmlElement.querySelector("button.cancel"), this.onBackButton);
|
||||||
|
this.trackClicks(this.htmlElement.querySelector("button.syncMods"), this.doSyncMods);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSyncMods() {
|
||||||
|
const closeLoading = this.dialogs.showLoadingDialog();
|
||||||
|
|
||||||
|
this.app.modManager.syncModsFromSavegame(this.targetMods).then(
|
||||||
|
() => {
|
||||||
|
closeLoading();
|
||||||
|
const { cancel, restart } = this.dialogs.showWarning(
|
||||||
|
T.syncSavegameMods.dialog_synced.title,
|
||||||
|
T.syncSavegameMods.dialog_synced.desc,
|
||||||
|
this.app.platformWrapper.getSupportsRestart()
|
||||||
|
? ["cancel:bad", "restart:misc"]
|
||||||
|
: ["cancel:bad"]
|
||||||
|
);
|
||||||
|
cancel.add(this.onBackButton, this);
|
||||||
|
if (restart) {
|
||||||
|
restart.add(() => this.app.platformWrapper.performRestart());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
closeLoading();
|
||||||
|
const { ok } = this.dialogs.showWarning(T.global.error, err);
|
||||||
|
ok.add(this.onBackButton, this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackButton() {
|
||||||
|
this.moveToState("SingleplayerOverviewState");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -97,6 +97,12 @@ mods:
|
|||||||
title: Mod Removed
|
title: Mod Removed
|
||||||
desc: The mod has successfully beeen removed and will get unloaded after a restart.
|
desc: The mod has successfully beeen removed and will get unloaded after a restart.
|
||||||
|
|
||||||
|
syncSavegameMods:
|
||||||
|
desc: You need
|
||||||
|
|
||||||
|
savegameMods: Savegame Mods
|
||||||
|
yourMods: Loaded Mods
|
||||||
|
syncMods: Sync Mods
|
||||||
|
|
||||||
demoBanners:
|
demoBanners:
|
||||||
# This is the "advertisement" shown in the main menu and other various places
|
# This is the "advertisement" shown in the main menu and other various places
|
||||||
@ -243,6 +249,10 @@ dialogs:
|
|||||||
title: New Marker
|
title: New Marker
|
||||||
desc: Give it a meaningful name
|
desc: Give it a meaningful name
|
||||||
|
|
||||||
|
modLoadFailDialog:
|
||||||
|
content: >-
|
||||||
|
Failed to initialize mods:
|
||||||
|
|
||||||
ingame:
|
ingame:
|
||||||
# This is shown in the top left corner and displays useful keybindings in
|
# This is shown in the top left corner and displays useful keybindings in
|
||||||
# every situation
|
# every situation
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user