Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 690 KiB After Width: | Height: | Size: 688 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.0 KiB |
@ -1,37 +1,46 @@
|
||||
#state_InGameState {
|
||||
.gameLoadingOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: all;
|
||||
display: flex;
|
||||
background: $mainBgColor;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#ingame_Canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
#ingame_HUD_ModalDialogs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
.gameLoadingOverlay {
|
||||
background: $darkModeGameBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
#state_InGameState {
|
||||
.gameLoadingOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: all;
|
||||
display: flex;
|
||||
background: $mainBgColor;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hint {
|
||||
position: absolute;
|
||||
@include S(bottom, 40px);
|
||||
@include S(left, 20px);
|
||||
@include S(right, 20px);
|
||||
@include PlainText;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#ingame_Canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
#ingame_HUD_ModalDialogs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
@include DarkThemeOverride {
|
||||
.gameLoadingOverlay {
|
||||
background: $darkModeGameBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,145 +1,145 @@
|
||||
#state_PreloadState {
|
||||
&.failure {
|
||||
.loadingImage,
|
||||
.loadingStatus {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.changelogDialogEntry {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
background: #eef1f4;
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: #424242;
|
||||
}
|
||||
|
||||
.version {
|
||||
@include Heading;
|
||||
}
|
||||
.date {
|
||||
@include PlainText;
|
||||
&::before {
|
||||
content: " | ";
|
||||
}
|
||||
color: #aaabaf;
|
||||
}
|
||||
|
||||
.changes {
|
||||
@include PlainText;
|
||||
@include S(padding-left, 15px);
|
||||
strong {
|
||||
background: $colorBlueBright;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
@include S(padding, 1px, 2px);
|
||||
@include S(margin-right, 3px);
|
||||
}
|
||||
a {
|
||||
color: $colorBlueBright;
|
||||
}
|
||||
li {
|
||||
@include SuperSmallText;
|
||||
@include S(margin-bottom, 10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.failureBox {
|
||||
.logo {
|
||||
img {
|
||||
@include S(width, 240px);
|
||||
}
|
||||
|
||||
@include S(margin-bottom, 30px);
|
||||
}
|
||||
|
||||
@include InlineAnimation(0.3s ease-in-out) {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.failureInner {
|
||||
// background: darken($mainBgColor, 6);
|
||||
@include S(max-width, 350px);
|
||||
margin: 0 20px;
|
||||
text-align: left;
|
||||
|
||||
@include BoxShadow3D(#fff);
|
||||
@include S(padding, 15px);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include DropShadow;
|
||||
|
||||
.errorHeader {
|
||||
color: #ef5072;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
@include PlainText;
|
||||
display: block;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
@include BreakText;
|
||||
hyphens: auto;
|
||||
// border: dotted #666;
|
||||
// @include S(border-width, 1px, 0);
|
||||
@include S(padding, 10px, 0);
|
||||
@include S(margin-top, 10px);
|
||||
}
|
||||
|
||||
.supportHelp {
|
||||
@include S(margin-top, 10px);
|
||||
@include PlainText;
|
||||
|
||||
.email {
|
||||
color: $themeColor;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.lower {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include S(margin-top, 16px);
|
||||
|
||||
i {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
color: #777;
|
||||
@include PlainText;
|
||||
}
|
||||
|
||||
button.resetApp {
|
||||
@include Button3D($colorRedBright);
|
||||
@include PlainText;
|
||||
@include S(padding, 5px, 8px, 4px);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.status {
|
||||
transform: scale(0.7) $hardwareAcc;
|
||||
opacity: 0;
|
||||
@include StateAnim(transform, opacity);
|
||||
}
|
||||
|
||||
&.arrived {
|
||||
.status {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
#state_PreloadState {
|
||||
&.failure {
|
||||
.loadingImage,
|
||||
.loadingStatus {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.changelogDialogEntry {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
background: #eef1f4;
|
||||
|
||||
@include DarkThemeOverride {
|
||||
background: #424242;
|
||||
}
|
||||
|
||||
.version {
|
||||
@include Heading;
|
||||
}
|
||||
.date {
|
||||
@include PlainText;
|
||||
&::before {
|
||||
content: " | ";
|
||||
}
|
||||
color: #aaabaf;
|
||||
}
|
||||
|
||||
.changes {
|
||||
@include PlainText;
|
||||
@include S(padding-left, 15px);
|
||||
strong {
|
||||
background: $colorBlueBright;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
@include S(padding, 1px, 2px);
|
||||
@include S(margin-right, 3px);
|
||||
}
|
||||
a {
|
||||
color: $colorBlueBright;
|
||||
}
|
||||
li {
|
||||
@include SuperSmallText;
|
||||
@include S(margin-bottom, 10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.failureBox {
|
||||
.logo {
|
||||
img {
|
||||
@include S(width, 240px);
|
||||
}
|
||||
|
||||
@include S(margin-bottom, 30px);
|
||||
}
|
||||
|
||||
@include InlineAnimation(0.3s ease-in-out) {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.failureInner {
|
||||
// background: darken($mainBgColor, 6);
|
||||
@include S(max-width, 350px);
|
||||
margin: 0 20px;
|
||||
text-align: left;
|
||||
|
||||
@include BoxShadow3D(#fff);
|
||||
@include S(padding, 15px);
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
@include DropShadow;
|
||||
|
||||
.errorHeader {
|
||||
color: #ef5072;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
@include PlainText;
|
||||
display: block;
|
||||
color: #666;
|
||||
text-align: left;
|
||||
@include BreakText;
|
||||
hyphens: auto;
|
||||
// border: dotted #666;
|
||||
// @include S(border-width, 1px, 0);
|
||||
@include S(padding, 10px, 0);
|
||||
@include S(margin-top, 10px);
|
||||
}
|
||||
|
||||
.supportHelp {
|
||||
@include S(margin-top, 10px);
|
||||
@include PlainText;
|
||||
|
||||
.email {
|
||||
color: $themeColor;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.lower {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include S(margin-top, 16px);
|
||||
|
||||
i {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
color: #777;
|
||||
@include PlainText;
|
||||
}
|
||||
|
||||
button.resetApp {
|
||||
@include Button3D($colorRedBright);
|
||||
@include PlainText;
|
||||
@include S(padding, 5px, 8px, 4px);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.status {
|
||||
transform: scale(0.7) $hardwareAcc;
|
||||
opacity: 0;
|
||||
@include StateAnim(transform, opacity);
|
||||
}
|
||||
|
||||
&.arrived {
|
||||
.status {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { randomChoice } from "../core/utils";
|
||||
import { T } from "../translations";
|
||||
|
||||
const hintsShown = [];
|
||||
|
||||
/**
|
||||
* Finds a new hint to show about the game which the user hasn't seen within this session
|
||||
*/
|
||||
export function getRandomHint() {
|
||||
let maxTries = 100 * T.tips.length;
|
||||
|
||||
while (maxTries-- > 0) {
|
||||
const hint = randomChoice(T.tips);
|
||||
if (!hintsShown.includes(hint)) {
|
||||
hintsShown.push(hint);
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
|
||||
// All tips shown so far
|
||||
return randomChoice(T.tips);
|
||||
}
|
@ -1,293 +1,331 @@
|
||||
import { GameState } from "../core/game_state";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { findNiceValue } from "../core/utils";
|
||||
import { cachebust } from "../core/cachebust";
|
||||
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
|
||||
import { T, autoDetectLanguageId, updateApplicationLanguage } from "../translations";
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { CHANGELOG } from "../changelog";
|
||||
import { globalConfig } from "../core/config";
|
||||
|
||||
const logger = createLogger("state/preload");
|
||||
|
||||
export class PreloadState extends GameState {
|
||||
constructor() {
|
||||
super("PreloadState");
|
||||
}
|
||||
|
||||
getInnerHTML() {
|
||||
return `
|
||||
<div class="loadingImage"></div>
|
||||
<div class="loadingStatus">
|
||||
<span class="desc">Booting</span>
|
||||
<span class="bar">
|
||||
<span class="inner" style="width: 0%"></span>
|
||||
<span class="status">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getThemeMusic() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getHasFadeIn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
onEnter() {
|
||||
this.htmlElement.classList.add("prefab_LoadingState");
|
||||
|
||||
const elementsToRemove = ["#loadingPreload", "#fontPreload"];
|
||||
for (let i = 0; i < elementsToRemove.length; ++i) {
|
||||
const elem = document.querySelector(elementsToRemove[i]);
|
||||
if (elem) {
|
||||
elem.remove();
|
||||
}
|
||||
}
|
||||
|
||||
this.dialogs = new HUDModalDialogs(null, this.app);
|
||||
const dialogsElement = document.body.querySelector(".modalDialogParent");
|
||||
this.dialogs.initializeToElement(dialogsElement);
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.statusText = this.htmlElement.querySelector(".loadingStatus > .desc");
|
||||
/** @type {HTMLElement} */
|
||||
this.statusBar = this.htmlElement.querySelector(".loadingStatus > .bar > .inner");
|
||||
/** @type {HTMLElement} */
|
||||
this.statusBarText = this.htmlElement.querySelector(".loadingStatus > .bar > .status");
|
||||
|
||||
this.currentStatus = "booting";
|
||||
this.currentIndex = 0;
|
||||
|
||||
this.startLoading();
|
||||
}
|
||||
|
||||
onLeave() {
|
||||
// this.dialogs.cleanup();
|
||||
}
|
||||
|
||||
startLoading() {
|
||||
this.setStatus("Booting")
|
||||
|
||||
.then(() => this.setStatus("Creating platform wrapper"))
|
||||
.then(() => this.app.platformWrapper.initialize())
|
||||
|
||||
.then(() => this.setStatus("Initializing local storage"))
|
||||
.then(() => {
|
||||
const wrapper = this.app.platformWrapper;
|
||||
if (wrapper instanceof PlatformWrapperImplBrowser) {
|
||||
try {
|
||||
window.localStorage.setItem("local_storage_test", "1");
|
||||
window.localStorage.removeItem("local_storage_test");
|
||||
} catch (ex) {
|
||||
logger.error("Failed to read/write local storage:", ex);
|
||||
return new Promise(() => {
|
||||
alert(`Your brower does not support thirdparty cookies or you have disabled it in your security settings.\n\n
|
||||
In Chrome this setting is called "Block third-party cookies and site data".\n\n
|
||||
Please allow third party cookies and then reload the page.`);
|
||||
// Never return
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Creating storage"))
|
||||
.then(() => {
|
||||
return this.app.storage.initialize();
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing libraries"))
|
||||
.then(() => this.app.analytics.initialize())
|
||||
.then(() => this.app.gameAnalytics.initialize())
|
||||
|
||||
.then(() => this.setStatus("Initializing settings"))
|
||||
.then(() => {
|
||||
return this.app.settings.initialize();
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
// Initialize fullscreen
|
||||
if (this.app.platformWrapper.getSupportsFullscreen()) {
|
||||
this.app.platformWrapper.setFullscreen(this.app.settings.getIsFullScreen());
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing language"))
|
||||
.then(() => {
|
||||
if (this.app.settings.getLanguage() === "auto-detect") {
|
||||
const language = autoDetectLanguageId();
|
||||
logger.log("Setting language to", language);
|
||||
return this.app.settings.updateLanguage(language);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const language = this.app.settings.getLanguage();
|
||||
updateApplicationLanguage(language);
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing sounds"))
|
||||
.then(() => {
|
||||
// Notice: We don't await the sounds loading itself
|
||||
return this.app.sound.initialize();
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
this.app.backgroundResourceLoader.startLoading();
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing savegame"))
|
||||
.then(() => {
|
||||
return this.app.savegameMgr.initialize().catch(err => {
|
||||
logger.error("Failed to initialize savegames:", err);
|
||||
alert(
|
||||
"Your savegames failed to load, it seems your data files got corrupted. I'm so sorry!\n\n(This can happen if your pc crashed while a game was saved).\n\nYou can try re-importing your savegames."
|
||||
);
|
||||
return this.app.savegameMgr.writeAsync();
|
||||
});
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Downloading resources"))
|
||||
.then(() => {
|
||||
return this.app.backgroundResourceLoader.getPromiseForBareGame();
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Checking changelog"))
|
||||
.then(() => {
|
||||
if (G_IS_DEV && globalConfig.debug.disableUpgradeNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.app.storage
|
||||
.readFileAsync("lastversion.bin")
|
||||
.catch(err => {
|
||||
logger.warn("Failed to read lastversion:", err);
|
||||
return G_BUILD_VERSION;
|
||||
})
|
||||
.then(version => {
|
||||
logger.log("Last version:", version, "App version:", G_BUILD_VERSION);
|
||||
this.app.storage.writeFileAsync("lastversion.bin", G_BUILD_VERSION);
|
||||
return version;
|
||||
})
|
||||
.then(version => {
|
||||
let changelogEntries = [];
|
||||
logger.log("Last seen version:", version);
|
||||
|
||||
for (let i = 0; i < CHANGELOG.length; ++i) {
|
||||
if (CHANGELOG[i].version === version) {
|
||||
break;
|
||||
}
|
||||
changelogEntries.push(CHANGELOG[i]);
|
||||
}
|
||||
if (changelogEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dialogHtml = T.dialogs.updateSummary.desc;
|
||||
for (let i = 0; i < changelogEntries.length; ++i) {
|
||||
const entry = changelogEntries[i];
|
||||
dialogHtml += `
|
||||
<div class="changelogDialogEntry">
|
||||
<span class="version">${entry.version}</span>
|
||||
<span class="date">${entry.date}</span>
|
||||
<ul class="changes">
|
||||
${entry.entries.map(text => `<li>${text}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.dialogs.showInfo(T.dialogs.updateSummary.title, dialogHtml).ok.add(resolve);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Launching"))
|
||||
.then(
|
||||
() => {
|
||||
this.moveToState("MainMenuState");
|
||||
},
|
||||
err => {
|
||||
this.showFailMessage(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setStatus(text) {
|
||||
logger.log("✅ " + text);
|
||||
this.currentIndex += 1;
|
||||
this.currentStatus = text;
|
||||
this.statusText.innerText = text;
|
||||
|
||||
const numSteps = 10; // FIXME
|
||||
|
||||
const percentage = (this.currentIndex / numSteps) * 100.0;
|
||||
this.statusBar.style.width = percentage + "%";
|
||||
this.statusBarText.innerText = findNiceValue(percentage) + "%";
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
showFailMessage(text) {
|
||||
logger.error("App init failed:", text);
|
||||
|
||||
const email = "bugs@shapez.io";
|
||||
|
||||
const subElement = document.createElement("div");
|
||||
subElement.classList.add("failureBox");
|
||||
|
||||
subElement.innerHTML = `
|
||||
<div class="logo">
|
||||
<img src="${cachebust("res/logo.png")}" alt="Shapez.io Logo">
|
||||
</div>
|
||||
<div class="failureInner">
|
||||
<div class="errorHeader">
|
||||
Failed to initialize application!
|
||||
</div>
|
||||
<div class="errorMessage">
|
||||
${this.currentStatus} failed:<br/>
|
||||
${text}
|
||||
</div>
|
||||
|
||||
<div class="supportHelp">
|
||||
Please send me an email with steps to reproduce and what you did before this happened:
|
||||
<br /><a class="email" href="mailto:${email}?subject=App%20does%20not%20launch">${email}</a>
|
||||
</div>
|
||||
|
||||
<div class="lower">
|
||||
<button class="resetApp styledButton">Reset App</button>
|
||||
<i>Build ${G_BUILD_VERSION} @ ${G_BUILD_COMMIT_HASH}</i>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.htmlElement.classList.add("failure");
|
||||
this.htmlElement.appendChild(subElement);
|
||||
|
||||
const resetBtn = subElement.querySelector("button.resetApp");
|
||||
this.trackClicks(resetBtn, this.showResetConfirm);
|
||||
}
|
||||
|
||||
showResetConfirm() {
|
||||
if (confirm("Are you sure you want to reset the app? This will delete all your savegames")) {
|
||||
this.resetApp();
|
||||
}
|
||||
}
|
||||
|
||||
resetApp() {
|
||||
this.app.settings
|
||||
.resetEverythingAsync()
|
||||
.then(() => {
|
||||
this.app.savegameMgr.resetEverythingAsync();
|
||||
})
|
||||
.then(() => {
|
||||
this.app.settings.resetEverythingAsync();
|
||||
})
|
||||
.then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
import { CHANGELOG } from "../changelog";
|
||||
import { cachebust } from "../core/cachebust";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { GameState } from "../core/game_state";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { findNiceValue } from "../core/utils";
|
||||
import { getRandomHint } from "../game/hints";
|
||||
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
|
||||
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
|
||||
import { autoDetectLanguageId, T, updateApplicationLanguage } from "../translations";
|
||||
|
||||
const logger = createLogger("state/preload");
|
||||
|
||||
export class PreloadState extends GameState {
|
||||
constructor() {
|
||||
super("PreloadState");
|
||||
}
|
||||
|
||||
getInnerHTML() {
|
||||
return `
|
||||
<div class="loadingImage"></div>
|
||||
<div class="loadingStatus">
|
||||
<span class="desc">Booting</span>
|
||||
<span class="bar">
|
||||
<span class="inner" style="width: 0%"></span>
|
||||
<span class="status">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="hint"></span>
|
||||
`;
|
||||
}
|
||||
|
||||
getThemeMusic() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getHasFadeIn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
onEnter() {
|
||||
this.htmlElement.classList.add("prefab_LoadingState");
|
||||
|
||||
const elementsToRemove = ["#loadingPreload", "#fontPreload"];
|
||||
for (let i = 0; i < elementsToRemove.length; ++i) {
|
||||
const elem = document.querySelector(elementsToRemove[i]);
|
||||
if (elem) {
|
||||
elem.remove();
|
||||
}
|
||||
}
|
||||
|
||||
this.dialogs = new HUDModalDialogs(null, this.app);
|
||||
const dialogsElement = document.body.querySelector(".modalDialogParent");
|
||||
this.dialogs.initializeToElement(dialogsElement);
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.statusText = this.htmlElement.querySelector(".loadingStatus > .desc");
|
||||
/** @type {HTMLElement} */
|
||||
this.statusBar = this.htmlElement.querySelector(".loadingStatus > .bar > .inner");
|
||||
/** @type {HTMLElement} */
|
||||
this.statusBarText = this.htmlElement.querySelector(".loadingStatus > .bar > .status");
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.hintsText = this.htmlElement.querySelector(".hint");
|
||||
this.lastHintShown = -1000;
|
||||
this.nextHintDuration = 0;
|
||||
|
||||
this.currentStatus = "booting";
|
||||
this.currentIndex = 0;
|
||||
|
||||
this.startLoading();
|
||||
}
|
||||
|
||||
onLeave() {
|
||||
// this.dialogs.cleanup();
|
||||
}
|
||||
|
||||
startLoading() {
|
||||
this.setStatus("Booting")
|
||||
|
||||
.then(() => this.setStatus("Creating platform wrapper"))
|
||||
.then(() => this.app.platformWrapper.initialize())
|
||||
|
||||
.then(() => this.setStatus("Initializing local storage"))
|
||||
.then(() => {
|
||||
const wrapper = this.app.platformWrapper;
|
||||
if (wrapper instanceof PlatformWrapperImplBrowser) {
|
||||
try {
|
||||
window.localStorage.setItem("local_storage_test", "1");
|
||||
window.localStorage.removeItem("local_storage_test");
|
||||
} catch (ex) {
|
||||
logger.error("Failed to read/write local storage:", ex);
|
||||
return new Promise(() => {
|
||||
alert(`Your brower does not support thirdparty cookies or you have disabled it in your security settings.\n\n
|
||||
In Chrome this setting is called "Block third-party cookies and site data".\n\n
|
||||
Please allow third party cookies and then reload the page.`);
|
||||
// Never return
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Creating storage"))
|
||||
.then(() => {
|
||||
return this.app.storage.initialize();
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing libraries"))
|
||||
.then(() => this.app.analytics.initialize())
|
||||
.then(() => this.app.gameAnalytics.initialize())
|
||||
|
||||
.then(() => this.setStatus("Initializing settings"))
|
||||
.then(() => {
|
||||
return this.app.settings.initialize();
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
// Initialize fullscreen
|
||||
if (this.app.platformWrapper.getSupportsFullscreen()) {
|
||||
this.app.platformWrapper.setFullscreen(this.app.settings.getIsFullScreen());
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing language"))
|
||||
.then(() => {
|
||||
if (this.app.settings.getLanguage() === "auto-detect") {
|
||||
const language = autoDetectLanguageId();
|
||||
logger.log("Setting language to", language);
|
||||
return this.app.settings.updateLanguage(language);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const language = this.app.settings.getLanguage();
|
||||
updateApplicationLanguage(language);
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing sounds"))
|
||||
.then(() => {
|
||||
// Notice: We don't await the sounds loading itself
|
||||
return this.app.sound.initialize();
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
this.app.backgroundResourceLoader.startLoading();
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Initializing savegame"))
|
||||
.then(() => {
|
||||
return this.app.savegameMgr.initialize().catch(err => {
|
||||
logger.error("Failed to initialize savegames:", err);
|
||||
alert(
|
||||
"Your savegames failed to load, it seems your data files got corrupted. I'm so sorry!\n\n(This can happen if your pc crashed while a game was saved).\n\nYou can try re-importing your savegames."
|
||||
);
|
||||
return this.app.savegameMgr.writeAsync();
|
||||
});
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Downloading resources"))
|
||||
.then(() => {
|
||||
return this.app.backgroundResourceLoader.getPromiseForBareGame();
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Checking changelog"))
|
||||
.then(() => {
|
||||
if (G_IS_DEV && globalConfig.debug.disableUpgradeNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.app.storage
|
||||
.readFileAsync("lastversion.bin")
|
||||
.catch(err => {
|
||||
logger.warn("Failed to read lastversion:", err);
|
||||
return G_BUILD_VERSION;
|
||||
})
|
||||
.then(version => {
|
||||
logger.log("Last version:", version, "App version:", G_BUILD_VERSION);
|
||||
this.app.storage.writeFileAsync("lastversion.bin", G_BUILD_VERSION);
|
||||
return version;
|
||||
})
|
||||
.then(version => {
|
||||
let changelogEntries = [];
|
||||
logger.log("Last seen version:", version);
|
||||
|
||||
for (let i = 0; i < CHANGELOG.length; ++i) {
|
||||
if (CHANGELOG[i].version === version) {
|
||||
break;
|
||||
}
|
||||
changelogEntries.push(CHANGELOG[i]);
|
||||
}
|
||||
if (changelogEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dialogHtml = T.dialogs.updateSummary.desc;
|
||||
for (let i = 0; i < changelogEntries.length; ++i) {
|
||||
const entry = changelogEntries[i];
|
||||
dialogHtml += `
|
||||
<div class="changelogDialogEntry">
|
||||
<span class="version">${entry.version}</span>
|
||||
<span class="date">${entry.date}</span>
|
||||
<ul class="changes">
|
||||
${entry.entries.map(text => `<li>${text}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.dialogs.showInfo(T.dialogs.updateSummary.title, dialogHtml).ok.add(resolve);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
.then(() => this.setStatus("Launching"))
|
||||
.then(
|
||||
() => {
|
||||
this.moveToState("MainMenuState");
|
||||
},
|
||||
err => {
|
||||
this.showFailMessage(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
update() {
|
||||
const now = performance.now();
|
||||
if (now - this.lastHintShown > this.nextHintDuration) {
|
||||
this.lastHintShown = now;
|
||||
const hintText = getRandomHint();
|
||||
|
||||
this.hintsText.innerHTML = hintText;
|
||||
|
||||
/**
|
||||
* Compute how long the user will need to read the hint.
|
||||
* We calculate with 130 words per minute, with an average of 5 chars
|
||||
* that is 650 characters / minute
|
||||
*/
|
||||
this.nextHintDuration = Math.max(2500, (hintText.length / 650) * 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
onRender() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
onBackgroundTick() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
*/
|
||||
setStatus(text) {
|
||||
logger.log("✅ " + text);
|
||||
this.currentIndex += 1;
|
||||
this.currentStatus = text;
|
||||
this.statusText.innerText = text;
|
||||
|
||||
const numSteps = 10; // FIXME
|
||||
|
||||
const percentage = (this.currentIndex / numSteps) * 100.0;
|
||||
this.statusBar.style.width = percentage + "%";
|
||||
this.statusBarText.innerText = findNiceValue(percentage) + "%";
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
showFailMessage(text) {
|
||||
logger.error("App init failed:", text);
|
||||
|
||||
const email = "bugs@shapez.io";
|
||||
|
||||
const subElement = document.createElement("div");
|
||||
subElement.classList.add("failureBox");
|
||||
|
||||
subElement.innerHTML = `
|
||||
<div class="logo">
|
||||
<img src="${cachebust("res/logo.png")}" alt="Shapez.io Logo">
|
||||
</div>
|
||||
<div class="failureInner">
|
||||
<div class="errorHeader">
|
||||
Failed to initialize application!
|
||||
</div>
|
||||
<div class="errorMessage">
|
||||
${this.currentStatus} failed:<br/>
|
||||
${text}
|
||||
</div>
|
||||
|
||||
<div class="supportHelp">
|
||||
Please send me an email with steps to reproduce and what you did before this happened:
|
||||
<br /><a class="email" href="mailto:${email}?subject=App%20does%20not%20launch">${email}</a>
|
||||
</div>
|
||||
|
||||
<div class="lower">
|
||||
<button class="resetApp styledButton">Reset App</button>
|
||||
<i>Build ${G_BUILD_VERSION} @ ${G_BUILD_COMMIT_HASH}</i>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.htmlElement.classList.add("failure");
|
||||
this.htmlElement.appendChild(subElement);
|
||||
|
||||
const resetBtn = subElement.querySelector("button.resetApp");
|
||||
this.trackClicks(resetBtn, this.showResetConfirm);
|
||||
|
||||
this.hintsText.remove();
|
||||
}
|
||||
|
||||
showResetConfirm() {
|
||||
if (confirm("Are you sure you want to reset the app? This will delete all your savegames!")) {
|
||||
this.resetApp();
|
||||
}
|
||||
}
|
||||
|
||||
resetApp() {
|
||||
this.app.settings
|
||||
.resetEverythingAsync()
|
||||
.then(() => {
|
||||
this.app.savegameMgr.resetEverythingAsync();
|
||||
})
|
||||
.then(() => {
|
||||
this.app.settings.resetEverythingAsync();
|
||||
})
|
||||
.then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|