From 24eb060000855ae5c7cb2c3219dbb5fa603665e3 Mon Sep 17 00:00:00 2001 From: tobspr Date: Sat, 19 Sep 2020 20:57:20 +0200 Subject: [PATCH] Fix mouse panning --- src/js/game/camera.js | 4 + src/js/game/hud/hud.js | 3 - src/js/game/hud/parts/modal_dialogs.js | 426 ++++++++-------- src/js/game/hud/parts/settings_menu.js | 4 + src/js/game/hud/parts/shop.js | 506 ++++++++++--------- src/js/game/hud/parts/statistics.js | 4 + src/js/game/hud/parts/unlock_notification.js | 316 ++++++------ 7 files changed, 642 insertions(+), 621 deletions(-) diff --git a/src/js/game/camera.js b/src/js/game/camera.js index a0931f5f..2be175b4 100644 --- a/src/js/game/camera.js +++ b/src/js/game/camera.js @@ -872,6 +872,10 @@ export class Camera extends BasicSerializableObject { return; } + if (this.root.hud.shouldPauseGame() || this.root.hud.hasBlockingOverlayOpen()) { + return; + } + if (this.desiredCenter || this.desiredZoom || this.currentlyMoving || this.currentlyPinching) { // Performing another method of movement right now return; diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 3edc4e17..e0ddfd9d 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -194,9 +194,6 @@ export class GameHUD { * Returns true if the rendering can be paused */ hasBlockingOverlayOpen() { - if (this.root.camera.getIsMapOverlayActive()) { - return true; - } for (const key in this.parts) { if (this.parts[key].isBlockingOverlay()) { return true; diff --git a/src/js/game/hud/parts/modal_dialogs.js b/src/js/game/hud/parts/modal_dialogs.js index 95428691..06993616 100644 --- a/src/js/game/hud/parts/modal_dialogs.js +++ b/src/js/game/hud/parts/modal_dialogs.js @@ -1,211 +1,215 @@ -/* typehints:start */ -import { Application } from "../../../application"; -/* typehints:end */ - -import { SOUNDS } from "../../../platform/sound"; -import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { BaseHUDPart } from "../base_hud_part"; -import { Dialog, DialogLoading, DialogOptionChooser } from "../../../core/modal_dialog_elements"; -import { makeDiv } from "../../../core/utils"; -import { T } from "../../../translations"; -import { THIRDPARTY_URLS } from "../../../core/config"; - -export class HUDModalDialogs extends BaseHUDPart { - constructor(root, app) { - // Important: Root is not always available here! Its also used in the main menu - super(root); - - /** @type {Application} */ - this.app = root ? root.app : app; - - this.dialogParent = null; - this.dialogStack = []; - } - - // For use inside of the game, implementation of base hud part - initialize() { - this.dialogParent = document.getElementById("ingame_HUD_ModalDialogs"); - this.domWatcher = new DynamicDomAttach(this.root, this.dialogParent); - } - - shouldPauseRendering() { - return this.dialogStack.length > 0; - } - - shouldPauseGame() { - return this.shouldPauseRendering(); - } - - createElements(parent) { - return makeDiv(parent, "ingame_HUD_ModalDialogs"); - } - - // For use outside of the game - initializeToElement(element) { - assert(element, "No element for dialogs given"); - this.dialogParent = element; - } - - // Methods - - /** - * @param {string} title - * @param {string} text - * @param {Array} buttons - */ - showInfo(title, text, buttons = ["ok:good"]) { - const dialog = new Dialog({ - app: this.app, - title: title, - contentHTML: text, - buttons: buttons, - type: "info", - }); - this.internalShowDialog(dialog); - - if (this.app) { - this.app.sound.playUiSound(SOUNDS.dialogOk); - } - - return dialog.buttonSignals; - } - - /** - * @param {string} title - * @param {string} text - * @param {Array} buttons - */ - showWarning(title, text, buttons = ["ok:good"]) { - const dialog = new Dialog({ - app: this.app, - title: title, - contentHTML: text, - buttons: buttons, - type: "warning", - }); - this.internalShowDialog(dialog); - - if (this.app) { - this.app.sound.playUiSound(SOUNDS.dialogError); - } - - return dialog.buttonSignals; - } - - /** - * @param {string} feature - * @param {string} textPrefab - */ - showFeatureRestrictionInfo(feature, textPrefab = T.dialogs.featureRestriction.desc) { - const dialog = new Dialog({ - app: this.app, - title: T.dialogs.featureRestriction.title, - contentHTML: textPrefab.replace("", feature), - buttons: ["cancel:bad", "getStandalone:good"], - type: "warning", - }); - this.internalShowDialog(dialog); - - if (this.app) { - this.app.sound.playUiSound(SOUNDS.dialogOk); - } - - this.app.analytics.trackUiClick("demo_dialog_show"); - - dialog.buttonSignals.cancel.add(() => { - this.app.analytics.trackUiClick("demo_dialog_cancel"); - }); - - dialog.buttonSignals.getStandalone.add(() => { - this.app.analytics.trackUiClick("demo_dialog_click"); - window.open(THIRDPARTY_URLS.standaloneStorePage); - }); - - return dialog.buttonSignals; - } - - showOptionChooser(title, options) { - const dialog = new DialogOptionChooser({ - app: this.app, - title, - options, - }); - this.internalShowDialog(dialog); - return dialog.buttonSignals; - } - - // Returns method to be called when laoding finishd - showLoadingDialog() { - const dialog = new DialogLoading(this.app); - this.internalShowDialog(dialog); - return this.closeDialog.bind(this, dialog); - } - - internalShowDialog(dialog) { - const elem = dialog.createElement(); - dialog.setIndex(this.dialogStack.length); - - // Hide last dialog in queue - if (this.dialogStack.length > 0) { - this.dialogStack[this.dialogStack.length - 1].hide(); - } - - this.dialogStack.push(dialog); - - // Append dialog - dialog.show(); - dialog.closeRequested.add(this.closeDialog.bind(this, dialog)); - - // Append to HTML - this.dialogParent.appendChild(elem); - - document.body.classList.toggle("modalDialogActive", this.dialogStack.length > 0); - - // IMPORTANT: Attach element directly, otherwise double submit is possible - this.update(); - } - - update() { - if (this.domWatcher) { - this.domWatcher.update(this.dialogStack.length > 0); - } - } - - closeDialog(dialog) { - dialog.destroy(); - - let index = -1; - for (let i = 0; i < this.dialogStack.length; ++i) { - if (this.dialogStack[i] === dialog) { - index = i; - break; - } - } - assert(index >= 0, "Dialog not in dialog stack"); - this.dialogStack.splice(index, 1); - - if (this.dialogStack.length > 0) { - // Show the dialog which was previously open - this.dialogStack[this.dialogStack.length - 1].show(); - } - - document.body.classList.toggle("modalDialogActive", this.dialogStack.length > 0); - } - - close() { - for (let i = 0; i < this.dialogStack.length; ++i) { - const dialog = this.dialogStack[i]; - dialog.destroy(); - } - this.dialogStack = []; - } - - cleanup() { - super.cleanup(); - for (let i = 0; i < this.dialogStack.length; ++i) { - this.dialogStack[i].destroy(); - } - this.dialogStack = []; - this.dialogParent = null; - } -} +/* typehints:start */ +import { Application } from "../../../application"; +/* typehints:end */ + +import { SOUNDS } from "../../../platform/sound"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; +import { BaseHUDPart } from "../base_hud_part"; +import { Dialog, DialogLoading, DialogOptionChooser } from "../../../core/modal_dialog_elements"; +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { THIRDPARTY_URLS } from "../../../core/config"; + +export class HUDModalDialogs extends BaseHUDPart { + constructor(root, app) { + // Important: Root is not always available here! Its also used in the main menu + super(root); + + /** @type {Application} */ + this.app = root ? root.app : app; + + this.dialogParent = null; + this.dialogStack = []; + } + + // For use inside of the game, implementation of base hud part + initialize() { + this.dialogParent = document.getElementById("ingame_HUD_ModalDialogs"); + this.domWatcher = new DynamicDomAttach(this.root, this.dialogParent); + } + + shouldPauseRendering() { + return this.dialogStack.length > 0; + } + + shouldPauseGame() { + return this.shouldPauseRendering(); + } + + createElements(parent) { + return makeDiv(parent, "ingame_HUD_ModalDialogs"); + } + + // For use outside of the game + initializeToElement(element) { + assert(element, "No element for dialogs given"); + this.dialogParent = element; + } + + isBlockingOverlay() { + return this.dialogStack.length > 0; + } + + // Methods + + /** + * @param {string} title + * @param {string} text + * @param {Array} buttons + */ + showInfo(title, text, buttons = ["ok:good"]) { + const dialog = new Dialog({ + app: this.app, + title: title, + contentHTML: text, + buttons: buttons, + type: "info", + }); + this.internalShowDialog(dialog); + + if (this.app) { + this.app.sound.playUiSound(SOUNDS.dialogOk); + } + + return dialog.buttonSignals; + } + + /** + * @param {string} title + * @param {string} text + * @param {Array} buttons + */ + showWarning(title, text, buttons = ["ok:good"]) { + const dialog = new Dialog({ + app: this.app, + title: title, + contentHTML: text, + buttons: buttons, + type: "warning", + }); + this.internalShowDialog(dialog); + + if (this.app) { + this.app.sound.playUiSound(SOUNDS.dialogError); + } + + return dialog.buttonSignals; + } + + /** + * @param {string} feature + * @param {string} textPrefab + */ + showFeatureRestrictionInfo(feature, textPrefab = T.dialogs.featureRestriction.desc) { + const dialog = new Dialog({ + app: this.app, + title: T.dialogs.featureRestriction.title, + contentHTML: textPrefab.replace("", feature), + buttons: ["cancel:bad", "getStandalone:good"], + type: "warning", + }); + this.internalShowDialog(dialog); + + if (this.app) { + this.app.sound.playUiSound(SOUNDS.dialogOk); + } + + this.app.analytics.trackUiClick("demo_dialog_show"); + + dialog.buttonSignals.cancel.add(() => { + this.app.analytics.trackUiClick("demo_dialog_cancel"); + }); + + dialog.buttonSignals.getStandalone.add(() => { + this.app.analytics.trackUiClick("demo_dialog_click"); + window.open(THIRDPARTY_URLS.standaloneStorePage); + }); + + return dialog.buttonSignals; + } + + showOptionChooser(title, options) { + const dialog = new DialogOptionChooser({ + app: this.app, + title, + options, + }); + this.internalShowDialog(dialog); + return dialog.buttonSignals; + } + + // Returns method to be called when laoding finishd + showLoadingDialog() { + const dialog = new DialogLoading(this.app); + this.internalShowDialog(dialog); + return this.closeDialog.bind(this, dialog); + } + + internalShowDialog(dialog) { + const elem = dialog.createElement(); + dialog.setIndex(this.dialogStack.length); + + // Hide last dialog in queue + if (this.dialogStack.length > 0) { + this.dialogStack[this.dialogStack.length - 1].hide(); + } + + this.dialogStack.push(dialog); + + // Append dialog + dialog.show(); + dialog.closeRequested.add(this.closeDialog.bind(this, dialog)); + + // Append to HTML + this.dialogParent.appendChild(elem); + + document.body.classList.toggle("modalDialogActive", this.dialogStack.length > 0); + + // IMPORTANT: Attach element directly, otherwise double submit is possible + this.update(); + } + + update() { + if (this.domWatcher) { + this.domWatcher.update(this.dialogStack.length > 0); + } + } + + closeDialog(dialog) { + dialog.destroy(); + + let index = -1; + for (let i = 0; i < this.dialogStack.length; ++i) { + if (this.dialogStack[i] === dialog) { + index = i; + break; + } + } + assert(index >= 0, "Dialog not in dialog stack"); + this.dialogStack.splice(index, 1); + + if (this.dialogStack.length > 0) { + // Show the dialog which was previously open + this.dialogStack[this.dialogStack.length - 1].show(); + } + + document.body.classList.toggle("modalDialogActive", this.dialogStack.length > 0); + } + + close() { + for (let i = 0; i < this.dialogStack.length; ++i) { + const dialog = this.dialogStack[i]; + dialog.destroy(); + } + this.dialogStack = []; + } + + cleanup() { + super.cleanup(); + for (let i = 0; i < this.dialogStack.length; ++i) { + this.dialogStack[i].destroy(); + } + this.dialogStack = []; + this.dialogParent = null; + } +} diff --git a/src/js/game/hud/parts/settings_menu.js b/src/js/game/hud/parts/settings_menu.js index 0cd94f79..31afe348 100644 --- a/src/js/game/hud/parts/settings_menu.js +++ b/src/js/game/hud/parts/settings_menu.js @@ -54,6 +54,10 @@ export class HUDSettingsMenu extends BaseHUDPart { } } + isBlockingOverlay() { + return this.visible; + } + returnToMenu() { this.root.gameState.goBackToMenu(); } diff --git a/src/js/game/hud/parts/shop.js b/src/js/game/hud/parts/shop.js index e2b8837b..93d11827 100644 --- a/src/js/game/hud/parts/shop.js +++ b/src/js/game/hud/parts/shop.js @@ -1,251 +1,255 @@ -import { ClickDetector } from "../../../core/click_detector"; -import { InputReceiver } from "../../../core/input_receiver"; -import { formatBigNumber, makeDiv } from "../../../core/utils"; -import { T } from "../../../translations"; -import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; -import { UPGRADES } from "../../upgrades"; -import { BaseHUDPart } from "../base_hud_part"; -import { DynamicDomAttach } from "../dynamic_dom_attach"; - -export class HUDShop extends BaseHUDPart { - createElements(parent) { - this.background = makeDiv(parent, "ingame_HUD_Shop", ["ingameDialog"]); - - // DIALOG Inner / Wrapper - this.dialogInner = makeDiv(this.background, null, ["dialogInner"]); - this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.shop.title); - this.closeButton = makeDiv(this.title, null, ["closeButton"]); - this.trackClicks(this.closeButton, this.close); - this.contentDiv = makeDiv(this.dialogInner, null, ["content"]); - - this.upgradeToElements = {}; - - // Upgrades - for (const upgradeId in UPGRADES) { - const handle = {}; - handle.requireIndexToElement = []; - - // Wrapper - handle.elem = makeDiv(this.contentDiv, null, ["upgrade"]); - handle.elem.setAttribute("data-upgrade-id", upgradeId); - - // Title - const title = makeDiv(handle.elem, null, ["title"], T.shopUpgrades[upgradeId].name); - - // Title > Tier - handle.elemTierLabel = makeDiv(title, null, ["tier"]); - - // Icon - handle.icon = makeDiv(handle.elem, null, ["icon"]); - handle.icon.setAttribute("data-icon", "upgrades/" + upgradeId + ".png"); - - // Description - handle.elemDescription = makeDiv(handle.elem, null, ["description"], "??"); - handle.elemRequirements = makeDiv(handle.elem, null, ["requirements"]); - - // Buy button - handle.buyButton = document.createElement("button"); - handle.buyButton.classList.add("buy", "styledButton"); - handle.buyButton.innerText = T.ingame.shop.buttonUnlock; - handle.elem.appendChild(handle.buyButton); - - this.trackClicks(handle.buyButton, () => this.tryUnlockNextTier(upgradeId)); - - // Assign handle - this.upgradeToElements[upgradeId] = handle; - } - } - - rerenderFull() { - for (const upgradeId in this.upgradeToElements) { - const handle = this.upgradeToElements[upgradeId]; - const { tiers } = UPGRADES[upgradeId]; - - const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId); - const currentTierMultiplier = this.root.hubGoals.upgradeImprovements[upgradeId]; - const tierHandle = tiers[currentTier]; - - // Set tier - handle.elemTierLabel.innerText = T.ingame.shop.tier.replace( - "", - "" + T.ingame.shop.tierLabels[currentTier] - ); - - handle.elemTierLabel.setAttribute("data-tier", currentTier); - - // Cleanup detectors - for (let i = 0; i < handle.requireIndexToElement.length; ++i) { - const requiredHandle = handle.requireIndexToElement[i]; - requiredHandle.container.remove(); - requiredHandle.pinDetector.cleanup(); - requiredHandle.infoDetector.cleanup(); - } - - // Cleanup - handle.requireIndexToElement = []; - - handle.elem.classList.toggle("maxLevel", !tierHandle); - - if (!tierHandle) { - // Max level - handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace( - "", - currentTierMultiplier.toString() - ); - continue; - } - - // Set description - handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description - .replace("", currentTierMultiplier.toString()) - .replace("", (currentTierMultiplier + tierHandle.improvement).toString()) - // Backwards compatibility - .replace("", (tierHandle.improvement * 100.0).toString()); - - tierHandle.required.forEach(({ shape, amount }) => { - const container = makeDiv(handle.elemRequirements, null, ["requirement"]); - - const shapeDef = this.root.shapeDefinitionMgr.getShapeFromShortKey(shape); - const shapeCanvas = shapeDef.generateAsCanvas(120); - shapeCanvas.classList.add(); - container.appendChild(shapeCanvas); - - const progressContainer = makeDiv(container, null, ["amount"]); - const progressBar = document.createElement("label"); - progressBar.classList.add("progressBar"); - progressContainer.appendChild(progressBar); - - const progressLabel = document.createElement("label"); - progressContainer.appendChild(progressLabel); - - const pinButton = document.createElement("button"); - pinButton.classList.add("pin"); - container.appendChild(pinButton); - - const viewInfoButton = document.createElement("button"); - viewInfoButton.classList.add("showInfo"); - container.appendChild(viewInfoButton); - - const currentGoalShape = this.root.hubGoals.currentGoal.definition.getHash(); - if (shape === currentGoalShape) { - pinButton.classList.add("isGoal"); - } else if (this.root.hud.parts.pinnedShapes.isShapePinned(shape)) { - pinButton.classList.add("alreadyPinned"); - } - - const pinDetector = new ClickDetector(pinButton, { - consumeEvents: true, - preventDefault: true, - }); - pinDetector.click.add(() => { - if (this.root.hud.parts.pinnedShapes.isShapePinned(shape)) { - this.root.hud.signals.shapeUnpinRequested.dispatch(shape); - pinButton.classList.add("unpinned"); - pinButton.classList.remove("pinned", "alreadyPinned"); - } else { - this.root.hud.signals.shapePinRequested.dispatch(shapeDef); - pinButton.classList.add("pinned"); - pinButton.classList.remove("unpinned"); - } - }); - - const infoDetector = new ClickDetector(viewInfoButton, { - consumeEvents: true, - preventDefault: true, - }); - infoDetector.click.add(() => - this.root.hud.signals.viewShapeDetailsRequested.dispatch(shapeDef) - ); - - handle.requireIndexToElement.push({ - container, - progressLabel, - progressBar, - definition: shapeDef, - required: amount, - pinDetector, - infoDetector, - }); - }); - } - } - - renderCountsAndStatus() { - for (const upgradeId in this.upgradeToElements) { - const handle = this.upgradeToElements[upgradeId]; - for (let i = 0; i < handle.requireIndexToElement.length; ++i) { - const { progressLabel, progressBar, definition, required } = handle.requireIndexToElement[i]; - - const haveAmount = this.root.hubGoals.getShapesStored(definition); - const progress = Math.min(haveAmount / required, 1.0); - - progressLabel.innerText = formatBigNumber(haveAmount) + " / " + formatBigNumber(required); - progressBar.style.width = progress * 100.0 + "%"; - progressBar.classList.toggle("complete", progress >= 1.0); - } - - handle.buyButton.classList.toggle("buyable", this.root.hubGoals.canUnlockUpgrade(upgradeId)); - } - } - - initialize() { - this.domAttach = new DynamicDomAttach(this.root, this.background, { - attachClass: "visible", - }); - - this.inputReciever = new InputReceiver("shop"); - this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); - - this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); - this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuClose).add(this.close, this); - this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuOpenShop).add(this.close, this); - - this.close(); - - this.rerenderFull(); - this.root.signals.upgradePurchased.add(this.rerenderFull, this); - } - - cleanup() { - document.body.classList.remove("ingameDialogOpen"); - - // Cleanup detectors - for (const upgradeId in this.upgradeToElements) { - const handle = this.upgradeToElements[upgradeId]; - for (let i = 0; i < handle.requireIndexToElement.length; ++i) { - const requiredHandle = handle.requireIndexToElement[i]; - requiredHandle.container.remove(); - requiredHandle.pinDetector.cleanup(); - requiredHandle.infoDetector.cleanup(); - } - handle.requireIndexToElement = []; - } - } - - show() { - this.visible = true; - document.body.classList.add("ingameDialogOpen"); - // this.background.classList.add("visible"); - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); - this.rerenderFull(); - } - - close() { - this.visible = false; - document.body.classList.remove("ingameDialogOpen"); - this.root.app.inputMgr.makeSureDetached(this.inputReciever); - this.update(); - } - - update() { - this.domAttach.update(this.visible); - if (this.visible) { - this.renderCountsAndStatus(); - } - } - - tryUnlockNextTier(upgradeId) { - // Nothing - this.root.hubGoals.tryUnlockUpgrade(upgradeId); - } -} +import { ClickDetector } from "../../../core/click_detector"; +import { InputReceiver } from "../../../core/input_receiver"; +import { formatBigNumber, makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; +import { UPGRADES } from "../../upgrades"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; + +export class HUDShop extends BaseHUDPart { + createElements(parent) { + this.background = makeDiv(parent, "ingame_HUD_Shop", ["ingameDialog"]); + + // DIALOG Inner / Wrapper + this.dialogInner = makeDiv(this.background, null, ["dialogInner"]); + this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.shop.title); + this.closeButton = makeDiv(this.title, null, ["closeButton"]); + this.trackClicks(this.closeButton, this.close); + this.contentDiv = makeDiv(this.dialogInner, null, ["content"]); + + this.upgradeToElements = {}; + + // Upgrades + for (const upgradeId in UPGRADES) { + const handle = {}; + handle.requireIndexToElement = []; + + // Wrapper + handle.elem = makeDiv(this.contentDiv, null, ["upgrade"]); + handle.elem.setAttribute("data-upgrade-id", upgradeId); + + // Title + const title = makeDiv(handle.elem, null, ["title"], T.shopUpgrades[upgradeId].name); + + // Title > Tier + handle.elemTierLabel = makeDiv(title, null, ["tier"]); + + // Icon + handle.icon = makeDiv(handle.elem, null, ["icon"]); + handle.icon.setAttribute("data-icon", "upgrades/" + upgradeId + ".png"); + + // Description + handle.elemDescription = makeDiv(handle.elem, null, ["description"], "??"); + handle.elemRequirements = makeDiv(handle.elem, null, ["requirements"]); + + // Buy button + handle.buyButton = document.createElement("button"); + handle.buyButton.classList.add("buy", "styledButton"); + handle.buyButton.innerText = T.ingame.shop.buttonUnlock; + handle.elem.appendChild(handle.buyButton); + + this.trackClicks(handle.buyButton, () => this.tryUnlockNextTier(upgradeId)); + + // Assign handle + this.upgradeToElements[upgradeId] = handle; + } + } + + rerenderFull() { + for (const upgradeId in this.upgradeToElements) { + const handle = this.upgradeToElements[upgradeId]; + const { tiers } = UPGRADES[upgradeId]; + + const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId); + const currentTierMultiplier = this.root.hubGoals.upgradeImprovements[upgradeId]; + const tierHandle = tiers[currentTier]; + + // Set tier + handle.elemTierLabel.innerText = T.ingame.shop.tier.replace( + "", + "" + T.ingame.shop.tierLabels[currentTier] + ); + + handle.elemTierLabel.setAttribute("data-tier", currentTier); + + // Cleanup detectors + for (let i = 0; i < handle.requireIndexToElement.length; ++i) { + const requiredHandle = handle.requireIndexToElement[i]; + requiredHandle.container.remove(); + requiredHandle.pinDetector.cleanup(); + requiredHandle.infoDetector.cleanup(); + } + + // Cleanup + handle.requireIndexToElement = []; + + handle.elem.classList.toggle("maxLevel", !tierHandle); + + if (!tierHandle) { + // Max level + handle.elemDescription.innerText = T.ingame.shop.maximumLevel.replace( + "", + currentTierMultiplier.toString() + ); + continue; + } + + // Set description + handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description + .replace("", currentTierMultiplier.toString()) + .replace("", (currentTierMultiplier + tierHandle.improvement).toString()) + // Backwards compatibility + .replace("", (tierHandle.improvement * 100.0).toString()); + + tierHandle.required.forEach(({ shape, amount }) => { + const container = makeDiv(handle.elemRequirements, null, ["requirement"]); + + const shapeDef = this.root.shapeDefinitionMgr.getShapeFromShortKey(shape); + const shapeCanvas = shapeDef.generateAsCanvas(120); + shapeCanvas.classList.add(); + container.appendChild(shapeCanvas); + + const progressContainer = makeDiv(container, null, ["amount"]); + const progressBar = document.createElement("label"); + progressBar.classList.add("progressBar"); + progressContainer.appendChild(progressBar); + + const progressLabel = document.createElement("label"); + progressContainer.appendChild(progressLabel); + + const pinButton = document.createElement("button"); + pinButton.classList.add("pin"); + container.appendChild(pinButton); + + const viewInfoButton = document.createElement("button"); + viewInfoButton.classList.add("showInfo"); + container.appendChild(viewInfoButton); + + const currentGoalShape = this.root.hubGoals.currentGoal.definition.getHash(); + if (shape === currentGoalShape) { + pinButton.classList.add("isGoal"); + } else if (this.root.hud.parts.pinnedShapes.isShapePinned(shape)) { + pinButton.classList.add("alreadyPinned"); + } + + const pinDetector = new ClickDetector(pinButton, { + consumeEvents: true, + preventDefault: true, + }); + pinDetector.click.add(() => { + if (this.root.hud.parts.pinnedShapes.isShapePinned(shape)) { + this.root.hud.signals.shapeUnpinRequested.dispatch(shape); + pinButton.classList.add("unpinned"); + pinButton.classList.remove("pinned", "alreadyPinned"); + } else { + this.root.hud.signals.shapePinRequested.dispatch(shapeDef); + pinButton.classList.add("pinned"); + pinButton.classList.remove("unpinned"); + } + }); + + const infoDetector = new ClickDetector(viewInfoButton, { + consumeEvents: true, + preventDefault: true, + }); + infoDetector.click.add(() => + this.root.hud.signals.viewShapeDetailsRequested.dispatch(shapeDef) + ); + + handle.requireIndexToElement.push({ + container, + progressLabel, + progressBar, + definition: shapeDef, + required: amount, + pinDetector, + infoDetector, + }); + }); + } + } + + renderCountsAndStatus() { + for (const upgradeId in this.upgradeToElements) { + const handle = this.upgradeToElements[upgradeId]; + for (let i = 0; i < handle.requireIndexToElement.length; ++i) { + const { progressLabel, progressBar, definition, required } = handle.requireIndexToElement[i]; + + const haveAmount = this.root.hubGoals.getShapesStored(definition); + const progress = Math.min(haveAmount / required, 1.0); + + progressLabel.innerText = formatBigNumber(haveAmount) + " / " + formatBigNumber(required); + progressBar.style.width = progress * 100.0 + "%"; + progressBar.classList.toggle("complete", progress >= 1.0); + } + + handle.buyButton.classList.toggle("buyable", this.root.hubGoals.canUnlockUpgrade(upgradeId)); + } + } + + initialize() { + this.domAttach = new DynamicDomAttach(this.root, this.background, { + attachClass: "visible", + }); + + this.inputReciever = new InputReceiver("shop"); + this.keyActionMapper = new KeyActionMapper(this.root, this.inputReciever); + + this.keyActionMapper.getBinding(KEYMAPPINGS.general.back).add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuClose).add(this.close, this); + this.keyActionMapper.getBinding(KEYMAPPINGS.ingame.menuOpenShop).add(this.close, this); + + this.close(); + + this.rerenderFull(); + this.root.signals.upgradePurchased.add(this.rerenderFull, this); + } + + cleanup() { + document.body.classList.remove("ingameDialogOpen"); + + // Cleanup detectors + for (const upgradeId in this.upgradeToElements) { + const handle = this.upgradeToElements[upgradeId]; + for (let i = 0; i < handle.requireIndexToElement.length; ++i) { + const requiredHandle = handle.requireIndexToElement[i]; + requiredHandle.container.remove(); + requiredHandle.pinDetector.cleanup(); + requiredHandle.infoDetector.cleanup(); + } + handle.requireIndexToElement = []; + } + } + + show() { + this.visible = true; + document.body.classList.add("ingameDialogOpen"); + // this.background.classList.add("visible"); + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.rerenderFull(); + } + + close() { + this.visible = false; + document.body.classList.remove("ingameDialogOpen"); + this.root.app.inputMgr.makeSureDetached(this.inputReciever); + this.update(); + } + + update() { + this.domAttach.update(this.visible); + if (this.visible) { + this.renderCountsAndStatus(); + } + } + + tryUnlockNextTier(upgradeId) { + // Nothing + this.root.hubGoals.tryUnlockUpgrade(upgradeId); + } + + isBlockingOverlay() { + return this.visible; + } +} diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index 4e8cc9d1..910c49d0 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -155,6 +155,10 @@ export class HUDStatistics extends BaseHUDPart { document.body.classList.remove("ingameDialogOpen"); } + isBlockingOverlay() { + return this.visible; + } + show() { this.visible = true; document.body.classList.add("ingameDialogOpen"); diff --git a/src/js/game/hud/parts/unlock_notification.js b/src/js/game/hud/parts/unlock_notification.js index 7a5c923b..d88e2dbb 100644 --- a/src/js/game/hud/parts/unlock_notification.js +++ b/src/js/game/hud/parts/unlock_notification.js @@ -1,156 +1,160 @@ -import { globalConfig } from "../../../core/config"; -import { gMetaBuildingRegistry } from "../../../core/global_registries"; -import { makeDiv } from "../../../core/utils"; -import { SOUNDS } from "../../../platform/sound"; -import { T } from "../../../translations"; -import { defaultBuildingVariant } from "../../meta_building"; -import { enumHubGoalRewards } from "../../tutorial_goals"; -import { BaseHUDPart } from "../base_hud_part"; -import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings"; -import { InputReceiver } from "../../../core/input_receiver"; - -export class HUDUnlockNotification extends BaseHUDPart { - initialize() { - this.visible = false; - - this.domAttach = new DynamicDomAttach(this.root, this.element, { - timeToKeepSeconds: 0, - }); - - if (!(G_IS_DEV && globalConfig.debug.disableUnlockDialog)) { - this.root.signals.storyGoalCompleted.add(this.showForLevel, this); - } - - this.buttonShowTimeout = null; - } - - createElements(parent) { - this.inputReciever = new InputReceiver("unlock-notification"); - - this.element = makeDiv(parent, "ingame_HUD_UnlockNotification", ["noBlur"]); - - const dialog = makeDiv(this.element, null, ["dialog"]); - - this.elemTitle = makeDiv(dialog, null, ["title"]); - this.elemSubTitle = makeDiv(dialog, null, ["subTitle"], T.ingame.levelCompleteNotification.completed); - - this.elemContents = makeDiv(dialog, null, ["contents"]); - - this.btnClose = document.createElement("button"); - this.btnClose.classList.add("close", "styledButton"); - this.btnClose.innerText = T.ingame.levelCompleteNotification.buttonNextLevel; - dialog.appendChild(this.btnClose); - - this.trackClicks(this.btnClose, this.requestClose); - } - - /** - * @param {number} level - * @param {enumHubGoalRewards} reward - */ - showForLevel(level, reward) { - this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); - this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace( - "", - ("" + level).padStart(2, "0") - ); - - const rewardName = T.storyRewards[reward].title; - - let html = ` -
- ${T.ingame.levelCompleteNotification.unlockText.replace("", rewardName)} -
- -
- ${T.storyRewards[reward].desc} -
- - `; - - html += "
"; - const gained = enumHubGoalRewardsToContentUnlocked[reward]; - if (gained) { - gained.forEach(([metaBuildingClass, variant]) => { - const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass); - html += `
`; - }); - } - html += "
"; - - this.elemContents.innerHTML = html; - this.visible = true; - this.root.soundProxy.playUi(SOUNDS.levelComplete); - - if (this.buttonShowTimeout) { - clearTimeout(this.buttonShowTimeout); - } - - this.element.querySelector("button.close").classList.remove("unlocked"); - - if (this.root.app.settings.getAllSettings().offerHints) { - this.buttonShowTimeout = setTimeout( - () => this.element.querySelector("button.close").classList.add("unlocked"), - G_IS_DEV ? 100 : 5000 - ); - } else { - this.element.querySelector("button.close").classList.add("unlocked"); - } - } - - cleanup() { - this.root.app.inputMgr.makeSureDetached(this.inputReciever); - if (this.buttonShowTimeout) { - clearTimeout(this.buttonShowTimeout); - this.buttonShowTimeout = null; - } - } - - requestClose() { - this.root.app.adProvider.showVideoAd().then(() => { - this.close(); - - if (!this.root.app.settings.getAllSettings().offerHints) { - return; - } - - if (this.root.hubGoals.level === 3) { - const { showUpgrades } = this.root.hud.parts.dialogs.showInfo( - T.dialogs.upgradesIntroduction.title, - T.dialogs.upgradesIntroduction.desc, - ["showUpgrades:good:timeout"] - ); - showUpgrades.add(() => this.root.hud.parts.shop.show()); - } - - if (this.root.hubGoals.level === 5) { - const { showKeybindings } = this.root.hud.parts.dialogs.showInfo( - T.dialogs.keybindingsIntroduction.title, - T.dialogs.keybindingsIntroduction.desc, - ["showKeybindings:misc", "ok:good:timeout"] - ); - showKeybindings.add(() => this.root.gameState.goToKeybindings()); - } - }); - } - - close() { - this.root.app.inputMgr.makeSureDetached(this.inputReciever); - if (this.buttonShowTimeout) { - clearTimeout(this.buttonShowTimeout); - this.buttonShowTimeout = null; - } - this.visible = false; - } - - update() { - this.domAttach.update(this.visible); - if (!this.visible && this.buttonShowTimeout) { - clearTimeout(this.buttonShowTimeout); - this.buttonShowTimeout = null; - } - } -} +import { globalConfig } from "../../../core/config"; +import { gMetaBuildingRegistry } from "../../../core/global_registries"; +import { makeDiv } from "../../../core/utils"; +import { SOUNDS } from "../../../platform/sound"; +import { T } from "../../../translations"; +import { defaultBuildingVariant } from "../../meta_building"; +import { enumHubGoalRewards } from "../../tutorial_goals"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; +import { enumHubGoalRewardsToContentUnlocked } from "../../tutorial_goals_mappings"; +import { InputReceiver } from "../../../core/input_receiver"; + +export class HUDUnlockNotification extends BaseHUDPart { + initialize() { + this.visible = false; + + this.domAttach = new DynamicDomAttach(this.root, this.element, { + timeToKeepSeconds: 0, + }); + + if (!(G_IS_DEV && globalConfig.debug.disableUnlockDialog)) { + this.root.signals.storyGoalCompleted.add(this.showForLevel, this); + } + + this.buttonShowTimeout = null; + } + + createElements(parent) { + this.inputReciever = new InputReceiver("unlock-notification"); + + this.element = makeDiv(parent, "ingame_HUD_UnlockNotification", ["noBlur"]); + + const dialog = makeDiv(this.element, null, ["dialog"]); + + this.elemTitle = makeDiv(dialog, null, ["title"]); + this.elemSubTitle = makeDiv(dialog, null, ["subTitle"], T.ingame.levelCompleteNotification.completed); + + this.elemContents = makeDiv(dialog, null, ["contents"]); + + this.btnClose = document.createElement("button"); + this.btnClose.classList.add("close", "styledButton"); + this.btnClose.innerText = T.ingame.levelCompleteNotification.buttonNextLevel; + dialog.appendChild(this.btnClose); + + this.trackClicks(this.btnClose, this.requestClose); + } + + /** + * @param {number} level + * @param {enumHubGoalRewards} reward + */ + showForLevel(level, reward) { + this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); + this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace( + "", + ("" + level).padStart(2, "0") + ); + + const rewardName = T.storyRewards[reward].title; + + let html = ` +
+ ${T.ingame.levelCompleteNotification.unlockText.replace("", rewardName)} +
+ +
+ ${T.storyRewards[reward].desc} +
+ + `; + + html += "
"; + const gained = enumHubGoalRewardsToContentUnlocked[reward]; + if (gained) { + gained.forEach(([metaBuildingClass, variant]) => { + const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass); + html += `
`; + }); + } + html += "
"; + + this.elemContents.innerHTML = html; + this.visible = true; + this.root.soundProxy.playUi(SOUNDS.levelComplete); + + if (this.buttonShowTimeout) { + clearTimeout(this.buttonShowTimeout); + } + + this.element.querySelector("button.close").classList.remove("unlocked"); + + if (this.root.app.settings.getAllSettings().offerHints) { + this.buttonShowTimeout = setTimeout( + () => this.element.querySelector("button.close").classList.add("unlocked"), + G_IS_DEV ? 100 : 5000 + ); + } else { + this.element.querySelector("button.close").classList.add("unlocked"); + } + } + + cleanup() { + this.root.app.inputMgr.makeSureDetached(this.inputReciever); + if (this.buttonShowTimeout) { + clearTimeout(this.buttonShowTimeout); + this.buttonShowTimeout = null; + } + } + + isBlockingOverlay() { + return this.visible; + } + + requestClose() { + this.root.app.adProvider.showVideoAd().then(() => { + this.close(); + + if (!this.root.app.settings.getAllSettings().offerHints) { + return; + } + + if (this.root.hubGoals.level === 3) { + const { showUpgrades } = this.root.hud.parts.dialogs.showInfo( + T.dialogs.upgradesIntroduction.title, + T.dialogs.upgradesIntroduction.desc, + ["showUpgrades:good:timeout"] + ); + showUpgrades.add(() => this.root.hud.parts.shop.show()); + } + + if (this.root.hubGoals.level === 5) { + const { showKeybindings } = this.root.hud.parts.dialogs.showInfo( + T.dialogs.keybindingsIntroduction.title, + T.dialogs.keybindingsIntroduction.desc, + ["showKeybindings:misc", "ok:good:timeout"] + ); + showKeybindings.add(() => this.root.gameState.goToKeybindings()); + } + }); + } + + close() { + this.root.app.inputMgr.makeSureDetached(this.inputReciever); + if (this.buttonShowTimeout) { + clearTimeout(this.buttonShowTimeout); + this.buttonShowTimeout = null; + } + this.visible = false; + } + + update() { + this.domAttach.update(this.visible); + if (!this.visible && this.buttonShowTimeout) { + clearTimeout(this.buttonShowTimeout); + this.buttonShowTimeout = null; + } + } +}