diff --git a/gulp/bundle-loader.js b/gulp/bundle-loader.js
deleted file mode 100644
index 2ae10169..00000000
--- a/gulp/bundle-loader.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * ES6 Bundle Loader
- *
- * Attempts to load the game code, and if that fails tries with the transpiled
- * version. Also handles errors during load.
- */
-
-(function () {
- var loadTimeout = null;
- var callbackDone = false;
-
- // Catch load errors
-
- function errorHandler(event, source, lineno, colno, error) {
- console.error("đ Init Error:", event, source, lineno, colno, error);
- var element = document.createElement("div");
- element.style.position = "fixed";
- element.style.top = "0";
- element.style.right = "0";
- element.style.bottom = "0";
- element.style.left = "0";
- element.style.zIndex = "29999";
- element.style.backgroundColor = "#222429";
- element.style.display = "flex";
- element.style.justifyContent = "center";
- element.style.alignItems = "center";
-
- var inner = document.createElement("div");
- inner.style.color = "#fff";
- inner.style.fontFamily = "GameFont, sans-serif";
- inner.style.fontSize = "15px";
- inner.style.padding = "30px";
- inner.style.textAlign = "center";
- element.appendChild(inner);
-
- var heading = document.createElement("h3");
- heading.style.color = "#ef5072";
- heading.innerText = "Error";
- heading.style.marginBottom = "40px";
- heading.style.fontSize = "45px";
- inner.appendChild(heading);
-
- var content = document.createElement("p");
- content.style.color = "#eee";
- content.innerText = error || (event && event.message) || event || "Unknown Error";
- inner.appendChild(content);
-
- if (source) {
- var sourceElement = document.createElement("p");
- sourceElement.style.color = "#777";
- sourceElement.innerText = sourceElement + ":" + lineno + ":" + colno;
- inner.appendChild(sourceElement);
- }
-
- document.documentElement.appendChild(element);
- }
-
- if (window.location.host.indexOf("localhost") < 0) {
- window.addEventListener("error", errorHandler);
- window.addEventListener("unhandledrejection", errorHandler);
- }
-
- function makeJsTag(src, integrity) {
- var script = document.createElement("script");
- script.src = src;
- script.type = "text/javascript";
- script.charset = "utf-8";
- script.defer = true;
- if (integrity) {
- script.setAttribute("integrity", integrity);
- }
- return script;
- }
-
- // function loadFallbackJs(error) {
- // console.warn("đ ES6 Script not supported, loading transpiled code.");
- // console.warn("đ Error was:", error);
- // var scriptTransp = makeJsTag(bundleSrcTranspiled, bundleIntegrityTranspiled);
- // scriptTransp.addEventListener("error", scriptFail);
- // scriptTransp.addEventListener("load", onJsLoaded);
- // document.head.appendChild(scriptTransp);
- // }
-
- function scriptFail(error) {
- console.error("đ Failed to load bundle!");
- console.error("đ Error was:", error);
- throw new Error("Core load failed.");
- }
-
- function expectJsParsed() {
- if (!callbackDone) {
- console.error("đ Got no core callback");
- throw new Error("Core thread failed to respond within time.");
- }
- }
-
- function onJsLoaded() {
- console.log("đ Core loaded at", Math.floor(performance.now()), "ms");
- loadTimeout = setTimeout(expectJsParsed, 15000);
- window.removeEventListener("error", errorHandler);
- window.removeEventListener("unhandledrejection", errorHandler);
- }
-
- window.coreThreadLoadedCb = function () {
- console.log("đ Core responded at", Math.floor(performance.now()), "ms");
- clearTimeout(loadTimeout);
- loadTimeout = null;
- callbackDone = true;
- };
-
- var scriptEs6 = makeJsTag(bundleSrc, bundleIntegrity);
- // scriptEs6.addEventListener("error", loadFallbackJs);
- scriptEs6.addEventListener("load", onJsLoaded);
- document.head.appendChild(scriptEs6);
-})();
diff --git a/gulp/gulpfile.js b/gulp/gulpfile.js
index 8db31a48..0029a992 100644
--- a/gulp/gulpfile.js
+++ b/gulp/gulpfile.js
@@ -165,7 +165,7 @@ function serveHTML({ version = "web-dev" }) {
// Watch .html files, those trigger a html rebuild
gulp.watch("../src/**/*.html", gulp.series("html." + version + ".dev"));
- gulp.watch("./preloader.css", gulp.series("html." + version + ".dev"));
+ gulp.watch("./preloader/*.*", gulp.series("html." + version + ".dev"));
// Watch translations
gulp.watch("../translations/**/*.yaml", gulp.series("translations.convertToJson"));
diff --git a/gulp/html.js b/gulp/html.js
index ced7f515..df80d2d4 100644
--- a/gulp/html.js
+++ b/gulp/html.js
@@ -110,7 +110,8 @@ function gulptasksHTML($, gulp, buildFolder) {
}
`;
let loadingCss =
- fontCss + fs.readFileSync(path.join(__dirname, "preloader.css")).toString();
+ fontCss +
+ fs.readFileSync(path.join(__dirname, "preloader", "preloader.css")).toString();
const style = document.createElement("style");
style.setAttribute("type", "text/css");
@@ -152,35 +153,16 @@ function gulptasksHTML($, gulp, buildFolder) {
scriptContent += "var bundleIntegrityTranspiled = null;\n";
}
- scriptContent += fs.readFileSync("./bundle-loader.js").toString();
+ scriptContent += fs
+ .readFileSync(path.join(__dirname, "preloader", "preloader.js"))
+ .toString();
loadJs.textContent = scriptContent;
document.head.appendChild(loadJs);
}
- const bodyContent = `
-
_
-
-
-
-
-
-
-
${
- hasLocalFiles ? "Loading" : "Downloading"
- } Game Files
-
-
-
-
- `;
+ const bodyContent = fs
+ .readFileSync(path.join(__dirname, "preloader", "preloader.html"))
+ .toString();
document.body.innerHTML = bodyContent;
}
diff --git a/gulp/preloader.css b/gulp/preloader/preloader.css
similarity index 65%
rename from gulp/preloader.css
rename to gulp/preloader/preloader.css
index e972238c..f6775f76 100644
--- a/gulp/preloader.css
+++ b/gulp/preloader/preloader.css
@@ -47,7 +47,7 @@ body {
}
#ll_fp {
- font-family: GameFont;
+ font-family: "GameFont", Arial, sans-serif;
font-size: 14px;
position: fixed;
z-index: -1;
@@ -89,8 +89,9 @@ body {
#ll_loader > .ll_text {
text-align: center;
color: #777a7f;
- font-family: "GameFont", sans-serif;
+ font-family: "GameFont", Arial, sans-serif;
font-size: 24px;
+ height: 30px;
line-height: 1.2em;
}
@@ -98,60 +99,39 @@ body {
width: 80vw;
max-width: 800px;
margin-top: 40px;
- height: 14px;
+ height: 7px;
border-radius: 20px;
- background: rgba(0, 10, 40, 0.1);
- border: 5px solid transparent;
+ background: rgba(0, 10, 20, 0.08);
+
+ /* border: 5px solid transparent; */
display: flex;
position: relative;
align-items: flex-start;
}
@keyframes LL_LoadingAnimation {
- 0% {
- width: 0%;
- background-color: #777a7f;
- }
- 19.99% {
- width: 99.5%;
- background-color: rgb(50, 197, 121);
- }
- 20% {
- width: 0%;
- background-color: #777a7f;
- }
- 49.99% {
- width: 98%;
- background-color: #4fbfce;
- }
50% {
- width: 0%;
- background-color: #777a7f;
- }
- 74.99% {
- width: 98%;
- background-color: #74a8c0;
- }
- 75% {
- width: 0%;
- background-color: #777a7f;
- }
- 100% {
- width: 98%;
- background-color: #a186d4;
+ background-color: #34ae67;
}
}
#ll_progressbar > span {
border-radius: 20px;
position: absolute;
- height: 100%;
- width: 98%;
+ height: 190%;
+ width: 5%;
background: #fff;
+ transform: translateY(-50%);
+ top: 50%;
display: inline-flex;
- animation: LL_LoadingAnimation 90s ease-in-out infinite;
+ background-color: #269fba;
+ animation: LL_LoadingAnimation 4s ease-in-out infinite;
position: relative;
z-index: 10;
+ border: 4px solid #d5d8de;
+ /* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); */
+ transition: width 0.5s ease-in-out;
+ min-width: 4%;
}
#ll_progressbar > #ll_loadinglabel {
@@ -181,10 +161,10 @@ body {
#ll_standalone {
text-align: center;
color: #777a7f;
- margin-top: 20px;
+ margin-top: 30px;
display: block;
font-size: 16px;
- animation: ShowStandaloneBannerAfterDelay 30s linear;
+ animation: ShowStandaloneBannerAfterDelay 60s linear;
}
#ll_standalone a {
@@ -212,13 +192,71 @@ body {
#ll_preload_status {
position: absolute;
- top: 30px;
+ top: 40px;
left: 50%;
transform: translate(-50%, -50%);
z-index: 100;
- color: rgba(#000, 0.4);
opacity: 1 !important;
- font-size: 12px;
+ font-size: 18px;
+ color: rgba(0, 10, 20, 0.5);
+
+ font-family: "GameFont", Arial, sans-serif;
text-transform: uppercase;
text-align: center;
}
+
+#ll_preload_error {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 999999;
+ background: #d5d8de;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+#ll_preload_error > .inner {
+ color: #fff;
+ font-family: Arial, "sans-serif";
+ font-size: 15px;
+ padding: 0;
+ text-align: center;
+}
+
+#ll_preload_error > .inner > .heading {
+ color: #ef5072;
+ margin-bottom: 40px;
+ font-size: 45px;
+}
+
+#ll_preload_error > .inner > .content {
+ color: #55585f;
+ font-family: monospace;
+ text-align: left;
+ background-color: #fff;
+ padding: 20px;
+ border-radius: 10px;
+
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
+}
+
+#ll_preload_error > .inner .discordLink {
+ color: #333;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ font-family: Arial;
+}
+
+#ll_preload_error > .inner .discordLink a {
+ color: #39f;
+}
+#ll_preload_error > .inner .discordLink strong {
+ font-weight: 900 !important;
+}
+
+#ll_preload_error > .inner .source {
+ color: #777;
+}
diff --git a/gulp/preloader/preloader.html b/gulp/preloader/preloader.html
new file mode 100644
index 00000000..48d3579c
--- /dev/null
+++ b/gulp/preloader/preloader.html
@@ -0,0 +1,21 @@
+_
+
+
+
+
+
+
diff --git a/gulp/preloader/preloader.js b/gulp/preloader/preloader.js
new file mode 100644
index 00000000..808ea132
--- /dev/null
+++ b/gulp/preloader/preloader.js
@@ -0,0 +1,127 @@
+(function () {
+ var loadTimeout = null;
+ var callbackDone = false;
+
+ // Catch load errors
+
+ function errorHandler(event, source, lineno, colno, error) {
+ console.error("đ Init Error:", event, source, lineno, colno, error);
+ var element = document.createElement("div");
+ element.id = "ll_preload_error";
+
+ var inner = document.createElement("div");
+ inner.classList.add("inner");
+ element.appendChild(inner);
+
+ var heading = document.createElement("h3");
+ heading.classList.add("heading");
+ heading.innerText = "Fatal Error";
+ inner.appendChild(heading);
+
+ var content = document.createElement("p");
+ content.classList.add("content");
+ content.innerText = error || (event && event.message) || event || "Unknown Error";
+ inner.appendChild(content);
+
+ var discordLink = document.createElement("p");
+ discordLink.classList.add("discordLink");
+ discordLink.innerHTML =
+ "Please report this error in the #bugs channel of the official discord!";
+
+ inner.appendChild(discordLink);
+
+ if (source) {
+ var sourceElement = document.createElement("p");
+ sourceElement.classList.add("source");
+ sourceElement.innerText = source + ":" + lineno + ":" + colno;
+ inner.appendChild(sourceElement);
+ }
+
+ document.documentElement.appendChild(element);
+ }
+
+ window.addEventListener("error", errorHandler);
+
+ function expectJsParsed() {
+ if (!callbackDone) {
+ console.error("đ Got no core callback");
+ throw new Error("Core thread failed to respond within time.");
+ }
+ }
+
+ function onJsLoaded() {
+ console.log("đ Core loaded at", Math.floor(performance.now()), "ms");
+ loadTimeout = setTimeout(expectJsParsed, 120000);
+ window.removeEventListener("unhandledrejection", errorHandler);
+ }
+
+ window.coreThreadLoadedCb = function () {
+ console.log("đ Core responded at", Math.floor(performance.now()), "ms");
+ clearTimeout(loadTimeout);
+ loadTimeout = null;
+ callbackDone = true;
+ };
+
+ function progressHandler(progress) {
+ var progressElement = document.querySelector("#ll_preload_status");
+ if (progressElement) {
+ progressElement.innerText = "Downloading Bundle (" + Math.round(progress * 1200) + " / 1200 KB)";
+ }
+ var barElement = document.querySelector("#ll_progressbar span");
+ if (barElement) {
+ barElement.style.width = (5 + progress * 75.0).toFixed(2) + "%";
+ }
+ }
+
+ function startBundleDownload() {
+ var xhr = new XMLHttpRequest();
+ var notifiedNotComputable = false;
+
+ xhr.open("GET", bundleSrc, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onprogress = function (ev) {
+ if (ev.lengthComputable) {
+ progressHandler(ev.loaded / ev.total);
+ } else {
+ if (!notifiedNotComputable) {
+ notifiedNotComputable = true;
+ console.warn("Progress not computable:", ev);
+ progressHandler(0);
+ }
+ }
+ };
+
+ xhr.onloadend = function () {
+ if (!xhr.status.toString().match(/^2/)) {
+ throw new Error("Failed to load bundle: " + xhr.status + " " + xhr.statusText);
+ } else {
+ if (!notifiedNotComputable) {
+ progressHandler(1);
+ }
+
+ var options = {};
+ var headers = xhr.getAllResponseHeaders();
+ var m = headers.match(/^Content-Type\:\s*(.*?)$/im);
+
+ if (m && m[1]) {
+ options.type = m[1];
+ }
+
+ var blob = new Blob([this.response], options);
+ var script = document.createElement("script");
+ script.addEventListener("load", onJsLoaded);
+ script.src = window.URL.createObjectURL(blob);
+ script.type = "text/javascript";
+ script.charset = "utf-8";
+ if (bundleIntegrity) {
+ script.setAttribute("integrity", bundleIntegrity);
+ }
+ document.head.appendChild(script);
+ }
+ };
+ xhr.send();
+ }
+
+ console.log("Start bundle download ...");
+ window.addEventListener("load", startBundleDownload);
+})();
diff --git a/src/css/application_error.scss b/src/css/application_error.scss
deleted file mode 100644
index b69a1cfe..00000000
--- a/src/css/application_error.scss
+++ /dev/null
@@ -1,67 +0,0 @@
-#applicationError {
- z-index: 9999;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: $mainBgColor;
- color: #333;
- display: flex;
- flex-direction: column;
- align-content: center;
- align-items: center;
- justify-content: center;
- @include S(padding, 30px);
-
- @include Text;
- text-align: center;
-
- h1 {
- @include TextShadow3D(#ff0b40);
- @include S(margin-top, 20px);
- @include S(margin-bottom, 30px);
- @include SuperHeading;
- @include S(font-size, 35px);
- }
-
- .desc {
- // color: rgba(#fff, 0.6);
- color: $themeColor;
- text-align: left;
- @include PlainText;
- font-weight: bold;
-
- a {
- cursor: pointer;
- pointer-events: all;
- font-weight: bold;
- display: block;
- @include TextShadow3D(#ff0b40);
- @include S(margin-top, 10px);
- }
-
- display: block;
- @include S(max-width, 350px);
- width: 100%;
- }
-
- .details {
- font-size: 11px;
- line-height: 15px;
- color: #888;
- font-family: monospace;
- text-align: left;
- @include S(padding, 6px);
- @include S(border-radius, $globalBorderRadius);
- @include BoxShadow3D(#eee);
- position: absolute;
- @include S(bottom, 25px);
- left: 50%;
- transform: translateX(-50%);
- max-width: calc(100vw - 40px);
- box-sizing: border-box;
- @include BreakText;
- min-width: 300px;
- }
-}
diff --git a/src/css/common.scss b/src/css/common.scss
index f4946e9d..b3b3b103 100644
--- a/src/css/common.scss
+++ b/src/css/common.scss
@@ -427,6 +427,16 @@ canvas {
}
}
+.prefab_LoadingProgressIndicator {
+ @include PlainText;
+ @include S(margin-top, 20px);
+ width: 100%;
+ color: #336c9f;
+ @include S(height, 20px);
+ text-transform: uppercase;
+ text-align: center;
+}
+
.prefab_FeatureComingSoon {
position: relative;
&::after {
diff --git a/src/css/main.scss b/src/css/main.scss
index 77ee8cd0..6ac138f8 100644
--- a/src/css/main.scss
+++ b/src/css/main.scss
@@ -16,7 +16,6 @@
@import "common";
@import "animations";
@import "game_state";
-@import "application_error";
@import "textual_game_state";
@import "adinplay";
@import "changelog_skins";
diff --git a/src/html/index.html b/src/html/index.html
index a4a0fff4..ab32169c 100644
--- a/src/html/index.html
+++ b/src/html/index.html
@@ -1,5 +1,5 @@
-
+
shapez Demo - Factory Automation Game
diff --git a/src/js/core/background_resources_loader.js b/src/js/core/background_resources_loader.js
index 99948850..8e8c8242 100644
--- a/src/js/core/background_resources_loader.js
+++ b/src/js/core/background_resources_loader.js
@@ -2,58 +2,36 @@
import { Application } from "../application";
/* typehints:end */
+import { initSpriteCache } from "../game/meta_building_registry";
+import { MUSIC, SOUNDS } from "../platform/sound";
+import { T } from "../translations";
+import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
+import { cachebust } from "./cachebust";
import { Loader } from "./loader";
import { createLogger } from "./logging";
import { Signal } from "./signal";
-import { SOUNDS, MUSIC } from "../platform/sound";
-import { AtlasDefinition, atlasFiles } from "./atlas_definitions";
-import { initBuildingCodesAfterResourcesLoaded } from "../game/meta_building_registry";
-import { cachebust } from "./cachebust";
+import { clamp, getLogoSprite, timeoutPromise } from "./utils";
const logger = createLogger("background_loader");
-export function getLogoSprite() {
- if (G_WEGAME_VERSION) {
- return "logo_wegame.png";
- }
+const MAIN_MENU_ASSETS = {
+ sprites: [getLogoSprite()],
+ sounds: [SOUNDS.uiClick, SOUNDS.uiError, SOUNDS.dialogError, SOUNDS.dialogOk],
+ atlas: [],
+ css: [],
+};
- if (G_IS_STEAM_DEMO) {
- return "logo_demo.png";
- }
+const INGAME_ASSETS = {
+ sprites: [],
+ sounds: [
+ ...Array.from(Object.values(MUSIC)),
+ ...Array.from(Object.values(SOUNDS)).filter(sound => !MAIN_MENU_ASSETS.sounds.includes(sound)),
+ ],
+ atlas: atlasFiles,
+ css: ["async-resources.css"],
+};
- if (G_CHINA_VERSION) {
- return "logo_cn.png";
- }
-
- if (G_IS_STANDALONE) {
- return "logo.png";
- }
-
- if (G_IS_BROWSER) {
- return "logo_demo.png";
- }
-
- return "logo.png";
-}
-
-const essentialMainMenuSprites = [getLogoSprite()];
-
-const essentialMainMenuSounds = [
- SOUNDS.uiClick,
- SOUNDS.uiError,
- SOUNDS.dialogError,
- SOUNDS.dialogOk,
- SOUNDS.swishShow,
- SOUNDS.swishHide,
-];
-
-const essentialBareGameAtlases = atlasFiles;
-const essentialBareGameSprites = [];
-const essentialBareGameSounds = [MUSIC.theme];
-
-const additionalGameSprites = [];
-// @ts-ignore
-const additionalGameSounds = [...Object.values(SOUNDS), ...Object.values(MUSIC)];
+const LOADER_TIMEOUT_PER_RESOURCE = 180000;
export class BackgroundResourcesLoader {
/**
@@ -63,193 +41,199 @@ export class BackgroundResourcesLoader {
constructor(app) {
this.app = app;
- this.registerReady = false;
- this.mainMenuReady = false;
- this.bareGameReady = false;
- this.additionalReady = false;
+ this.mainMenuPromise = null;
+ this.ingamePromise = null;
- this.signalMainMenuLoaded = new Signal();
- this.signalBareGameLoaded = new Signal();
- this.signalAdditionalLoaded = new Signal();
-
- this.numAssetsLoaded = 0;
- this.numAssetsToLoadTotal = 0;
-
- // Avoid loading stuff twice
- this.spritesLoaded = [];
- this.soundsLoaded = [];
- this.atlasesLoaded = [];
- this.cssLoaded = [];
+ this.resourceStateChangedSignal = new Signal();
}
- getNumAssetsLoaded() {
- return this.numAssetsLoaded;
- }
-
- getNumAssetsTotal() {
- return this.numAssetsToLoadTotal;
- }
-
- getPromiseForMainMenu() {
- if (this.mainMenuReady) {
- return Promise.resolve();
+ getMainMenuPromise() {
+ if (this.mainMenuPromise) {
+ return this.mainMenuPromise;
}
- return new Promise(resolve => {
- this.signalMainMenuLoaded.add(resolve);
- });
+ logger.warn("â° Loading main menu assets");
+ return (this.mainMenuPromise = this.loadAssets(MAIN_MENU_ASSETS));
}
- getPromiseForBareGame() {
- if (this.bareGameReady) {
- return Promise.resolve();
+ getIngamePromise() {
+ if (this.ingamePromise) {
+ return this.ingamePromise;
}
-
- return new Promise(resolve => {
- this.signalBareGameLoaded.add(resolve);
- });
- }
-
- startLoading() {
- this.internalStartLoadingEssentialsForMainMenu();
- }
-
- internalStartLoadingEssentialsForMainMenu() {
- logger.log("â° Start load: main menu");
- this.internalLoadSpritesAndSounds(essentialMainMenuSprites, essentialMainMenuSounds)
- .catch(err => {
- logger.warn("â° Failed to load essentials for main menu:", err);
- })
- .then(() => {
- logger.log("â° Finish load: main menu");
- this.mainMenuReady = true;
- this.signalMainMenuLoaded.dispatch();
- });
- }
-
- internalStartLoadingEssentialsForBareGame() {
- logger.log("â° Start load: bare game");
- this.internalLoadSpritesAndSounds(
- essentialBareGameSprites,
- essentialBareGameSounds,
- essentialBareGameAtlases
- )
- .then(() => this.internalPreloadCss("async-resources.scss"))
- .catch(err => {
- logger.warn("â° Failed to load essentials for bare game:", err);
- })
- .then(() => {
- logger.log("â° Finish load: bare game");
- this.bareGameReady = true;
- initBuildingCodesAfterResourcesLoaded();
- this.signalBareGameLoaded.dispatch();
- this.internalStartLoadingAdditionalGameAssets();
- });
- }
-
- internalStartLoadingAdditionalGameAssets() {
- const additionalAtlases = [];
- logger.log("â° Start load: additional assets (", additionalAtlases.length, "images)");
- this.internalLoadSpritesAndSounds(additionalGameSprites, additionalGameSounds, additionalAtlases)
- .catch(err => {
- logger.warn("â° Failed to load additional assets:", err);
- })
- .then(() => {
- logger.log("â° Finish load: additional assets");
- this.additionalReady = true;
- this.signalAdditionalLoaded.dispatch();
- });
- }
-
- internalPreloadCss(name) {
- if (this.cssLoaded.includes(name)) {
- return;
- }
- this.cssLoaded.push(name);
- return new Promise((resolve, reject) => {
- const link = document.createElement("link");
-
- link.onload = resolve;
- link.onerror = reject;
-
- link.setAttribute("rel", "stylesheet");
- link.setAttribute("media", "all");
- link.setAttribute("type", "text/css");
- link.setAttribute("href", cachebust("async-resources.css"));
- document.head.appendChild(link);
- });
+ logger.warn("â° Loading ingame assets");
+ const promise = this.loadAssets(INGAME_ASSETS).then(() => initSpriteCache());
+ return (this.ingamePromise = promise);
}
/**
- * @param {Array} sprites
- * @param {Array} sounds
- * @param {Array} atlases
- * @returns {Promise}
+ *
+ * @param {object} param0
+ * @param {string[]} param0.sprites
+ * @param {string[]} param0.sounds
+ * @param {AtlasDefinition[]} param0.atlas
+ * @param {string[]} param0.css
*/
- internalLoadSpritesAndSounds(sprites, sounds, atlases = []) {
- this.numAssetsToLoadTotal = sprites.length + sounds.length + atlases.length;
- this.numAssetsLoaded = 0;
+ async loadAssets({ sprites, sounds, atlas, css }) {
+ /**
+ * @type {((progressHandler: (progress: number) => void) => Promise)[]}
+ */
+ let promiseFunctions = [];
- let promises = [];
-
- for (let i = 0; i < sounds.length; ++i) {
- if (this.soundsLoaded.indexOf(sounds[i]) >= 0) {
- // Already loaded
- continue;
- }
-
- this.soundsLoaded.push(sounds[i]);
- promises.push(
- this.app.sound
- .loadSound(sounds[i])
- .catch(err => {
- logger.warn("Failed to load sound:", sounds[i]);
- })
- .then(() => {
- this.numAssetsLoaded++;
- })
+ // CSS
+ for (let i = 0; i < css.length; ++i) {
+ promiseFunctions.push(progress =>
+ timeoutPromise(
+ this.internalPreloadCss(cachebust(css[i]), progress),
+ LOADER_TIMEOUT_PER_RESOURCE
+ ).catch(err => {
+ logger.error("Failed to load css:", css[i], err);
+ throw new Error("HUD Stylesheet " + css[i] + " failed to load: " + err);
+ })
);
}
+ // ATLAS FILES
+ for (let i = 0; i < atlas.length; ++i) {
+ promiseFunctions.push(progress =>
+ timeoutPromise(Loader.preloadAtlas(atlas[i], progress), LOADER_TIMEOUT_PER_RESOURCE).catch(
+ err => {
+ logger.error("Failed to load atlas:", atlas[i].sourceFileName, err);
+ throw new Error("Atlas " + atlas[i].sourceFileName + " failed to load: " + err);
+ }
+ )
+ );
+ }
+
+ // HUD Sprites
for (let i = 0; i < sprites.length; ++i) {
- if (this.spritesLoaded.indexOf(sprites[i]) >= 0) {
- // Already loaded
- continue;
- }
- this.spritesLoaded.push(sprites[i]);
- promises.push(
- Loader.preloadCSSSprite(sprites[i])
- .catch(err => {
- logger.warn("Failed to load css sprite:", sprites[i]);
- })
- .then(() => {
- this.numAssetsLoaded++;
- })
+ promiseFunctions.push(progress =>
+ timeoutPromise(
+ Loader.preloadCSSSprite(sprites[i], progress),
+ LOADER_TIMEOUT_PER_RESOURCE
+ ).catch(err => {
+ logger.error("Failed to load css sprite:", sprites[i], err);
+ throw new Error("HUD Sprite " + sprites[i] + " failed to load: " + err);
+ })
);
}
- for (let i = 0; i < atlases.length; ++i) {
- const atlas = atlases[i];
- if (this.atlasesLoaded.includes(atlas)) {
- // Already loaded
- continue;
- }
- this.atlasesLoaded.push(atlas);
-
- promises.push(
- Loader.preloadAtlas(atlas)
- .catch(err => {
- logger.warn("Failed to load atlas:", atlas.sourceFileName, err);
- })
- .then(() => {
- this.numAssetsLoaded++;
- })
+ // SFX & Music
+ for (let i = 0; i < sounds.length; ++i) {
+ promiseFunctions.push(progress =>
+ timeoutPromise(this.app.sound.loadSound(sounds[i]), LOADER_TIMEOUT_PER_RESOURCE).catch(
+ err => {
+ logger.warn("Failed to load sound, will not be available:", sounds[i], err);
+ }
+ )
);
}
- return Promise.all(promises).then(() => {
- this.numAssetsToLoadTotal = 0;
- this.numAssetsLoaded = 0;
+ const originalAmount = promiseFunctions.length;
+ const start = performance.now();
+
+ logger.log("â° Preloading", originalAmount, "assets");
+
+ let progress = 0;
+ this.resourceStateChangedSignal.dispatch({ progress });
+ let promises = [];
+ for (let i = 0; i < promiseFunctions.length; i++) {
+ let lastIndividualProgress = 0;
+ const progressHandler = individualProgress => {
+ const delta = clamp(individualProgress) - lastIndividualProgress;
+ lastIndividualProgress = individualProgress;
+ progress += delta / originalAmount;
+ this.resourceStateChangedSignal.dispatch({ progress });
+ };
+ promises.push(
+ promiseFunctions
+ .shift()(progressHandler)
+ .then(() => {
+ progressHandler(1);
+ })
+ );
+ }
+ await Promise.all(promises);
+
+ logger.log("â° Preloaded assets in", Math.round((performance.now() - start) / 1000.0), "ms");
+ }
+
+ /**
+ * Shows an error when a resource failed to load and allows to reload the game
+ */
+ showLoaderError(dialogs, err) {
+ if (G_IS_STANDALONE) {
+ dialogs
+ .showWarning(
+ T.dialogs.resourceLoadFailed.title,
+ T.dialogs.resourceLoadFailed.descSteamDemo + "
" + err,
+ ["retry"]
+ )
+ .retry.add(() => window.location.reload());
+ } else {
+ dialogs
+ .showWarning(
+ T.dialogs.resourceLoadFailed.title,
+ T.dialogs.resourceLoadFailed.descWeb.replace(
+ "",
+ `${T.dialogs.resourceLoadFailed.demoLinkText}`
+ ) +
+ "
" +
+ err,
+ ["retry"]
+ )
+ .retry.add(() => window.location.reload());
+ }
+ }
+
+ preloadWithProgress(src, progressHandler) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ let notifiedNotComputable = false;
+
+ xhr.open("GET", src, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onprogress = function (ev) {
+ if (ev.lengthComputable) {
+ progressHandler(ev.loaded / ev.total);
+ } else {
+ if (!notifiedNotComputable) {
+ notifiedNotComputable = true;
+ console.warn("Progress not computable:", ev);
+ progressHandler(0);
+ }
+ }
+ };
+
+ xhr.onloadend = function () {
+ if (!xhr.status.toString().match(/^2/)) {
+ reject(src + ": " + xhr.status + " " + xhr.statusText);
+ } else {
+ if (!notifiedNotComputable) {
+ progressHandler(1);
+ }
+
+ const options = {};
+ const headers = xhr.getAllResponseHeaders();
+ const contentType = headers.match(/^Content-Type\:\s*(.*?)$/im);
+ if (contentType && contentType[1]) {
+ options.type = contentType[1].split(";")[0];
+ }
+ const blob = new Blob([this.response], options);
+ resolve(window.URL.createObjectURL(blob));
+ }
+ };
+ xhr.send();
+ });
+ }
+
+ internalPreloadCss(src, progressHandler) {
+ return this.preloadWithProgress(cachebust(src), progressHandler).then(blobSrc => {
+ var styleElement = document.createElement("link");
+ styleElement.href = blobSrc;
+ styleElement.rel = "stylesheet";
+ styleElement.setAttribute("media", "all");
+ styleElement.type = "text/css";
+ document.head.appendChild(styleElement);
});
}
}
diff --git a/src/js/core/error_handler.js b/src/js/core/error_handler.js
index 686e4e4e..61cd9251 100644
--- a/src/js/core/error_handler.js
+++ b/src/js/core/error_handler.js
@@ -1,7 +1,3 @@
-import { logSection } from "./logging";
-import { stringifyObjectContainingErrors } from "./logging";
-import { removeAllChildren } from "./utils";
-
export let APPLICATION_ERROR_OCCURED = false;
/**
@@ -13,114 +9,8 @@ export let APPLICATION_ERROR_OCCURED = false;
* @param {Error} source
*/
function catchErrors(message, source, lineno, colno, error) {
- let fullPayload = JSON.parse(
- stringifyObjectContainingErrors({
- message,
- source,
- lineno,
- colno,
- error,
- })
- );
-
- if (("" + message).indexOf("Script error.") >= 0) {
- console.warn("Thirdparty script error:", message);
- return;
- }
-
- if (("" + message).indexOf("NS_ERROR_FAILURE") >= 0) {
- console.warn("Firefox NS_ERROR_FAILURE error:", message);
- return;
- }
-
- if (("" + message).indexOf("Cannot read property 'postMessage' of null") >= 0) {
- console.warn("Safari can not read post message error:", message);
- return;
- }
-
- if (!G_IS_DEV && G_IS_BROWSER && ("" + source).indexOf("shapez.io") < 0) {
- console.warn("Thirdparty error:", message);
- return;
- }
-
- console.log("\n\n\nâ ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸\n\n\n");
- console.log(" APPLICATION CRASHED ");
- console.log("\n\nâ ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸â ď¸\n\n\n");
-
- logSection("APPLICATION CRASH", "#e53935");
- console.error("Error:", message, "->", error);
- console.log("Payload:", fullPayload);
-
- if (window.Sentry && !window.anyModLoaded) {
- window.Sentry.withScope(scope => {
- window.Sentry.setTag("message", message);
- window.Sentry.setTag("source", source);
-
- window.Sentry.setExtra("message", message);
- window.Sentry.setExtra("source", source);
- window.Sentry.setExtra("lineno", lineno);
- window.Sentry.setExtra("colno", colno);
- window.Sentry.setExtra("error", error);
- window.Sentry.setExtra("fullPayload", fullPayload);
-
- try {
- const userName = window.localStorage.getItem("tracking_context") || null;
- window.Sentry.setTag("username", userName);
- } catch (ex) {
- // ignore
- }
-
- window.Sentry.captureException(error || source);
- });
- }
-
- if (APPLICATION_ERROR_OCCURED) {
- console.warn("ERROR: Only showing and submitting first error");
- return;
- }
-
APPLICATION_ERROR_OCCURED = true;
- const element = document.createElement("div");
- element.id = "applicationError";
-
- const title = document.createElement("h1");
- title.innerText = "Whoops!";
- element.appendChild(title);
-
- const desc = document.createElement("div");
- desc.classList.add("desc");
- desc.innerHTML = `
- It seems the application crashed - I am sorry for that!
- An anonymized crash report has been sent, and I will have a look as soon as possible.
- If you have additional information how I can reproduce this error, please E-Mail me:
- bugs@shapez.io`;
- element.appendChild(desc);
-
- const details = document.createElement("pre");
- details.classList.add("details");
- details.innerText = (error && error.stack) || message;
- element.appendChild(details);
-
- const inject = function () {
- if (!G_IS_DEV) {
- removeAllChildren(document.body);
- }
- if (document.body.parentElement) {
- document.body.parentElement.appendChild(element);
- } else {
- document.body.appendChild(element);
- }
- };
-
- if (document.body) {
- inject();
- } else {
- setTimeout(() => {
- inject();
- }, 200);
- }
-
- return true;
+ console.error(message, source, lineno, colno, error);
}
-window.onerror = catchErrors;
+window.addEventListener("error", catchErrors);
diff --git a/src/js/core/loader.js b/src/js/core/loader.js
index dc52185d..b134aca9 100644
--- a/src/js/core/loader.js
+++ b/src/js/core/loader.js
@@ -76,61 +76,36 @@ class LoaderImpl {
/**
*
* @param {string} key
+ * @param {(progress: number) => void} progressHandler
* @returns {Promise}
*/
- internalPreloadImage(key) {
+ internalPreloadImage(key, progressHandler) {
const url = cachebust("res/" + key);
const image = new Image();
- let triesSoFar = 0;
-
- return Promise.race([
- new Promise((resolve, reject) => {
- setTimeout(() => reject("loader request timeout"), G_IS_DEV ? 500 : 30000);
- }),
-
- new Promise(resolve => {
- image.onload = () => {
- image.onerror = null;
- image.onload = null;
-
- if (typeof image.decode === "function") {
- // SAFARI: Image.decode() fails on safari with svgs -> we dont want to fail
- // on that
- // FIREFOX: Decode never returns if the image is in cache, so call it in background
- image.decode().then(
- () => null,
- () => null
- );
- }
- resolve(image);
- };
-
- image.onerror = reason => {
- logger.warn("Failed to load '" + url + "':", reason);
- if (++triesSoFar < 4) {
- logger.log("Retrying to load image from", url);
- image.src = url + "?try=" + triesSoFar;
- } else {
- logger.warn("Failed to load", url, "after", triesSoFar, "tries with reason", reason);
- image.onerror = null;
- image.onload = null;
- resolve(null);
- }
- };
-
- image.src = url;
- }),
- ]);
+ return this.app.backgroundResourceLoader
+ .preloadWithProgress(url, progress => {
+ progressHandler(progress);
+ })
+ .then(url => {
+ return new Promise((resolve, reject) => {
+ image.addEventListener("load", () => resolve(image));
+ image.addEventListener("error", err =>
+ reject("Failed to load sprite " + key + ": " + err)
+ );
+ image.src = url;
+ });
+ });
}
/**
* Preloads a sprite
* @param {string} key
+ * @param {(progress: number) => void} progressHandler
* @returns {Promise}
*/
- preloadCSSSprite(key) {
- return this.internalPreloadImage(key).then(image => {
+ preloadCSSSprite(key, progressHandler) {
+ return this.internalPreloadImage(key, progressHandler).then(image => {
if (key.indexOf("game_misc") >= 0) {
// Allow access to regular sprites
this.sprites.set(key, new RegularSprite(image, image.width, image.height));
@@ -142,10 +117,11 @@ class LoaderImpl {
/**
* Preloads an atlas
* @param {AtlasDefinition} atlas
+ * @param {(progress: number) => void} progressHandler
* @returns {Promise}
*/
- preloadAtlas(atlas) {
- return this.internalPreloadImage(atlas.getFullSourcePath()).then(image => {
+ preloadAtlas(atlas, progressHandler) {
+ return this.internalPreloadImage(atlas.getFullSourcePath(), progressHandler).then(image => {
// @ts-ignore
image.label = atlas.sourceFileName;
return this.internalParseAtlas(atlas, image);
diff --git a/src/js/core/utils.js b/src/js/core/utils.js
index 4092d861..e75789b9 100644
--- a/src/js/core/utils.js
+++ b/src/js/core/utils.js
@@ -747,3 +747,42 @@ export function getRomanNumber(number) {
romanLiteralsCache[number] = formatted;
return formatted;
}
+
+/**
+ * Returns the appropriate logo sprite path
+ */
+export function getLogoSprite() {
+ if (G_WEGAME_VERSION) {
+ return "logo_wegame.png";
+ }
+
+ if (G_IS_STEAM_DEMO) {
+ return "logo_demo.png";
+ }
+
+ if (G_CHINA_VERSION) {
+ return "logo_cn.png";
+ }
+
+ if (G_IS_STANDALONE) {
+ return "logo.png";
+ }
+
+ if (G_IS_BROWSER) {
+ return "logo_demo.png";
+ }
+
+ return "logo.png";
+}
+
+/**
+ * Rejects a promise after X ms
+ */
+export function timeoutPromise(promise, timeout = 30000) {
+ return Promise.race([
+ new Promise((resolve, reject) => {
+ setTimeout(() => reject("timeout of " + timeout + " ms exceeded"), timeout);
+ }),
+ promise,
+ ]);
+}
diff --git a/src/js/game/game_loading_overlay.js b/src/js/game/game_loading_overlay.js
index d6bb79f0..94a07160 100644
--- a/src/js/game/game_loading_overlay.js
+++ b/src/js/game/game_loading_overlay.js
@@ -46,6 +46,7 @@ export class GameLoadingOverlay {
this.parent.appendChild(this.element);
this.internalAddSpinnerAndText(this.element);
this.internalAddHint(this.element);
+ this.internalAddProgressIndicator(this.element);
}
/**
@@ -68,4 +69,12 @@ export class GameLoadingOverlay {
hint.classList.add("prefab_GameHint");
element.appendChild(hint);
}
+
+ internalAddProgressIndicator(element) {
+ const indicator = document.createElement("span");
+ indicator.innerHTML = "";
+ indicator.classList.add("prefab_LoadingProgressIndicator");
+ element.appendChild(indicator);
+ this.loadingIndicator = indicator;
+ }
}
diff --git a/src/js/game/meta_building_registry.js b/src/js/game/meta_building_registry.js
index 55bc46d4..eba6fd61 100644
--- a/src/js/game/meta_building_registry.js
+++ b/src/js/game/meta_building_registry.js
@@ -113,7 +113,7 @@ export function initMetaBuildingRegistry() {
/**
* Once all sprites are loaded, propagates the cache
*/
-export function initBuildingCodesAfterResourcesLoaded() {
+export function initSpriteCache() {
logger.log("Propagating sprite cache");
for (const key in gBuildingVariants) {
const variant = gBuildingVariants[key];
diff --git a/src/js/main.js b/src/js/main.js
index 0f80f527..634ad4d2 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -64,4 +64,4 @@ function bootApp() {
app.boot();
}
-window.addEventListener("load", bootApp);
+bootApp();
diff --git a/src/js/platform/browser/sound.js b/src/js/platform/browser/sound.js
index a837c2e4..a7089665 100644
--- a/src/js/platform/browser/sound.js
+++ b/src/js/platform/browser/sound.js
@@ -21,33 +21,28 @@ class SoundSpritesContainer {
if (this.loadingPromise) {
return this.loadingPromise;
}
- return (this.loadingPromise = Promise.race([
- new Promise((resolve, reject) => {
- setTimeout(reject, G_IS_DEV ? 500 : 5000);
- }),
- new Promise(resolve => {
- this.howl = new Howl({
- src: cachebust("res/sounds/sfx.mp3"),
- sprite: sprites.sprite,
- autoplay: false,
- loop: false,
- volume: 0,
- preload: true,
- pool: 20,
- onload: () => {
- resolve();
- },
- onloaderror: (id, err) => {
- logger.warn("SFX failed to load:", id, err);
- this.howl = null;
- resolve();
- },
- onplayerror: (id, err) => {
- logger.warn("SFX failed to play:", id, err);
- },
- });
- }),
- ]));
+ return (this.loadingPromise = new Promise(resolve => {
+ this.howl = new Howl({
+ src: cachebust("res/sounds/sfx.mp3"),
+ sprite: sprites.sprite,
+ autoplay: false,
+ loop: false,
+ volume: 0,
+ preload: true,
+ pool: 20,
+ onload: () => {
+ resolve();
+ },
+ onloaderror: (id, err) => {
+ logger.warn("SFX failed to load:", id, err);
+ this.howl = null;
+ resolve();
+ },
+ onplayerror: (id, err) => {
+ logger.warn("SFX failed to play:", id, err);
+ },
+ });
+ }));
}
play(volume, key) {
@@ -98,41 +93,37 @@ class MusicInstance extends MusicInstanceInterface {
this.playing = false;
}
load() {
- return Promise.race([
- new Promise((resolve, reject) => {
- setTimeout(reject, G_IS_DEV ? 500 : 5000);
- }),
- new Promise((resolve, reject) => {
- this.howl = new Howl({
- src: cachebust("res/sounds/music/" + this.url + ".mp3"),
- autoplay: false,
- loop: true,
- html5: true,
- volume: 1,
- preload: true,
- pool: 2,
+ return new Promise((resolve, reject) => {
+ this.howl = new Howl({
+ src: cachebust("res/sounds/music/" + this.url + ".mp3"),
+ autoplay: false,
+ loop: true,
+ html5: true,
+ volume: 1,
+ preload: true,
+ pool: 2,
- onunlock: () => {
- if (this.playing) {
- logger.log("Playing music after manual unlock");
- this.play();
- }
- },
+ onunlock: () => {
+ if (this.playing) {
+ logger.log("Playing music after manual unlock");
+ this.play();
+ }
+ },
- onload: () => {
- resolve();
- },
- onloaderror: (id, err) => {
- logger.warn(this, "Music", this.url, "failed to load:", id, err);
- this.howl = null;
- resolve();
- },
- onplayerror: (id, err) => {
- logger.warn(this, "Music", this.url, "failed to play:", id, err);
- },
- });
- }),
- ]);
+ onload: () => {
+ resolve();
+ },
+ onloaderror: (id, err) => {
+ logger.warn(this, "Music", this.url, "failed to load:", id, err);
+ this.howl = null;
+ resolve();
+ },
+
+ onplayerror: (id, err) => {
+ logger.warn(this, "Music", this.url, "failed to play:", id, err);
+ },
+ });
+ });
}
stop() {
diff --git a/src/js/states/about.js b/src/js/states/about.js
index 4380b02c..69f2e9b5 100644
--- a/src/js/states/about.js
+++ b/src/js/states/about.js
@@ -2,7 +2,7 @@ import { TextualGameState } from "../core/textual_game_state";
import { T } from "../translations";
import { THIRDPARTY_URLS } from "../core/config";
import { cachebust } from "../core/cachebust";
-import { getLogoSprite } from "../core/background_resources_loader";
+import { getLogoSprite } from "../core/utils";
export class AboutState extends TextualGameState {
constructor() {
diff --git a/src/js/states/ingame.js b/src/js/states/ingame.js
index d950553c..21e29803 100644
--- a/src/js/states/ingame.js
+++ b/src/js/states/ingame.js
@@ -10,6 +10,8 @@ import { GameCore } from "../game/core";
import { MUSIC } from "../platform/sound";
import { enumGameModeIds } from "../game/game_mode";
import { MOD_SIGNALS } from "../mods/mod_signals";
+import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
+import { T } from "../translations";
const logger = createLogger("state/ingame");
@@ -231,19 +233,42 @@ export class InGameState extends GameState {
if (this.switchStage(GAME_LOADING_STATES.s3_createCore)) {
logger.log("Waiting for resources to load");
- this.app.backgroundResourceLoader.getPromiseForBareGame().then(() => {
- logger.log("Creating new game core");
- this.core = new GameCore(this.app);
-
- this.core.initializeRoot(this, this.savegame, this.gameModeId);
-
- if (this.savegame.hasGameDump()) {
- this.stage4bResumeGame();
- } else {
- this.app.gameAnalytics.handleGameStarted();
- this.stage4aInitEmptyGame();
- }
+ this.app.backgroundResourceLoader.resourceStateChangedSignal.add(({ progress }) => {
+ this.loadingOverlay.loadingIndicator.innerText = T.global.loadingResources.replace(
+ "",
+ (progress * 100.0).toFixed(1)
+ );
});
+
+ this.app.backgroundResourceLoader.getIngamePromise().then(
+ () => {
+ this.loadingOverlay.loadingIndicator.innerText = "";
+ this.app.backgroundResourceLoader.resourceStateChangedSignal.removeAll();
+
+ logger.log("Creating new game core");
+ this.core = new GameCore(this.app);
+
+ this.core.initializeRoot(this, this.savegame, this.gameModeId);
+
+ if (this.savegame.hasGameDump()) {
+ this.stage4bResumeGame();
+ } else {
+ this.app.gameAnalytics.handleGameStarted();
+ this.stage4aInitEmptyGame();
+ }
+ },
+ err => {
+ logger.error("Failed to preload resources:", err);
+ const dialogs = new HUDModalDialogs(null, this.app);
+ const dialogsElement = document.createElement("div");
+ dialogsElement.id = "ingame_HUD_ModalDialogs";
+ dialogsElement.style.zIndex = "999999";
+ document.body.appendChild(dialogsElement);
+ dialogs.initializeToElement(dialogsElement);
+
+ this.app.backgroundResourceLoader.showLoaderError(dialogs, err);
+ }
+ );
}
}
diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js
index f8c90c33..ff013bc0 100644
--- a/src/js/states/main_menu.js
+++ b/src/js/states/main_menu.js
@@ -1,4 +1,3 @@
-import { getLogoSprite } from "../core/background_resources_loader";
import { cachebust } from "../core/cachebust";
import { globalConfig, openStandaloneLink, THIRDPARTY_URLS } from "../core/config";
import { GameState } from "../core/game_state";
@@ -8,7 +7,7 @@ import { ReadWriteProxy } from "../core/read_write_proxy";
import {
formatSecondsToTimeAgo,
generateFileDownload,
- isSupportedBrowser,
+ getLogoSprite,
makeButton,
makeDiv,
makeDivElement,
@@ -321,8 +320,9 @@ export class MainMenuState extends GameState {
}
onEnter(payload) {
+ // Start loading already
const app = this.app;
- setTimeout(() => app.backgroundResourceLoader.internalStartLoadingEssentialsForBareGame(), 10);
+ setTimeout(() => app.backgroundResourceLoader.getIngamePromise(), 10);
this.dialogs = new HUDModalDialogs(null, this.app);
const dialogsElement = document.body.querySelector(".modalDialogParent");
diff --git a/src/js/states/mobile_warning.js b/src/js/states/mobile_warning.js
index 520b155a..24eda8e5 100644
--- a/src/js/states/mobile_warning.js
+++ b/src/js/states/mobile_warning.js
@@ -1,7 +1,6 @@
-import { GameState } from "../core/game_state";
import { cachebust } from "../core/cachebust";
-import { THIRDPARTY_URLS } from "../core/config";
-import { getLogoSprite } from "../core/background_resources_loader";
+import { GameState } from "../core/game_state";
+import { getLogoSprite } from "../core/utils";
export class MobileWarningState extends GameState {
constructor() {
diff --git a/src/js/states/preload.js b/src/js/states/preload.js
index 3d9c2370..3706555f 100644
--- a/src/js/states/preload.js
+++ b/src/js/states/preload.js
@@ -1,9 +1,9 @@
import { CHANGELOG } from "../changelog";
-import { getLogoSprite } from "../core/background_resources_loader";
import { cachebust } from "../core/cachebust";
import { globalConfig } from "../core/config";
import { GameState } from "../core/game_state";
import { createLogger } from "../core/logging";
+import { getLogoSprite } from "../core/utils";
import { getRandomHint } from "../game/hints";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
@@ -39,6 +39,7 @@ export class PreloadState extends GameState {
this.nextHintDuration = 0;
this.statusText = this.htmlElement.querySelector("#ll_preload_status");
+ this.progressElement = this.htmlElement.querySelector("#ll_progressbar span");
this.startLoading();
}
@@ -70,10 +71,10 @@ export class PreloadState extends GameState {
startLoading() {
this.setStatus("Booting")
- .then(() => this.setStatus("Creating platform wrapper"))
+ .then(() => this.setStatus("Creating platform wrapper", 3))
.then(() => this.app.platformWrapper.initialize())
- .then(() => this.setStatus("Initializing local storage"))
+ .then(() => this.setStatus("Initializing local storage", 6))
.then(() => {
const wrapper = this.app.platformWrapper;
if (wrapper instanceof PlatformWrapperImplBrowser) {
@@ -94,19 +95,19 @@ export class PreloadState extends GameState {
}
})
- .then(() => this.setStatus("Creating storage"))
+ .then(() => this.setStatus("Creating storage", 9))
.then(() => {
return this.app.storage.initialize();
})
- .then(() => this.setStatus("Initializing libraries"))
+ .then(() => this.setStatus("Initializing libraries", 12))
.then(() => this.app.analytics.initialize())
.then(() => this.app.gameAnalytics.initialize())
- .then(() => this.setStatus("Connecting to api"))
+ .then(() => this.setStatus("Connecting to api", 15))
.then(() => this.fetchDiscounts())
- .then(() => this.setStatus("Initializing settings"))
+ .then(() => this.setStatus("Initializing settings", 20))
.then(() => {
return this.app.settings.initialize();
})
@@ -118,7 +119,7 @@ export class PreloadState extends GameState {
}
})
- .then(() => this.setStatus("Initializing language"))
+ .then(() => this.setStatus("Initializing language", 25))
.then(() => {
if (G_CHINA_VERSION || G_WEGAME_VERSION) {
return this.app.settings.updateLanguage("zh-CN");
@@ -139,22 +140,17 @@ export class PreloadState extends GameState {
updateApplicationLanguage(language);
})
- .then(() => this.setStatus("Initializing sounds"))
+ .then(() => this.setStatus("Initializing sounds", 30))
.then(() => {
- // Notice: We don't await the sounds loading itself
return this.app.sound.initialize();
})
- .then(() => {
- this.app.backgroundResourceLoader.startLoading();
- })
-
- .then(() => this.setStatus("Initializing restrictions"))
+ .then(() => this.setStatus("Initializing restrictions", 34))
.then(() => {
return this.app.restrictionMgr.initialize();
})
- .then(() => this.setStatus("Initializing savegame"))
+ .then(() => this.setStatus("Initializing savegames", 38))
.then(() => {
return this.app.savegameMgr.initialize().catch(err => {
logger.error("Failed to initialize savegames:", err);
@@ -165,12 +161,25 @@ export class PreloadState extends GameState {
});
})
- .then(() => this.setStatus("Downloading resources"))
+ .then(() => this.setStatus("Downloading resources", 40))
.then(() => {
- return this.app.backgroundResourceLoader.getPromiseForMainMenu();
+ this.app.backgroundResourceLoader.resourceStateChangedSignal.add(({ progress }) => {
+ this.setStatus(
+ "Downloading resources (" + (progress * 100.0).toFixed(1) + " %)",
+ 40 + progress * 50
+ );
+ });
+ return this.app.backgroundResourceLoader.getMainMenuPromise().catch(err => {
+ logger.error("Failed to load resources:", err);
+ this.app.backgroundResourceLoader.showLoaderError(this.dialogs, err);
+ return new Promise(() => null);
+ });
+ })
+ .then(() => {
+ this.app.backgroundResourceLoader.resourceStateChangedSignal.removeAll();
})
- .then(() => this.setStatus("Checking changelog"))
+ .then(() => this.setStatus("Checking changelog", 95))
.then(() => {
if (G_IS_DEV && globalConfig.debug.disableUpgradeNotification) {
return;
@@ -231,7 +240,7 @@ export class PreloadState extends GameState {
});
})
- .then(() => this.setStatus("Launching"))
+ .then(() => this.setStatus("Launching", 99))
.then(
() => {
this.moveToState("MainMenuState");
@@ -274,7 +283,7 @@ export class PreloadState extends GameState {
*
* @param {string} text
*/
- setStatus(text) {
+ setStatus(text, progress) {
logger.log("â
" + text);
if (G_CHINA_VERSION || G_WEGAME_VERSION) {
@@ -282,6 +291,7 @@ export class PreloadState extends GameState {
}
this.currentStatus = text;
this.statusText.innerText = text;
+ this.progressElement.style.width = 80 + (progress / 100) * 20 + "%";
return Promise.resolve();
}
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index 6afe84f1..72e4457a 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -50,6 +50,8 @@ global:
error: Error
loggingIn: Logging in
+ loadingResources: Downloading additional resources ( %)
+
# How big numbers are rendered, e.g. "10,000"
thousandsDivider: ","
@@ -443,6 +445,22 @@ dialogs:
missingMods: Missing Mods
newMods: Newly installed Mods
+ resourceLoadFailed:
+ title: Resources failed to load
+
+ demoLinkText: shapez demo on Steam
+ descWeb: >-
+ One ore more resources could not be loaded. Make sure you have a stable internet connection and try again.
+ If this still doesn't work, make sure to also disable any browser extensions (including adblockers).
+ As an alternative, you can also play the .
+
+ Error Message:
+
+ descSteamDemo: >-
+ One ore more resources could not be loaded. Try restarting the game - If that does not help, try reinstalling and verifying the game files via Steam.
+
+ Error Message:
+
ingame:
# This is shown in the top left corner and displays useful keybindings in
# every situation