1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Cleanup IS_DEMO flags and introduce Restriction Manager

This commit is contained in:
tobspr
2020-10-07 09:48:31 +02:00
parent 81e7d96dd8
commit fa27d1681f
21 changed files with 971 additions and 884 deletions

View File

@@ -1,179 +1,173 @@
import { TextualGameState } from "../core/textual_game_state";
import { SOUNDS } from "../platform/sound";
import { T } from "../translations";
import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper";
import { Dialog } from "../core/modal_dialog_elements";
import { IS_DEMO } from "../core/config";
export class KeybindingsState extends TextualGameState {
constructor() {
super("KeybindingsState");
}
getStateHeaderTitle() {
return T.keybindings.title;
}
getMainContentHTML() {
return `
<div class="topEntries">
<span class="hint">${T.keybindings.hint}</span>
<button class="styledButton resetBindings">${T.keybindings.resetKeybindings}</button>
</div>
<div class="keybindings">
</div>
`;
}
onEnter() {
const keybindingsElem = this.htmlElement.querySelector(".keybindings");
this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings);
for (const category in KEYMAPPINGS) {
const categoryDiv = document.createElement("div");
categoryDiv.classList.add("category");
keybindingsElem.appendChild(categoryDiv);
const labelDiv = document.createElement("strong");
labelDiv.innerText = T.keybindings.categoryLabels[category];
labelDiv.classList.add("categoryLabel");
categoryDiv.appendChild(labelDiv);
for (const keybindingId in KEYMAPPINGS[category]) {
const mapped = KEYMAPPINGS[category][keybindingId];
const elem = document.createElement("div");
elem.classList.add("entry");
elem.setAttribute("data-keybinding", keybindingId);
categoryDiv.appendChild(elem);
const title = document.createElement("span");
title.classList.add("title");
title.innerText = T.keybindings.mappings[keybindingId];
elem.appendChild(title);
const mappingDiv = document.createElement("span");
mappingDiv.classList.add("mapping");
elem.appendChild(mappingDiv);
const editBtn = document.createElement("button");
editBtn.classList.add("styledButton", "editKeybinding");
const resetBtn = document.createElement("button");
resetBtn.classList.add("styledButton", "resetKeybinding");
if (mapped.builtin) {
editBtn.classList.add("disabled");
resetBtn.classList.add("disabled");
} else {
this.trackClicks(editBtn, () => this.editKeybinding(keybindingId));
this.trackClicks(resetBtn, () => this.resetKeybinding(keybindingId));
}
elem.appendChild(editBtn);
elem.appendChild(resetBtn);
}
}
this.updateKeybindings();
}
editKeybinding(id) {
// if (IS_DEMO) {
// this.dialogs.showFeatureRestrictionInfo(T.demo.features.customizeKeybindings);
// return;
// }
const dialog = new Dialog({
app: this.app,
title: T.dialogs.editKeybinding.title,
contentHTML: T.dialogs.editKeybinding.desc,
buttons: ["cancel:good"],
type: "info",
});
dialog.inputReciever.keydown.add(({ keyCode, shift, alt, event }) => {
if (keyCode === 27) {
this.dialogs.closeDialog(dialog);
return;
}
if (event) {
event.preventDefault();
}
if (event.target && event.target.tagName === "BUTTON" && keyCode === 1) {
return;
}
if (
// Enter
keyCode === 13
) {
// Ignore builtins
return;
}
this.app.settings.updateKeybindingOverride(id, keyCode);
this.dialogs.closeDialog(dialog);
this.updateKeybindings();
});
dialog.inputReciever.backButton.add(() => {});
this.dialogs.internalShowDialog(dialog);
this.app.sound.playUiSound(SOUNDS.dialogOk);
}
updateKeybindings() {
const overrides = this.app.settings.getKeybindingOverrides();
for (const category in KEYMAPPINGS) {
for (const keybindingId in KEYMAPPINGS[category]) {
const mapped = KEYMAPPINGS[category][keybindingId];
const container = this.htmlElement.querySelector("[data-keybinding='" + keybindingId + "']");
assert(container, "Container for keybinding not found: " + keybindingId);
let keyCode = mapped.keyCode;
if (overrides[keybindingId]) {
keyCode = overrides[keybindingId];
}
const mappingDiv = container.querySelector(".mapping");
mappingDiv.innerHTML = getStringForKeyCode(keyCode);
mappingDiv.classList.toggle("changed", !!overrides[keybindingId]);
const resetBtn = container.querySelector("button.resetKeybinding");
resetBtn.classList.toggle("disabled", mapped.builtin || !overrides[keybindingId]);
}
}
}
resetKeybinding(id) {
this.app.settings.resetKeybindingOverride(id);
this.updateKeybindings();
}
resetBindings() {
const { reset } = this.dialogs.showWarning(
T.dialogs.resetKeybindingsConfirmation.title,
T.dialogs.resetKeybindingsConfirmation.desc,
["cancel:good", "reset:bad"]
);
reset.add(() => {
this.app.settings.resetKeybindingOverrides();
this.updateKeybindings();
this.dialogs.showInfo(T.dialogs.keybindingsResetOk.title, T.dialogs.keybindingsResetOk.desc);
});
}
getDefaultPreviousState() {
return "SettingsState";
}
}
import { Dialog } from "../core/modal_dialog_elements";
import { TextualGameState } from "../core/textual_game_state";
import { getStringForKeyCode, KEYMAPPINGS } from "../game/key_action_mapper";
import { SOUNDS } from "../platform/sound";
import { T } from "../translations";
export class KeybindingsState extends TextualGameState {
constructor() {
super("KeybindingsState");
}
getStateHeaderTitle() {
return T.keybindings.title;
}
getMainContentHTML() {
return `
<div class="topEntries">
<span class="hint">${T.keybindings.hint}</span>
<button class="styledButton resetBindings">${T.keybindings.resetKeybindings}</button>
</div>
<div class="keybindings">
</div>
`;
}
onEnter() {
const keybindingsElem = this.htmlElement.querySelector(".keybindings");
this.trackClicks(this.htmlElement.querySelector(".resetBindings"), this.resetBindings);
for (const category in KEYMAPPINGS) {
const categoryDiv = document.createElement("div");
categoryDiv.classList.add("category");
keybindingsElem.appendChild(categoryDiv);
const labelDiv = document.createElement("strong");
labelDiv.innerText = T.keybindings.categoryLabels[category];
labelDiv.classList.add("categoryLabel");
categoryDiv.appendChild(labelDiv);
for (const keybindingId in KEYMAPPINGS[category]) {
const mapped = KEYMAPPINGS[category][keybindingId];
const elem = document.createElement("div");
elem.classList.add("entry");
elem.setAttribute("data-keybinding", keybindingId);
categoryDiv.appendChild(elem);
const title = document.createElement("span");
title.classList.add("title");
title.innerText = T.keybindings.mappings[keybindingId];
elem.appendChild(title);
const mappingDiv = document.createElement("span");
mappingDiv.classList.add("mapping");
elem.appendChild(mappingDiv);
const editBtn = document.createElement("button");
editBtn.classList.add("styledButton", "editKeybinding");
const resetBtn = document.createElement("button");
resetBtn.classList.add("styledButton", "resetKeybinding");
if (mapped.builtin) {
editBtn.classList.add("disabled");
resetBtn.classList.add("disabled");
} else {
this.trackClicks(editBtn, () => this.editKeybinding(keybindingId));
this.trackClicks(resetBtn, () => this.resetKeybinding(keybindingId));
}
elem.appendChild(editBtn);
elem.appendChild(resetBtn);
}
}
this.updateKeybindings();
}
editKeybinding(id) {
const dialog = new Dialog({
app: this.app,
title: T.dialogs.editKeybinding.title,
contentHTML: T.dialogs.editKeybinding.desc,
buttons: ["cancel:good"],
type: "info",
});
dialog.inputReciever.keydown.add(({ keyCode, shift, alt, event }) => {
if (keyCode === 27) {
this.dialogs.closeDialog(dialog);
return;
}
if (event) {
event.preventDefault();
}
if (event.target && event.target.tagName === "BUTTON" && keyCode === 1) {
return;
}
if (
// Enter
keyCode === 13
) {
// Ignore builtins
return;
}
this.app.settings.updateKeybindingOverride(id, keyCode);
this.dialogs.closeDialog(dialog);
this.updateKeybindings();
});
dialog.inputReciever.backButton.add(() => {});
this.dialogs.internalShowDialog(dialog);
this.app.sound.playUiSound(SOUNDS.dialogOk);
}
updateKeybindings() {
const overrides = this.app.settings.getKeybindingOverrides();
for (const category in KEYMAPPINGS) {
for (const keybindingId in KEYMAPPINGS[category]) {
const mapped = KEYMAPPINGS[category][keybindingId];
const container = this.htmlElement.querySelector("[data-keybinding='" + keybindingId + "']");
assert(container, "Container for keybinding not found: " + keybindingId);
let keyCode = mapped.keyCode;
if (overrides[keybindingId]) {
keyCode = overrides[keybindingId];
}
const mappingDiv = container.querySelector(".mapping");
mappingDiv.innerHTML = getStringForKeyCode(keyCode);
mappingDiv.classList.toggle("changed", !!overrides[keybindingId]);
const resetBtn = container.querySelector("button.resetKeybinding");
resetBtn.classList.toggle("disabled", mapped.builtin || !overrides[keybindingId]);
}
}
}
resetKeybinding(id) {
this.app.settings.resetKeybindingOverride(id);
this.updateKeybindings();
}
resetBindings() {
const { reset } = this.dialogs.showWarning(
T.dialogs.resetKeybindingsConfirmation.title,
T.dialogs.resetKeybindingsConfirmation.desc,
["cancel:good", "reset:bad"]
);
reset.add(() => {
this.app.settings.resetKeybindingOverrides();
this.updateKeybindings();
this.dialogs.showInfo(T.dialogs.keybindingsResetOk.title, T.dialogs.keybindingsResetOk.desc);
});
}
getDefaultPreviousState() {
return "SettingsState";
}
}

