From 38970141d893843c057b7fe7b12a272b9166808a Mon Sep 17 00:00:00 2001 From: tobspr Date: Thu, 14 May 2020 13:29:42 +0200 Subject: [PATCH] Allow pinning shapes --- res/ui/icons/current_goal_marker.png | Bin 0 -> 878 bytes res/ui/icons/pin.png | Bin 0 -> 1664 bytes res/ui/icons/unpin.png | Bin 0 -> 854 bytes src/css/ingame_hud/blur_overlay.scss | 8 -- src/css/ingame_hud/keybindings_overlay.scss | 2 +- src/css/ingame_hud/pinned_shapes.scss | 54 +++++++++ src/css/ingame_hud/shop.scss | 44 +++++++ src/css/main.scss | 20 ++- src/js/game/hub_goals.js | 11 +- src/js/game/hud/hud.js | 5 + src/js/game/hud/parts/pinned_shapes.js | 128 ++++++++++++++++++++ src/js/game/hud/parts/shop.js | 49 +++++++- src/js/game/hud/parts/statistics.js | 6 +- 13 files changed, 303 insertions(+), 24 deletions(-) create mode 100644 res/ui/icons/current_goal_marker.png create mode 100644 res/ui/icons/pin.png create mode 100644 res/ui/icons/unpin.png delete mode 100644 src/css/ingame_hud/blur_overlay.scss create mode 100644 src/css/ingame_hud/pinned_shapes.scss create mode 100644 src/js/game/hud/parts/pinned_shapes.js diff --git a/res/ui/icons/current_goal_marker.png b/res/ui/icons/current_goal_marker.png new file mode 100644 index 0000000000000000000000000000000000000000..0ebd6c0deeed17105a8ee257ba50d3144c9cd9fd GIT binary patch literal 878 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10;z@u{h%1l=0uvK!Gcz-D zb8}-83n0?Bv@|xhFfy?KN*SA)0~sK3V+#idhtSYaFdMAW*a9YN2^IwD0wIuMh*3as z5Cf>u1jsP9G&Thq3sGzgGS<{WSJ%b}qy{K!VPaxwVBi1}H?{<_A$oxtjZ8tt8X1|J znp#-`-3C-(0#s~nYzktVf`Fx|sfD4Lm4StY0mK)^Hb7CJEYJ(*pL~`8QeGuNe!&b( z>S{74Pe^ax$e4BNY&i?__lk3wC7sWnb{F0MRQ&nw``;Y?QSTOQW!<(waK^&()!@YXCxU{t# zt4y53fljSsN6#KUeVjj$o1;mDgUg}fkJF(wN-;c(6bublWEBdyGo6~qQn*B7iQ^QW zE2*0V3_GfyI>=PAu?j3*!6F#KRv4m?qv|T)KGl)eWu@~GlN$~)a=aY=!bdJ9T?k_j zvP#f4DcO6#fqzX?p!|s^1`L~c8hOG*mDQ4k^)(DklZ}j?GaB(6bc?Kb$dZ=8I&Ft& zM5Abg_M=JL8>`NVO=q&sm~iuP&;j<)1MeDQ?;R8rOY}Pz(a8Pr<%v)^?SfYAg2V6F z%y%#`JLY_P$Hrl8qqC90|I?+v4h#&nr!%|H&Z#s527zjcYeY#(Vo9o1a#1RfVlXl= zGSD?J)ipE;F*L9;HMBCd)HX1%GBDVm%xH+BAvZrIGp!Q0hI4j1dx07xK{f>ErERK(!v>gTe~DWM4fiV6fA literal 0 HcmV?d00001 diff --git a/res/ui/icons/pin.png b/res/ui/icons/pin.png new file mode 100644 index 0000000000000000000000000000000000000000..768599f39aa9b5781f06aadd50751d498c2974d8 GIT binary patch literal 1664 zcmZ`(dpML?96m#{QwG^55wTs|HuISIX1@8%jO!?tS(lJ`B$9DmxnD9OH7q5&T|DVR z)K=?~T)I67VKa&@+RP&gNeOF<-G-D>d(LP7*+2Gq&Ueo5{LcG(fA4v}@A(dTdN^yS z>8k+%G(;|r-f$J7XQ?WjOX@RU!(~Z?gS!I&SB}s>NvLqYHpIo-9e^}b0A$$!%)^vy z0)Sl@0FyxgSe*huH@>*h%LV|-XHPdDCkVhWA&1~|xO|+;!_h1tXBhBtf}F_!0=QfO zkH_P1V1VL90Ec427we=#NZD|QJFeV*&Pl=QV&8x02*9AgTa8U$t|=8Wnd%HOym_TTuj0|bQ=l7$x~QD z@S*we6bgW+XppZAZQ;Fe+2d<6!V|wM@1L}WjC#OFi9g~d{PpRukiZ_?$wU)WgfdN{XKn{co)AU05s+3p&Vyuu7*ikvdGB&fcg-|<(MO6sB5+X01&w6UE=Hw~Yp`TydpkuM?PBoKS+29}F zt|4v9=*VcYS8z9ME_-EK=+Rx@Rj9NpvBv9{$uiXpa-0;#r$Z$Lq-trNvuKk{ zT%k>58odL~BdhS8Oq;z~;s8}{sBQF-s|zE>#XU6l@=RArnez=qI*7K?}qAYpAjKPkULkHzSvY}pO3%!cDGc)z^rePDy z1ihMmYZu>7XvvrZeDh{()eb^R8ok_f}}B@Mq}T93OTzzpyDAGP|#_Pv5BAMiDSy zA-0J~mrtw`h~8k5!mGD;gh~r_8r4}J18c-?y-T`2H8lZ*z)s>2v+J;P+~h z?6fsGRFWKK6_OYR7r?|)PlxfX2p$8&tT3!K*I4qOf|$6_$nf3&U(nrK xJOm5$P=_sX;mJ}-Vi=H0rOe3KsH6}{d>At>G2--;jXneck&}mG&Bmate*i!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10Vs(H|h%1mbH#avjwJ?NMQuIx`(`M8vL_paIk3@%1(PZ!4!i_@*aFRPji1Y%f|MFj&}gtQ!W*XFL>DtGsL z{pO;a29FCro%hXEym^y(fz$f@8O+DnmKnZrl=fe27$y-tS^CyIqmTo0A}5zWzj5f$ z>Yq#drC3hCs@I*b`J(Tpaals1c#~$?A-#&rA$GNQ_k*Vq5Vn!AG`94`R{M~_sdCkRnY$z-nA?nR?NJWCONZA>Gt2~ zt=5l@aGRGuE#W`2Qf6ndxcoF__P7Plo4aLRuMQK8d42SQSnj`?Mz);iX9?W=Y2h<=5vLQG>t)x7$ mD3zhSyj(9cFS|H7u^?41zbJk7I~ysWA_h-aKbLh*2~7YCyC~uS literal 0 HcmV?d00001 diff --git a/src/css/ingame_hud/blur_overlay.scss b/src/css/ingame_hud/blur_overlay.scss deleted file mode 100644 index d821b6b8..00000000 --- a/src/css/ingame_hud/blur_overlay.scss +++ /dev/null @@ -1,8 +0,0 @@ -body.ingameDialogOpen { - #ingame_Canvas, - #ingame_HUD_GameMenu, - #ingame_HUD_KeybindingOverlay, - #ingame_HUD_buildings_toolbar { - filter: blur(5px); - } -} diff --git a/src/css/ingame_hud/keybindings_overlay.scss b/src/css/ingame_hud/keybindings_overlay.scss index aa6248a0..14f28b2b 100644 --- a/src/css/ingame_hud/keybindings_overlay.scss +++ b/src/css/ingame_hud/keybindings_overlay.scss @@ -20,7 +20,7 @@ @include S(height, 10px); width: 1px; @include S(margin, 0, 3px); - background-color: #ccc; + background-color: #888; transform: rotate(10deg); // @include S(margin, 0, 3px); } diff --git a/src/css/ingame_hud/pinned_shapes.scss b/src/css/ingame_hud/pinned_shapes.scss new file mode 100644 index 00000000..bf4da985 --- /dev/null +++ b/src/css/ingame_hud/pinned_shapes.scss @@ -0,0 +1,54 @@ +#ingame_HUD_PinnedShapes { + position: absolute; + @include S(left, 9px); + @include S(top, 120px); + @include PlainText; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + + > .shape { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + @include S(margin-bottom, 5px); + + &.unpinable { + > canvas { + cursor: pointer; + pointer-events: all; + } + } + + > canvas { + @include S(width, 25px); + @include S(height, 25px); + } + + > .amountLabel { + @include S(margin-left, 5px); + @include SuperSmallText; + font-weight: bold; + display: inline-flex; + align-items: center; + flex-direction: row; + } + + &.marked .amountLabel { + &::after { + content: " "; + position: absolute; + display: inline-block; + @include S(width, 9px); + @include S(height, 9px); + opacity: 0.8; + @include S(top, -4px); + @include S(left, -4px); + background: uiResource("icons/current_goal_marker.png") center center / contain no-repeat; + } + } + } +} diff --git a/src/css/ingame_hud/shop.scss b/src/css/ingame_hud/shop.scss index 74be3d46..3cb2d31d 100644 --- a/src/css/ingame_hud/shop.scss +++ b/src/css/ingame_hud/shop.scss @@ -100,6 +100,50 @@ flex-direction: column; align-items: center; + button.pin { + @include S(width, 12px); + @include S(height, 12px); + background: uiResource("icons/pin.png") center center / 95% no-repeat; + position: absolute; + @include S(top, -2px); + @include S(right, -2px); + opacity: 0.6; + cursor: pointer; + pointer-events: all; + @include IncreasedClickArea(5px); + transition: opacity 0.12s ease-in-out; + &:hover { + opacity: 0.7; + } + + &.alreadyPinned { + opacity: 0.1 !important; + pointer-events: none; + cursor: default; + } + + &.pinned { + opacity: 0.1; + pointer-events: none; + cursor: default; + @include InlineAnimation(0.3s ease-in-out) { + 0% { + opacity: 1; + transform: scale(0.8); + } + + 30% { + opacity: 1; + transform: scale(1.2); + } + + 100% { + transform: scale(1); + } + } + } + } + canvas { @include S(width, 40px); @include S(height, 40px); diff --git a/src/css/main.scss b/src/css/main.scss index 46107002..9b2bbe54 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -31,16 +31,16 @@ @import "ingame_hud/unlock_notification"; @import "ingame_hud/shop"; @import "ingame_hud/game_menu"; -@import "ingame_hud/blur_overlay"; @import "ingame_hud/dialogs"; @import "ingame_hud/mass_selector"; @import "ingame_hud/vignette_overlay"; @import "ingame_hud/statistics"; +@import "ingame_hud/pinned_shapes"; // Z-Index -$elements: ingame_Canvas, ingame_VignetteOverlay, ingame_HUD_building_placer, ingame_HUD_buildings_toolbar, - ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, ingame_HUD_Shop, ingame_HUD_Statistics, - ingame_HUD_BetaOverlay, ingame_HUD_MassSelector, ingame_HUD_UnlockNotification; +$elements: ingame_Canvas, ingame_VignetteOverlay, ingame_HUD_building_placer, ingame_HUD_PinnedShapes, + ingame_HUD_buildings_toolbar, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, ingame_HUD_Shop, + ingame_HUD_Statistics, ingame_HUD_BetaOverlay, ingame_HUD_MassSelector, ingame_HUD_UnlockNotification; $zindex: 100; @@ -56,7 +56,17 @@ body.uiHidden { #ingame_HUD_buildings_toolbar, #ingame_HUD_building_placer, #ingame_HUD_GameMenu, - #ingame_HUD_MassSelector { + #ingame_HUD_MassSelector, + #ingame_HUD_PinnedShapes { display: none !important; } } +body.ingameDialogOpen { + #ingame_Canvas, + #ingame_HUD_GameMenu, + #ingame_HUD_KeybindingOverlay, + #ingame_HUD_buildings_toolbar, + #ingame_HUD_PinnedShapes { + filter: blur(5px); + } +} diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index c6d10b6b..15a1f9d1 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -75,6 +75,14 @@ export class HubGoals extends BasicSerializableObject { getShapesStored(definition) { return this.storedShapes[definition.getHash()] || 0; } + /** + * Returns how much of the current shape is stored + * @param {string} key + * @returns {number} + */ + getShapesStoredByKey(key) { + return this.storedShapes[key] || 0; + } /** * Returns how much of the current goal was already delivered @@ -158,11 +166,12 @@ export class HubGoals extends BasicSerializableObject { onGoalCompleted() { const reward = this.currentGoal.reward; this.gainedRewards[reward] = (this.gainedRewards[reward] || 0) + 1; - this.root.signals.storyGoalCompleted.dispatch(this.level, reward); this.root.app.gameAnalytics.handleLevelCompleted(this.level); ++this.level; this.createNextGoal(); + + this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward); } /** diff --git a/src/js/game/hud/hud.js b/src/js/game/hud/hud.js index 1cb7d048..e1588181 100644 --- a/src/js/game/hud/hud.js +++ b/src/js/game/hud/hud.js @@ -17,6 +17,8 @@ import { HUDMassSelector } from "./parts/mass_selector"; import { HUDVignetteOverlay } from "./parts/vignette_overlay"; import { HUDStatistics } from "./parts/statistics"; import { MetaBuilding } from "../meta_building"; +import { HUDPinnedShapes } from "./parts/pinned_shapes"; +import { ShapeDefinition } from "../shape_definition"; export class GameHUD { /** @@ -47,11 +49,14 @@ export class GameHUD { vignetteOverlay: new HUDVignetteOverlay(this.root), + pinnedShapes: new HUDPinnedShapes(this.root), + // betaOverlay: new HUDBetaOverlay(this.root), }; this.signals = { selectedPlacementBuildingChanged: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()), + shapePinRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()), }; if (!IS_MOBILE) { diff --git a/src/js/game/hud/parts/pinned_shapes.js b/src/js/game/hud/parts/pinned_shapes.js new file mode 100644 index 00000000..883025b8 --- /dev/null +++ b/src/js/game/hud/parts/pinned_shapes.js @@ -0,0 +1,128 @@ +import { BaseHUDPart } from "../base_hud_part"; +import { makeDiv, removeAllChildren, formatBigNumber } from "../../../core/utils"; +import { ClickDetector } from "../../../core/click_detector"; +import { ShapeDefinition } from "../../shape_definition"; + +export class HUDPinnedShapes extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []); + } + + initialize() { + this.pinnedShapes = []; + + /** @type {Array<{key: string, amountLabel: HTMLElement, lastRenderedValue: number, element: HTMLElement, detector?: ClickDetector}>} */ + this.handles = []; + this.rerenderFull(); + + this.root.signals.storyGoalCompleted.add(this.rerenderFull, this); + this.root.hud.signals.shapePinRequested.add(this.pinNewShape, this); + } + + /** + * Returns whether a given shape is pinned + * @param {string} key + */ + isShapePinned(key) { + if (!this.pinnedShapes) { + return false; + } + return ( + this.pinnedShapes.indexOf(key) >= 0 || key === this.root.hubGoals.currentGoal.definition.getHash() + ); + } + + rerenderFull() { + const currentGoal = this.root.hubGoals.currentGoal.definition.getHash(); + + // First, remove old ones + for (let i = 0; i < this.handles.length; ++i) { + this.handles[i].element.remove(); + const detector = this.handles[i].detector; + if (detector) { + detector.cleanup(); + } + } + this.handles = []; + + this.internalPinShape(currentGoal, false); + + for (let i = 0; i < this.pinnedShapes.length; ++i) { + const key = this.pinnedShapes[i]; + if (key !== currentGoal) { + this.internalPinShape(key); + } + } + } + + /** + * Pins a shape + * @param {string} key + * @param {boolean} canUnpin + */ + internalPinShape(key, canUnpin = true) { + const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key); + + const element = makeDiv(this.element, null, ["shape"]); + const canvas = definition.generateAsCanvas(120); + element.appendChild(canvas); + + let detector = null; + if (canUnpin) { + element.classList.add("unpinable"); + detector = new ClickDetector(element, { + consumeEvents: true, + preventDefault: true, + }); + detector.click.add(() => this.unpinShape(key)); + } else { + element.classList.add("marked"); + } + + const amountLabel = makeDiv(element, null, ["amountLabel"], "123"); + + this.handles.push({ + key, + element, + amountLabel, + lastRenderedValue: -1, + }); + } + + update() { + for (let i = 0; i < this.handles.length; ++i) { + const handle = this.handles[i]; + + const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key); + if (currentValue !== handle.lastRenderedValue) { + handle.lastRenderedValue = currentValue; + handle.amountLabel.innerText = formatBigNumber(currentValue); + } + } + } + + unpinShape(key) { + const index = this.pinnedShapes.indexOf(key); + if (index >= 0) { + const key = this.pinnedShapes[index]; + this.pinnedShapes.splice(index, 1); + this.rerenderFull(); + } + } + + /** + * @param {ShapeDefinition} definition + */ + pinNewShape(definition) { + const key = definition.getHash(); + if (key === this.root.hubGoals.currentGoal.definition.getHash()) { + // Can not pin current goal + return; + } + if (this.pinnedShapes.indexOf(key) < 0) { + // Pin + this.pinnedShapes.push(key); + this.rerenderFull(); + } + } +} diff --git a/src/js/game/hud/parts/shop.js b/src/js/game/hud/parts/shop.js index d1c30f17..a272d499 100644 --- a/src/js/game/hud/parts/shop.js +++ b/src/js/game/hud/parts/shop.js @@ -6,6 +6,7 @@ import { DynamicDomAttach } from "../dynamic_dom_attach"; import { InputReceiver } from "../../../core/input_receiver"; import { KeyActionMapper } from "../../key_action_mapper"; import { Math_min } from "../../../core/builtins"; +import { ClickDetector } from "../../../core/click_detector"; export class HUDShop extends BaseHUDPart { createElements(parent) { @@ -61,7 +62,6 @@ export class HUDShop extends BaseHUDPart { for (const upgradeId in this.upgradeToElements) { const handle = this.upgradeToElements[upgradeId]; const { description, tiers } = UPGRADES[upgradeId]; - // removeAllChildren(handle.elem); const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId); const tierHandle = tiers[currentTier]; @@ -70,9 +70,15 @@ export class HUDShop extends BaseHUDPart { handle.elemTierLabel.innerText = "Tier " + TIER_LABELS[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(); + } + // Cleanup handle.requireIndexToElement = []; - removeAllChildren(handle.elemRequirements); handle.elem.classList.toggle("maxLevel", !tierHandle); @@ -86,14 +92,14 @@ export class HUDShop extends BaseHUDPart { handle.elemDescription.innerText = description(tierHandle.improvement); tierHandle.required.forEach(({ shape, amount }) => { - const requireDiv = makeDiv(handle.elemRequirements, null, ["requirement"]); + const container = makeDiv(handle.elemRequirements, null, ["requirement"]); const shapeDef = this.root.shapeDefinitionMgr.getShapeFromShortKey(shape); const shapeCanvas = shapeDef.generateAsCanvas(120); shapeCanvas.classList.add(); - requireDiv.appendChild(shapeCanvas); + container.appendChild(shapeCanvas); - const progressContainer = makeDiv(requireDiv, null, ["amount"]); + const progressContainer = makeDiv(container, null, ["amount"]); const progressBar = document.createElement("label"); progressBar.classList.add("progressBar"); progressContainer.appendChild(progressBar); @@ -101,11 +107,31 @@ export class HUDShop extends BaseHUDPart { const progressLabel = document.createElement("label"); progressContainer.appendChild(progressLabel); + const pinButton = document.createElement("button"); + pinButton.classList.add("pin"); + container.appendChild(pinButton); + + if (this.root.hud.parts.pinnedShapes.isShapePinned(shape)) { + console.log("ALREADY PINNED:", shape); + pinButton.classList.add("alreadyPinned"); + } + + const pinDetector = new ClickDetector(pinButton, { + consumeEvents: true, + preventDefault: true, + }); + pinDetector.click.add(() => { + this.root.hud.signals.shapePinRequested.dispatch(shapeDef); + pinButton.classList.add("pinned"); + }); + handle.requireIndexToElement.push({ + container, progressLabel, progressBar, definition: shapeDef, required: amount, + pinDetector, }); }); } @@ -148,6 +174,17 @@ export class HUDShop extends BaseHUDPart { 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(); + } + handle.requireIndexToElement = []; + } } show() { @@ -155,7 +192,7 @@ export class HUDShop extends BaseHUDPart { document.body.classList.add("ingameDialogOpen"); // this.background.classList.add("visible"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); - this.update(); + this.rerenderFull(); } close() { diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index 78a29373..688ac63c 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -153,6 +153,9 @@ export class HUDStatistics extends BaseHUDPart { * Performs a full rerender, regenerating everything */ rerenderFull() { + for (const key in this.activeHandles) { + this.activeHandles[key].detach(); + } removeAllChildren(this.contentDiv); // Now, attach new ones @@ -170,8 +173,6 @@ export class HUDStatistics extends BaseHUDPart { } } - // const entries = Object.entries(this.root.hubGoals.storedShapes); - entries.sort((a, b) => b[1] - a[1]); let rendered = new Set(); @@ -179,7 +180,6 @@ export class HUDStatistics extends BaseHUDPart { for (let i = 0; i < Math_min(entries.length, 200); ++i) { const entry = entries[i]; const shapeKey = entry[0]; - const amount = entry[1]; let handle = this.activeHandles[shapeKey]; if (!handle) {