import { cachebust } from "../core/cachebust";
import { globalConfig, openStandaloneLink, THIRDPARTY_URLS } from "../core/config";
import { GameState } from "../core/game_state";
import { DialogWithForm } from "../core/modal_dialog_elements";
import { FormElementInput } from "../core/modal_dialog_forms";
import { ReadWriteProxy } from "../core/read_write_proxy";
import { STOP_PROPAGATION } from "../core/signal";
import { WEB_STEAM_SSO_AUTHENTICATED } from "../core/steam_sso";
import {
formatSecondsToTimeAgo,
generateFileDownload,
getLogoSprite,
makeButton,
makeDiv,
makeDivElement,
removeAllChildren,
startFileChoose,
waitNextFrame,
} from "../core/utils";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { MODS } from "../mods/modloader";
import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper";
import { PlatformWrapperImplElectron } from "../platform/electron/wrapper";
import { Savegame } from "../savegame/savegame";
import { T } from "../translations";
const trim = require("trim");
/**
* @typedef {import("../savegame/savegame_typedefs").SavegameMetadata} SavegameMetadata
* @typedef {import("../profile/setting_types").EnumSetting} EnumSetting
*/
export class MainMenuState extends GameState {
constructor() {
super("MainMenuState");
this.refreshInterval = null;
}
getInnerHTML() {
const showLanguageIcon = !G_CHINA_VERSION && !G_ISBN_VERSION;
const showExitAppButton = G_IS_STANDALONE;
const showPuzzleDLC =
!G_ISBN_VERSION &&
(G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED) &&
!G_IS_STEAM_DEMO &&
!G_GOG_VERSION;
const showWegameFooter = G_ISBN_VERSION;
const hasMods = MODS.anyModsActive();
const hasSteamBridge = !G_GOG_VERSION && !G_IS_STEAM_DEMO && !G_ISBN_VERSION;
let showExternalLinks = true;
if (G_IS_STANDALONE) {
if (G_ISBN_VERSION || G_CHINA_VERSION) {
showExternalLinks = false;
}
} else {
const wrapper = /** @type {PlatformWrapperImplBrowser} */ (this.app.platformWrapper);
if (!wrapper.embedProvider.externalLinks) {
showExternalLinks = false;
}
}
let showDiscordLink = showExternalLinks;
if (G_CHINA_VERSION) {
showDiscordLink = true;
}
const showDemoAdvertisement =
(showExternalLinks || G_CHINA_VERSION) &&
this.app.restrictionMgr.getIsStandaloneMarketingActive();
const ownsPuzzleDLC =
WEB_STEAM_SSO_AUTHENTICATED ||
(G_IS_STANDALONE &&
!G_IS_STEAM_DEMO &&
/** @type { PlatformWrapperImplElectron}*/ (this.app.platformWrapper).dlcs.puzzle);
const showShapez2 = showExternalLinks && MODS.mods.length === 0;
const bannerHtml = `
${Array.from(Object.entries(T.ingame.standaloneAdvantages.points))
.slice(0, 6)
.map(
([key, trans]) => `
${trans.title}
${trans.desc}
`
)
.join("")}
${
showLanguageIcon
? ``
: ""
}
${showExitAppButton ? `` : ""}
${
hasSteamBridge && (G_IS_STANDALONE || !WEB_STEAM_SSO_AUTHENTICATED)
? `
${
G_IS_STANDALONE
? T.mainMenu.playFullVersionStandalone
: T.mainMenu.playFullVersionV2
}
Sign in
`
: ""
}
${
hasSteamBridge && WEB_STEAM_SSO_AUTHENTICATED
? `
`
: ""
}
${showDemoAdvertisement ? `
${bannerHtml}
` : ""}
${
showShapez2
? `
We are currently prototyping Shapez 2!
`
: ""
}
${
showPuzzleDLC
? `
${
ownsPuzzleDLC && !hasMods
? `
`
: ""
}
${
!ownsPuzzleDLC && !hasMods
? `
${T.mainMenu.puzzleDlcText}
`
: ""
}
`
: ""
}
${
hasMods
? `
${MODS.mods
.map(mod => {
return `
${mod.metadata.name}
by ${mod.metadata.author}
`;
})
.join("")}
${T.mainMenu.mods.warningPuzzleDLC}
`
: ""
}
${
showWegameFooter
? `
`
: `
`
}
`;
}
/**
* Asks the user to import a savegame
*/
requestImportSavegame() {
if (
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
!this.app.restrictionMgr.getHasUnlimitedSavegames()
) {
this.showSavegameSlotLimit();
return;
}
this.app.gameAnalytics.note("startimport");
// Create a 'fake' file-input to accept savegames
startFileChoose(".bin").then(file => {
if (file) {
const closeLoader = this.dialogs.showLoadingDialog();
waitNextFrame().then(() => {
const reader = new FileReader();
reader.addEventListener("load", event => {
const contents = event.target.result;
let realContent;
try {
realContent = ReadWriteProxy.deserializeObject(contents);
} catch (err) {
closeLoader();
this.dialogs.showWarning(
T.dialogs.importSavegameError.title,
T.dialogs.importSavegameError.text
);
return;
}
this.app.savegameMgr.importSavegame(realContent).then(
() => {
closeLoader();
this.dialogs.showWarning(
T.dialogs.importSavegameSuccess.title,
T.dialogs.importSavegameSuccess.text
);
this.renderMainMenu();
this.renderSavegames();
},
err => {
closeLoader();
this.dialogs.showWarning(
T.dialogs.importSavegameError.title,
T.dialogs.importSavegameError.text
);
}
);
});
reader.addEventListener("error", error => {
this.dialogs.showWarning(
T.dialogs.importSavegameError.title,
T.dialogs.importSavegameError.text
);
});
reader.readAsText(file, "utf-8");
});
}
});
}
onBackButton() {
this.app.platformWrapper.exitApp();
}
onEnter(payload) {
// Start loading already
const app = this.app;
setTimeout(() => app.backgroundResourceLoader.getIngamePromise(), 10);
this.dialogs = new HUDModalDialogs(null, this.app);
const dialogsElement = document.body.querySelector(".modalDialogParent");
this.dialogs.initializeToElement(dialogsElement);
if (payload.loadError) {
this.dialogs.showWarning(T.dialogs.gameLoadFailure.title, T.dialogs.gameLoadFailure.text);
}
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
this.onPuzzleModeButtonClicked(true);
return;
}
if (G_IS_DEV && globalConfig.debug.fastGameEnter) {
const games = this.app.savegameMgr.getSavegamesMetaData();
if (games.length > 0 && globalConfig.debug.resumeGameOnFastEnter) {
this.resumeGame(games[0]);
} else {
this.onPlayButtonClicked();
}
}
// Initialize video
this.videoElement = this.htmlElement.querySelector("video");
this.videoElement.playbackRate = 0.9;
this.videoElement.addEventListener("canplay", () => {
if (this.videoElement) {
this.videoElement.classList.add("loaded");
}
});
const clickHandling = {
".settingsButton": this.onSettingsButtonClicked,
".languageChoose": this.onLanguageChooseClicked,
".redditLink": this.onRedditClicked,
".twitterLink": this.onTwitterLinkClicked,
".patreonLink": this.onPatreonLinkClicked,
".changelog": this.onChangelogClicked,
".helpTranslate": this.onTranslationHelpLinkClicked,
".exitAppButton": this.onExitAppButtonClicked,
".steamLink": this.onSteamLinkClicked,
".steamLinkSocial": this.onSteamLinkClickedSocial,
".shapez2": this.onShapez2Clicked,
".discordLink": () => {
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.discord);
},
".githubLink": () => {
this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.github);
},
".puzzleDlcPlayButton": this.onPuzzleModeButtonClicked,
".puzzleDlcGetButton": this.onPuzzleWishlistButtonClicked,
".wegameDisclaimer > .rating": this.onWegameRatingClicked,
".editMods": this.onModsClicked,
};
for (const key in clickHandling) {
const handler = clickHandling[key];
const element = this.htmlElement.querySelector(key);
if (element) {
this.trackClicks(element, handler, { preventClick: true });
}
}
this.renderMainMenu();
this.renderSavegames();
this.fetchPlayerCount();
this.refreshInterval = setInterval(() => this.fetchPlayerCount(), 10000);
this.app.gameAnalytics.noteMinor("menu.enter");
}
renderMainMenu() {
const buttonContainer = this.htmlElement.querySelector(".mainContainer .buttons");
removeAllChildren(buttonContainer);
const outerDiv = makeDivElement(null, ["outer"], null);
// Import button
this.trackClicks(
makeButton(outerDiv, ["importButton", "styledButton"], T.mainMenu.importSavegame),
this.requestImportSavegame
);
if (this.savedGames.length > 0) {
// Continue game
this.trackClicks(
makeButton(buttonContainer, ["continueButton", "styledButton"], T.mainMenu.continue),
this.onContinueButtonClicked
);
// New game
this.trackClicks(
makeButton(outerDiv, ["newGameButton", "styledButton"], T.mainMenu.newGame),
this.onPlayButtonClicked
);
} else {
// New game
this.trackClicks(
makeButton(buttonContainer, ["playButton", "styledButton"], T.mainMenu.play),
this.onPlayButtonClicked
);
}
this.htmlElement
.querySelector(".mainContainer")
.setAttribute("data-savegames", String(this.savedGames.length));
// Mods
if (!G_STEAM_ISBN_VERSION) {
this.trackClicks(
makeButton(outerDiv, ["modsButton", "styledButton"], T.mods.title),
this.onModsClicked
);
}
buttonContainer.appendChild(outerDiv);
}
fetchPlayerCount() {
const element = this.htmlElement.querySelector(".onlinePlayerCount");
if (!element) {
return;
}
fetch("https://analytics.shapez.io/v1/player-count", {
cache: "no-cache",
})
.then(res => res.json())
.then(
count => {
element.innerText = T.demoBanners.playerCount.replace("