1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2024-10-27 20:34:29 +00:00

Fix mouse panning

This commit is contained in:
tobspr 2020-09-19 20:57:20 +02:00
parent ed32238412
commit 24eb060000
7 changed files with 642 additions and 621 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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<string>} 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<string>} 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>", 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<string>} 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<string>} 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>", 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;
}
}

View File

@ -54,6 +54,10 @@ export class HUDSettingsMenu extends BaseHUDPart {
}
}
isBlockingOverlay() {
return this.visible;
}
returnToMenu() {
this.root.gameState.goBackToMenu();
}

View File

@ -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(
"<x>",
"" + 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(
"<currentMult>",
currentTierMultiplier.toString()
);
continue;
}
// Set description
handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description
.replace("<currentMult>", currentTierMultiplier.toString())
.replace("<newMult>", (currentTierMultiplier + tierHandle.improvement).toString())
// Backwards compatibility
.replace("<gain>", (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(
"<x>",
"" + 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(
"<currentMult>",
currentTierMultiplier.toString()
);
continue;
}
// Set description
handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description
.replace("<currentMult>", currentTierMultiplier.toString())
.replace("<newMult>", (currentTierMultiplier + tierHandle.improvement).toString())
// Backwards compatibility
.replace("<gain>", (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;
}
}

View File

@ -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");

View File

@ -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>",
("" + level).padStart(2, "0")
);
const rewardName = T.storyRewards[reward].title;
let html = `
<div class="rewardName">
${T.ingame.levelCompleteNotification.unlockText.replace("<reward>", rewardName)}
</div>
<div class="rewardDesc">
${T.storyRewards[reward].desc}
</div>
`;
html += "<div class='images'>";
const gained = enumHubGoalRewardsToContentUnlocked[reward];
if (gained) {
gained.forEach(([metaBuildingClass, variant]) => {
const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass);
html += `<div class="buildingExplanation" data-icon="building_tutorials/${
metaBuilding.getId() + (variant === defaultBuildingVariant ? "" : "-" + variant)
}.png"></div>`;
});
}
html += "</div>";
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>",
("" + level).padStart(2, "0")
);
const rewardName = T.storyRewards[reward].title;
let html = `
<div class="rewardName">
${T.ingame.levelCompleteNotification.unlockText.replace("<reward>", rewardName)}
</div>
<div class="rewardDesc">
${T.storyRewards[reward].desc}
</div>
`;
html += "<div class='images'>";
const gained = enumHubGoalRewardsToContentUnlocked[reward];
if (gained) {
gained.forEach(([metaBuildingClass, variant]) => {
const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass);
html += `<div class="buildingExplanation" data-icon="building_tutorials/${
metaBuilding.getId() + (variant === defaultBuildingVariant ? "" : "-" + variant)
}.png"></div>`;
});
}
html += "</div>";
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;
}
}
}