mirror of
https://github.com/tobspr/shapez.io.git
synced 2026-03-02 03:39:21 +00:00
Rebalance the whole game, rename splitter -> balancer
This commit is contained in:
@@ -8,7 +8,7 @@ import { MetaMixerBuilding } from "../../buildings/mixer";
|
||||
import { MetaPainterBuilding } from "../../buildings/painter";
|
||||
import { MetaReaderBuilding } from "../../buildings/reader";
|
||||
import { MetaRotaterBuilding } from "../../buildings/rotater";
|
||||
import { MetaSplitterBuilding } from "../../buildings/splitter";
|
||||
import { MetaBalancerBuilding } from "../../buildings/balancer";
|
||||
import { MetaStackerBuilding } from "../../buildings/stacker";
|
||||
import { MetaTrashBuilding } from "../../buildings/trash";
|
||||
import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt";
|
||||
@@ -16,7 +16,7 @@ import { HUDBaseToolbar } from "./base_toolbar";
|
||||
|
||||
const supportedBuildings = [
|
||||
MetaBeltBuilding,
|
||||
MetaSplitterBuilding,
|
||||
MetaBalancerBuilding,
|
||||
MetaUndergroundBeltBuilding,
|
||||
MetaMinerBuilding,
|
||||
MetaCutterBuilding,
|
||||
|
||||
@@ -1,291 +1,291 @@
|
||||
import { ClickDetector } from "../../../core/click_detector";
|
||||
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
|
||||
import { ShapeDefinition } from "../../shape_definition";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { blueprintShape, UPGRADES } from "../../upgrades";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
|
||||
/**
|
||||
* Manages the pinned shapes on the left side of the screen
|
||||
*/
|
||||
export class HUDPinnedShapes extends BaseHUDPart {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
/**
|
||||
* Store a list of pinned shapes
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.pinnedShapes = [];
|
||||
|
||||
/**
|
||||
* Store handles to the currently rendered elements, so we can update them more
|
||||
* convenient. Also allows for cleaning up handles.
|
||||
* @type {Array<{
|
||||
* key: string,
|
||||
* amountLabel: HTMLElement,
|
||||
* lastRenderedValue: string,
|
||||
* element: HTMLElement,
|
||||
* detector?: ClickDetector,
|
||||
* infoDetector?: ClickDetector
|
||||
* }>}
|
||||
*/
|
||||
this.handles = [];
|
||||
}
|
||||
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the pinned shapes
|
||||
*/
|
||||
serialize() {
|
||||
return {
|
||||
shapes: this.pinnedShapes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the pinned shapes
|
||||
* @param {{ shapes: Array<string>}} data
|
||||
*/
|
||||
deserialize(data) {
|
||||
if (!data || !data.shapes || !Array.isArray(data.shapes)) {
|
||||
return "Invalid pinned shapes data";
|
||||
}
|
||||
this.pinnedShapes = data.shapes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the hud component
|
||||
*/
|
||||
initialize() {
|
||||
// Connect to any relevant signals
|
||||
this.root.signals.storyGoalCompleted.add(this.rerenderFull, this);
|
||||
this.root.signals.upgradePurchased.add(this.updateShapesAfterUpgrade, this);
|
||||
this.root.signals.postLoadHook.add(this.rerenderFull, this);
|
||||
this.root.hud.signals.shapePinRequested.add(this.pinNewShape, this);
|
||||
this.root.hud.signals.shapeUnpinRequested.add(this.unpinShape, this);
|
||||
|
||||
// Perform initial render
|
||||
this.updateShapesAfterUpgrade();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all shapes after an upgrade has been purchased and removes the unused ones
|
||||
*/
|
||||
updateShapesAfterUpgrade() {
|
||||
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
||||
const key = this.pinnedShapes[i];
|
||||
if (key === blueprintShape) {
|
||||
// Ignore blueprint shapes
|
||||
continue;
|
||||
}
|
||||
let goal = this.findGoalValueForShape(key);
|
||||
if (!goal) {
|
||||
// Seems no longer relevant
|
||||
this.pinnedShapes.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.rerenderFull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the current goal for the given key. If the key is the story goal, returns
|
||||
* the story goal. If its the blueprint shape, no goal is returned. Otherwise
|
||||
* it's searched for upgrades.
|
||||
* @param {string} key
|
||||
*/
|
||||
findGoalValueForShape(key) {
|
||||
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
|
||||
return this.root.hubGoals.currentGoal.required;
|
||||
}
|
||||
if (key === blueprintShape) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if this shape is required for any upgrade
|
||||
for (const upgradeId in UPGRADES) {
|
||||
const { tiers } = UPGRADES[upgradeId];
|
||||
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
||||
const tierHandle = tiers[currentTier];
|
||||
|
||||
if (!tierHandle) {
|
||||
// Max level
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tierHandle.required.length; ++i) {
|
||||
const { shape, amount } = tierHandle.required[i];
|
||||
if (shape === key) {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given shape is currently pinned
|
||||
* @param {string} key
|
||||
*/
|
||||
isShapePinned(key) {
|
||||
if (key === this.root.hubGoals.currentGoal.definition.getHash() || key === blueprintShape) {
|
||||
// This is a "special" shape which is always pinned
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.pinnedShapes.indexOf(key) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rerenders the whole component
|
||||
*/
|
||||
rerenderFull() {
|
||||
const currentGoal = this.root.hubGoals.currentGoal;
|
||||
const currentKey = currentGoal.definition.getHash();
|
||||
|
||||
// First, remove all old shapes
|
||||
for (let i = 0; i < this.handles.length; ++i) {
|
||||
this.handles[i].element.remove();
|
||||
const detector = this.handles[i].detector;
|
||||
if (detector) {
|
||||
detector.cleanup();
|
||||
}
|
||||
const infoDetector = this.handles[i].infoDetector;
|
||||
if (infoDetector) {
|
||||
infoDetector.cleanup();
|
||||
}
|
||||
}
|
||||
this.handles = [];
|
||||
|
||||
// Pin story goal
|
||||
this.internalPinShape(currentKey, false, "goal");
|
||||
|
||||
// Pin blueprint shape as well
|
||||
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
|
||||
this.internalPinShape(blueprintShape, false, "blueprint");
|
||||
}
|
||||
|
||||
// Pin manually pinned shapes
|
||||
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
||||
const key = this.pinnedShapes[i];
|
||||
if (key !== currentKey) {
|
||||
this.internalPinShape(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins a new shape
|
||||
* @param {string} key
|
||||
* @param {boolean} canUnpin
|
||||
* @param {string=} className
|
||||
*/
|
||||
internalPinShape(key, canUnpin = true, className = null) {
|
||||
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
|
||||
|
||||
const element = makeDiv(this.element, null, ["shape"]);
|
||||
const canvas = definition.generateAsCanvas(120);
|
||||
element.appendChild(canvas);
|
||||
|
||||
if (className) {
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
let detector = null;
|
||||
if (canUnpin) {
|
||||
element.classList.add("unpinable");
|
||||
detector = new ClickDetector(element, {
|
||||
consumeEvents: true,
|
||||
preventDefault: true,
|
||||
targetOnly: true,
|
||||
});
|
||||
detector.click.add(() => this.unpinShape(key));
|
||||
} else {
|
||||
element.classList.add("marked");
|
||||
}
|
||||
|
||||
// Show small info icon
|
||||
const infoButton = document.createElement("button");
|
||||
infoButton.classList.add("infoButton");
|
||||
element.appendChild(infoButton);
|
||||
const infoDetector = new ClickDetector(infoButton, {
|
||||
consumeEvents: true,
|
||||
preventDefault: true,
|
||||
targetOnly: true,
|
||||
});
|
||||
infoDetector.click.add(() => this.root.hud.signals.viewShapeDetailsRequested.dispatch(definition));
|
||||
|
||||
const amountLabel = makeDiv(element, null, ["amountLabel"], "");
|
||||
|
||||
const goal = this.findGoalValueForShape(key);
|
||||
if (goal) {
|
||||
makeDiv(element, null, ["goalLabel"], "/" + formatBigNumber(goal));
|
||||
}
|
||||
|
||||
this.handles.push({
|
||||
key,
|
||||
element,
|
||||
amountLabel,
|
||||
lastRenderedValue: "",
|
||||
detector,
|
||||
infoDetector,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all amount labels
|
||||
*/
|
||||
update() {
|
||||
for (let i = 0; i < this.handles.length; ++i) {
|
||||
const handle = this.handles[i];
|
||||
|
||||
const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
|
||||
const currentValueFormatted = formatBigNumber(currentValue);
|
||||
if (currentValueFormatted !== handle.lastRenderedValue) {
|
||||
handle.lastRenderedValue = currentValueFormatted;
|
||||
handle.amountLabel.innerText = currentValueFormatted;
|
||||
const goal = this.findGoalValueForShape(handle.key);
|
||||
handle.element.classList.toggle("completed", goal && currentValue > goal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins a shape
|
||||
* @param {string} key
|
||||
*/
|
||||
unpinShape(key) {
|
||||
arrayDeleteValue(this.pinnedShapes, key);
|
||||
this.rerenderFull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests to pin a new shape
|
||||
* @param {ShapeDefinition} definition
|
||||
*/
|
||||
pinNewShape(definition) {
|
||||
const key = definition.getHash();
|
||||
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
|
||||
// Can not pin current goal
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === blueprintShape) {
|
||||
// Can not pin the blueprint shape
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if its already pinned
|
||||
if (this.pinnedShapes.indexOf(key) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pinnedShapes.push(key);
|
||||
this.rerenderFull();
|
||||
}
|
||||
}
|
||||
import { ClickDetector } from "../../../core/click_detector";
|
||||
import { formatBigNumber, makeDiv, arrayDeleteValue } from "../../../core/utils";
|
||||
import { ShapeDefinition } from "../../shape_definition";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { blueprintShape, UPGRADES } from "../../upgrades";
|
||||
import { enumHubGoalRewards } from "../../tutorial_goals";
|
||||
|
||||
/**
|
||||
* Manages the pinned shapes on the left side of the screen
|
||||
*/
|
||||
export class HUDPinnedShapes extends BaseHUDPart {
|
||||
constructor(root) {
|
||||
super(root);
|
||||
/**
|
||||
* Store a list of pinned shapes
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.pinnedShapes = [];
|
||||
|
||||
/**
|
||||
* Store handles to the currently rendered elements, so we can update them more
|
||||
* convenient. Also allows for cleaning up handles.
|
||||
* @type {Array<{
|
||||
* key: string,
|
||||
* amountLabel: HTMLElement,
|
||||
* lastRenderedValue: string,
|
||||
* element: HTMLElement,
|
||||
* detector?: ClickDetector,
|
||||
* infoDetector?: ClickDetector
|
||||
* }>}
|
||||
*/
|
||||
this.handles = [];
|
||||
}
|
||||
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the pinned shapes
|
||||
*/
|
||||
serialize() {
|
||||
return {
|
||||
shapes: this.pinnedShapes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the pinned shapes
|
||||
* @param {{ shapes: Array<string>}} data
|
||||
*/
|
||||
deserialize(data) {
|
||||
if (!data || !data.shapes || !Array.isArray(data.shapes)) {
|
||||
return "Invalid pinned shapes data";
|
||||
}
|
||||
this.pinnedShapes = data.shapes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the hud component
|
||||
*/
|
||||
initialize() {
|
||||
// Connect to any relevant signals
|
||||
this.root.signals.storyGoalCompleted.add(this.rerenderFull, this);
|
||||
this.root.signals.upgradePurchased.add(this.updateShapesAfterUpgrade, this);
|
||||
this.root.signals.postLoadHook.add(this.rerenderFull, this);
|
||||
this.root.hud.signals.shapePinRequested.add(this.pinNewShape, this);
|
||||
this.root.hud.signals.shapeUnpinRequested.add(this.unpinShape, this);
|
||||
|
||||
// Perform initial render
|
||||
this.updateShapesAfterUpgrade();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all shapes after an upgrade has been purchased and removes the unused ones
|
||||
*/
|
||||
updateShapesAfterUpgrade() {
|
||||
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
||||
const key = this.pinnedShapes[i];
|
||||
if (key === blueprintShape) {
|
||||
// Ignore blueprint shapes
|
||||
continue;
|
||||
}
|
||||
let goal = this.findGoalValueForShape(key);
|
||||
if (!goal) {
|
||||
// Seems no longer relevant
|
||||
this.pinnedShapes.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.rerenderFull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the current goal for the given key. If the key is the story goal, returns
|
||||
* the story goal. If its the blueprint shape, no goal is returned. Otherwise
|
||||
* it's searched for upgrades.
|
||||
* @param {string} key
|
||||
*/
|
||||
findGoalValueForShape(key) {
|
||||
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
|
||||
return this.root.hubGoals.currentGoal.required;
|
||||
}
|
||||
if (key === blueprintShape) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if this shape is required for any upgrade
|
||||
for (const upgradeId in UPGRADES) {
|
||||
const upgradeTiers = UPGRADES[upgradeId];
|
||||
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
||||
const tierHandle = upgradeTiers[currentTier];
|
||||
|
||||
if (!tierHandle) {
|
||||
// Max level
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tierHandle.required.length; ++i) {
|
||||
const { shape, amount } = tierHandle.required[i];
|
||||
if (shape === key) {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given shape is currently pinned
|
||||
* @param {string} key
|
||||
*/
|
||||
isShapePinned(key) {
|
||||
if (key === this.root.hubGoals.currentGoal.definition.getHash() || key === blueprintShape) {
|
||||
// This is a "special" shape which is always pinned
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.pinnedShapes.indexOf(key) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rerenders the whole component
|
||||
*/
|
||||
rerenderFull() {
|
||||
const currentGoal = this.root.hubGoals.currentGoal;
|
||||
const currentKey = currentGoal.definition.getHash();
|
||||
|
||||
// First, remove all old shapes
|
||||
for (let i = 0; i < this.handles.length; ++i) {
|
||||
this.handles[i].element.remove();
|
||||
const detector = this.handles[i].detector;
|
||||
if (detector) {
|
||||
detector.cleanup();
|
||||
}
|
||||
const infoDetector = this.handles[i].infoDetector;
|
||||
if (infoDetector) {
|
||||
infoDetector.cleanup();
|
||||
}
|
||||
}
|
||||
this.handles = [];
|
||||
|
||||
// Pin story goal
|
||||
this.internalPinShape(currentKey, false, "goal");
|
||||
|
||||
// Pin blueprint shape as well
|
||||
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
|
||||
this.internalPinShape(blueprintShape, false, "blueprint");
|
||||
}
|
||||
|
||||
// Pin manually pinned shapes
|
||||
for (let i = 0; i < this.pinnedShapes.length; ++i) {
|
||||
const key = this.pinnedShapes[i];
|
||||
if (key !== currentKey) {
|
||||
this.internalPinShape(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins a new shape
|
||||
* @param {string} key
|
||||
* @param {boolean} canUnpin
|
||||
* @param {string=} className
|
||||
*/
|
||||
internalPinShape(key, canUnpin = true, className = null) {
|
||||
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
|
||||
|
||||
const element = makeDiv(this.element, null, ["shape"]);
|
||||
const canvas = definition.generateAsCanvas(120);
|
||||
element.appendChild(canvas);
|
||||
|
||||
if (className) {
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
let detector = null;
|
||||
if (canUnpin) {
|
||||
element.classList.add("unpinable");
|
||||
detector = new ClickDetector(element, {
|
||||
consumeEvents: true,
|
||||
preventDefault: true,
|
||||
targetOnly: true,
|
||||
});
|
||||
detector.click.add(() => this.unpinShape(key));
|
||||
} else {
|
||||
element.classList.add("marked");
|
||||
}
|
||||
|
||||
// Show small info icon
|
||||
const infoButton = document.createElement("button");
|
||||
infoButton.classList.add("infoButton");
|
||||
element.appendChild(infoButton);
|
||||
const infoDetector = new ClickDetector(infoButton, {
|
||||
consumeEvents: true,
|
||||
preventDefault: true,
|
||||
targetOnly: true,
|
||||
});
|
||||
infoDetector.click.add(() => this.root.hud.signals.viewShapeDetailsRequested.dispatch(definition));
|
||||
|
||||
const amountLabel = makeDiv(element, null, ["amountLabel"], "");
|
||||
|
||||
const goal = this.findGoalValueForShape(key);
|
||||
if (goal) {
|
||||
makeDiv(element, null, ["goalLabel"], "/" + formatBigNumber(goal));
|
||||
}
|
||||
|
||||
this.handles.push({
|
||||
key,
|
||||
element,
|
||||
amountLabel,
|
||||
lastRenderedValue: "",
|
||||
detector,
|
||||
infoDetector,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all amount labels
|
||||
*/
|
||||
update() {
|
||||
for (let i = 0; i < this.handles.length; ++i) {
|
||||
const handle = this.handles[i];
|
||||
|
||||
const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key);
|
||||
const currentValueFormatted = formatBigNumber(currentValue);
|
||||
if (currentValueFormatted !== handle.lastRenderedValue) {
|
||||
handle.lastRenderedValue = currentValueFormatted;
|
||||
handle.amountLabel.innerText = currentValueFormatted;
|
||||
const goal = this.findGoalValueForShape(handle.key);
|
||||
handle.element.classList.toggle("completed", goal && currentValue > goal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins a shape
|
||||
* @param {string} key
|
||||
*/
|
||||
unpinShape(key) {
|
||||
arrayDeleteValue(this.pinnedShapes, key);
|
||||
this.rerenderFull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests to pin a new shape
|
||||
* @param {ShapeDefinition} definition
|
||||
*/
|
||||
pinNewShape(definition) {
|
||||
const key = definition.getHash();
|
||||
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
|
||||
// Can not pin current goal
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === blueprintShape) {
|
||||
// Can not pin the blueprint shape
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if its already pinned
|
||||
if (this.pinnedShapes.indexOf(key) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pinnedShapes.push(key);
|
||||
this.rerenderFull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +89,8 @@ export class HUDSandboxController extends BaseHUDPart {
|
||||
}
|
||||
|
||||
modifyUpgrade(id, amount) {
|
||||
const handle = UPGRADES[id];
|
||||
const maxLevel = handle.tiers.length;
|
||||
const upgradeTiers = UPGRADES[id];
|
||||
const maxLevel = upgradeTiers.length;
|
||||
|
||||
this.root.hubGoals.upgradeLevels[id] = Math.max(
|
||||
0,
|
||||
@@ -100,7 +100,7 @@ export class HUDSandboxController extends BaseHUDPart {
|
||||
// Compute improvement
|
||||
let improvement = 1;
|
||||
for (let i = 0; i < this.root.hubGoals.upgradeLevels[id]; ++i) {
|
||||
improvement += handle.tiers[i].improvement;
|
||||
improvement += upgradeTiers[i].improvement;
|
||||
}
|
||||
this.root.hubGoals.upgradeImprovements[id] = improvement;
|
||||
this.root.signals.upgradePurchased.dispatch(id);
|
||||
|
||||
@@ -59,11 +59,11 @@ export class HUDShop extends BaseHUDPart {
|
||||
rerenderFull() {
|
||||
for (const upgradeId in this.upgradeToElements) {
|
||||
const handle = this.upgradeToElements[upgradeId];
|
||||
const { tiers } = UPGRADES[upgradeId];
|
||||
const upgradeTiers = UPGRADES[upgradeId];
|
||||
|
||||
const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId);
|
||||
const currentTierMultiplier = this.root.hubGoals.upgradeImprovements[upgradeId];
|
||||
const tierHandle = tiers[currentTier];
|
||||
const tierHandle = upgradeTiers[currentTier];
|
||||
|
||||
// Set tier
|
||||
handle.elemTierLabel.innerText = T.ingame.shop.tier.replace(
|
||||
|
||||
Reference in New Issue
Block a user