View File

@@ -1,21 +1,23 @@
import { GameState } from "../core/game_state";
import { cachebust } from "../core/cachebust";
import { A_B_TESTING_LINK_TYPE, globalConfig, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
import { A_B_TESTING_LINK_TYPE, globalConfig, 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 {
makeDiv,
makeButtonElement,
formatSecondsToTimeAgo,
waitNextFrame,
generateFileDownload,
isSupportedBrowser,
makeButton,
makeButtonElement,
makeDiv,
removeAllChildren,
startFileChoose,
waitNextFrame,
} from "../core/utils";
import { ReadWriteProxy } from "../core/read_write_proxy";
import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs";
import { T } from "../translations";
import { getApplicationSettingById } from "../profile/application_settings";
import { FormElementInput } from "../core/modal_dialog_forms";
import { DialogWithForm } from "../core/modal_dialog_elements";
import { T } from "../translations";
const trim = require("trim");
@@ -24,23 +26,6 @@ const trim = require("trim");
* @typedef {import("../profile/setting_types").EnumSetting} EnumSetting
*/
/**
* Generates a file download
* @param {string} filename
* @param {string} text
*/
function generateFileDownload(filename, text) {
var element = document.createElement("a");
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
export class MainMenuState extends GameState {
constructor() {
super("MainMenuState");
@@ -49,18 +34,16 @@ export class MainMenuState extends GameState {
getInnerHTML() {
const bannerHtml = `
<h3>${T.demoBanners.title}</h3>
<p>${T.demoBanners.intro}</p>
<a href="#" class="steamLink ${A_B_TESTING_LINK_TYPE}" target="_blank">Get the shapez.io standalone!</a>
`;
return `
const showDemoBadges = this.app.restrictionMgr.getIsStandaloneMarketingActive();
return `
<div class="topButtons">
<button class="languageChoose" data-languageicon="${this.app.settings.getLanguage()}"></button>
<button class="settingsButton"></button>
${
G_IS_STANDALONE || G_IS_DEV
? `
@@ -74,17 +57,14 @@ export class MainMenuState extends GameState {
<source src="${cachebust("res/bg_render.webm")}" type="video/webm">
</video>
<div class="logo">
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
<span class="updateLabel">Wires update!</span>
</div>
<div class="mainWrapper ${IS_DEMO ? "demo" : "noDemo"}">
<div class="mainWrapper ${showDemoBadges ? "demo" : "noDemo"}">
<div class="sideContainer">
${IS_DEMO ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
${showDemoBadges ? `<div class="standaloneBanner">${bannerHtml}</div>` : ""}
</div>
<div class="mainContainer">
@@ -95,12 +75,9 @@ export class MainMenuState extends GameState {
}
<div class="buttons"></div>
</div>
</div>
<div class="footer">
<a class="githubLink boxLink" target="_blank">
${T.mainMenu.openSourceHint}
<span class="thirdpartyLogo githubLogo"></span>
@@ -123,32 +100,29 @@ export class MainMenuState extends GameState {
"<author-link>",
'<a class="producerLink" target="_blank">Tobias Springer</a>'
)}</div>
</div>
`;
}
/**
* Asks the user to import a savegame
*/
requestImportSavegame() {
if (
IS_DEMO &&
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
!this.app.platformWrapper.getHasUnlimitedSavegames()
!this.app.restrictionMgr.getHasUnlimitedSavegames()
) {
this.app.analytics.trackUiClick("importgame_slot_limit_show");
this.showSavegameSlotLimit();
return;
}
var input = document.createElement("input");
input.type = "file";
input.accept = ".bin";
input.onchange = e => {
const file = input.files[0];
// Create a 'fake' file-input to accept savegames
startFileChoose(".bin").then(file => {
if (file) {
const closeLoader = this.dialogs.showLoadingDialog();
waitNextFrame().then(() => {
this.app.analytics.trackUiClick("import_savegame");
const closeLoader = this.dialogs.showLoadingDialog();
const reader = new FileReader();
reader.addEventListener("load", event => {
const contents = event.target.result;
@@ -194,8 +168,7 @@ export class MainMenuState extends GameState {
reader.readAsText(file, "utf-8");
});
}
};
input.click();
});
}
onBackButton() {
@@ -557,9 +530,8 @@ export class MainMenuState extends GameState {
onPlayButtonClicked() {
if (
IS_DEMO &&
this.app.savegameMgr.getSavegamesMetaData().length > 0 &&
!this.app.platformWrapper.getHasUnlimitedSavegames()
!this.app.restrictionMgr.getHasUnlimitedSavegames()
) {
this.app.analytics.trackUiClick("startgame_slot_limit_show");
this.showSavegameSlotLimit();

View File

@@ -1,169 +1,169 @@
import { TextualGameState } from "../core/textual_game_state";
import { formatSecondsToTimeAgo } from "../core/utils";
import { allApplicationSettings, enumCategories } from "../profile/application_settings";
import { T } from "../translations";
export class SettingsState extends TextualGameState {
constructor() {
super("SettingsState");
}
getStateHeaderTitle() {
return T.settings.title;
}
getMainContentHTML() {
return `
<div class="sidebar">
${this.getCategoryButtonsHtml()}
${
this.app.platformWrapper.getSupportsKeyboard()
? `
<button class="styledButton categoryButton editKeybindings">
${T.keybindings.title}
</button>`
: ""
}
<div class="other">
<button class="styledButton about">${T.about.title}</button>
<div class="versionbar">
<div class="buildVersion">${T.global.loading} ...</div>
</div>
</div>
</div>
<div class="categoryContainer">
${this.getSettingsHtml()}
</div>
`;
}
getCategoryButtonsHtml() {
return Object.keys(enumCategories)
.map(key => enumCategories[key])
.map(
category =>
`
<button class="styledButton categoryButton" data-category-btn="${category}">
${T.settings.categories[category]}
</button>
`
)
.join("");
}
getSettingsHtml() {
const categoriesHTML = {};
Object.keys(enumCategories).forEach(key => {
const catName = enumCategories[key];
categoriesHTML[catName] = `<div class="category" data-category="${catName}">`;
});
for (let i = 0; i < allApplicationSettings.length; ++i) {
const setting = allApplicationSettings[i];
categoriesHTML[setting.categoryId] += setting.getHtml();
}
return Object.keys(categoriesHTML)
.map(k => categoriesHTML[k] + "</div>")
.join("");
}
renderBuildText() {
const labelVersion = this.htmlElement.querySelector(".buildVersion");
const lastBuildMs = new Date().getTime() - G_BUILD_TIME;
const lastBuildText = formatSecondsToTimeAgo(lastBuildMs / 1000.0);
const version = T.settings.versionBadges[G_APP_ENVIRONMENT];
labelVersion.innerHTML = `
<span class='version'>
${G_BUILD_VERSION} @ ${version} @ ${G_BUILD_COMMIT_HASH}
</span>
<span class='buildTime'>
${T.settings.buildDate.replace("<at-date>", lastBuildText)}<br />
</span>`;
}
onEnter(payload) {
this.renderBuildText();
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
preventDefault: false,
});
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
if (keybindingsButton) {
this.trackClicks(keybindingsButton, this.onKeybindingsClicked, { preventDefault: false });
}
this.initSettings();
this.initCategoryButtons();
this.htmlElement.querySelector(".category").classList.add("active");
this.htmlElement.querySelector(".categoryButton").classList.add("active");
}
setActiveCategory(category) {
const previousCategory = this.htmlElement.querySelector(".category.active");
const previousCategoryButton = this.htmlElement.querySelector(".categoryButton.active");
if (previousCategory.getAttribute("data-category") == category) {
return;
}
previousCategory.classList.remove("active");
previousCategoryButton.classList.remove("active");
const newCategory = this.htmlElement.querySelector("[data-category='" + category + "']");
const newCategoryButton = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
newCategory.classList.add("active");
newCategoryButton.classList.add("active");
}
initSettings() {
allApplicationSettings.forEach(setting => {
/** @type {HTMLElement} */
const element = this.htmlElement.querySelector("[data-setting='" + setting.id + "']");
setting.bind(this.app, element, this.dialogs);
setting.syncValueToElement();
this.trackClicks(
element,
() => {
setting.modify();
},
{ preventDefault: false }
);
});
}
initCategoryButtons() {
Object.keys(enumCategories).forEach(key => {
const category = enumCategories[key];
const button = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
this.trackClicks(
button,
() => {
this.setActiveCategory(category);
},
{ preventDefault: false }
);
});
}
onAboutClicked() {
this.moveToStateAddGoBack("AboutState");
}
onKeybindingsClicked() {
this.moveToStateAddGoBack("KeybindingsState");
}
}
import { TextualGameState } from "../core/textual_game_state";
import { formatSecondsToTimeAgo } from "../core/utils";
import { allApplicationSettings, enumCategories } from "../profile/application_settings";
import { T } from "../translations";
export class SettingsState extends TextualGameState {
constructor() {
super("SettingsState");
}
getStateHeaderTitle() {
return T.settings.title;
}
getMainContentHTML() {
return `
<div class="sidebar">
${this.getCategoryButtonsHtml()}
${
this.app.platformWrapper.getSupportsKeyboard()
? `
<button class="styledButton categoryButton editKeybindings">
${T.keybindings.title}
</button>`
: ""
}
<div class="other">
<button class="styledButton about">${T.about.title}</button>
<div class="versionbar">
<div class="buildVersion">${T.global.loading} ...</div>
</div>
</div>
</div>
<div class="categoryContainer">
${this.getSettingsHtml()}
</div>
`;
}
getCategoryButtonsHtml() {
return Object.keys(enumCategories)
.map(key => enumCategories[key])
.map(
category =>
`
<button class="styledButton categoryButton" data-category-btn="${category}">
${T.settings.categories[category]}
</button>
`
)
.join("");
}
getSettingsHtml() {
const categoriesHTML = {};
Object.keys(enumCategories).forEach(key => {
const catName = enumCategories[key];
categoriesHTML[catName] = `<div class="category" data-category="${catName}">`;
});
for (let i = 0; i < allApplicationSettings.length; ++i) {
const setting = allApplicationSettings[i];
categoriesHTML[setting.categoryId] += setting.getHtml(this.app);
}
return Object.keys(categoriesHTML)
.map(k => categoriesHTML[k] + "</div>")
.join("");
}
renderBuildText() {
const labelVersion = this.htmlElement.querySelector(".buildVersion");
const lastBuildMs = new Date().getTime() - G_BUILD_TIME;
const lastBuildText = formatSecondsToTimeAgo(lastBuildMs / 1000.0);
const version = T.settings.versionBadges[G_APP_ENVIRONMENT];
labelVersion.innerHTML = `
<span class='version'>
${G_BUILD_VERSION} @ ${version} @ ${G_BUILD_COMMIT_HASH}
</span>
<span class='buildTime'>
${T.settings.buildDate.replace("<at-date>", lastBuildText)}<br />
</span>`;
}
onEnter(payload) {
this.renderBuildText();
this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, {
preventDefault: false,
});
const keybindingsButton = this.htmlElement.querySelector(".editKeybindings");
if (keybindingsButton) {
this.trackClicks(keybindingsButton, this.onKeybindingsClicked, { preventDefault: false });
}
this.initSettings();
this.initCategoryButtons();
this.htmlElement.querySelector(".category").classList.add("active");
this.htmlElement.querySelector(".categoryButton").classList.add("active");
}
setActiveCategory(category) {
const previousCategory = this.htmlElement.querySelector(".category.active");
const previousCategoryButton = this.htmlElement.querySelector(".categoryButton.active");
if (previousCategory.getAttribute("data-category") == category) {
return;
}
previousCategory.classList.remove("active");
previousCategoryButton.classList.remove("active");
const newCategory = this.htmlElement.querySelector("[data-category='" + category + "']");
const newCategoryButton = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
newCategory.classList.add("active");
newCategoryButton.classList.add("active");
}
initSettings() {
allApplicationSettings.forEach(setting => {
/** @type {HTMLElement} */
const element = this.htmlElement.querySelector("[data-setting='" + setting.id + "']");
setting.bind(this.app, element, this.dialogs);
setting.syncValueToElement();
this.trackClicks(
element,
() => {
setting.modify();
},
{ preventDefault: false }
);
});
}
initCategoryButtons() {
Object.keys(enumCategories).forEach(key => {
const category = enumCategories[key];
const button = this.htmlElement.querySelector("[data-category-btn='" + category + "']");
this.trackClicks(
button,
() => {
this.setActiveCategory(category);
},
{ preventDefault: false }
);
});
}
onAboutClicked() {
this.moveToStateAddGoBack("AboutState");
}
onKeybindingsClicked() {
this.moveToStateAddGoBack("KeybindingsState");
}
}