Refactor pinned shapes and make them smart, closes #72

pull/130/head
tobspr 4 years ago
parent 8c01cc23d0
commit 3af6532d32

3
.gitignore vendored

@ -112,3 +112,6 @@ tmp_standalone_files
# Github Actions files
.github/workflows
# Local config
config.local.js

@ -109,5 +109,9 @@
}
}
}
&.completed {
opacity: 0.5;
}
}
}

@ -1,4 +1,13 @@
export const CHANGELOG = [
{
version: "1.1.11",
date: "unrelease",
entries: [
"Pinned shapes are now smart, they dynamically update their goal and also unpin when no longer required. Completed objectives are now rendered transparent.",
"Improve upgrade number rounding, so there are no goals like '37.4k', instead it will now be '35k'",
"Fix bug regarding number rounding",
],
},
{
version: "1.1.10",
date: "12.06.2020",

@ -377,7 +377,23 @@ export function findNiceValue(num) {
return 0;
}
const roundAmount = 0.5 * Math_pow(10, Math_floor(Math_log10(num) - 1));
let roundAmount = 1;
if (num > 50000) {
roundAmount = 10000;
} else if (num > 20000) {
roundAmount = 5000;
} else if (num > 5000) {
roundAmount = 1000;
} else if (num > 2000) {
roundAmount = 500;
} else if (num > 1000) {
roundAmount = 100;
} else if (num > 100) {
roundAmount = 20;
} else if (num > 20) {
roundAmount = 5;
}
const niceValue = Math_floor(num / roundAmount) * roundAmount;
if (num >= 10) {
return Math_round(niceValue);
@ -389,6 +405,8 @@ export function findNiceValue(num) {
return Math_round(niceValue * 100) / 100;
}
window.fn = findNiceValue;
/**
* Finds a nice integer value
* @see findNiceValue

@ -70,7 +70,7 @@ export class GameHUD {
this.signals = {
selectedPlacementBuildingChanged: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()),
shapePinRequested: /** @type {TypedSignal<[ShapeDefinition, number]>} */ (new Signal()),
shapePinRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()),
shapeUnpinRequested: /** @type {TypedSignal<[string]>} */ (new Signal()),
notification: /** @type {TypedSignal<[string, enumNotificationType]>} */ (new Signal()),
buildingsSelectedForCopy: /** @type {TypedSignal<[Array<number>]>} */ (new Signal()),

@ -1,22 +1,54 @@
import { Math_max } from "../../../core/builtins";
import { ClickDetector } from "../../../core/click_detector";
import { formatBigNumber, makeDiv } from "../../../core/utils";
import { formatBigNumber, makeDiv, arrayDelete, arrayDeleteValue } from "../../../core/utils";
import { ShapeDefinition } from "../../shape_definition";
import { BaseHUDPart } from "../base_hud_part";
import { blueprintShape } from "../../upgrades";
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
* }>}
*/
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";
@ -24,48 +56,99 @@ export class HUDPinnedShapes extends BaseHUDPart {
this.pinnedShapes = data.shapes;
}
/**
* Initializes the hud component
*/
initialize() {
/** @type {Array<{ key: string, goal: number }>} */
this.pinnedShapes = [];
/** @type {Array<{key: string, amountLabel: HTMLElement, lastRenderedValue: number, element: HTMLElement, detector?: ClickDetector}>} */
this.handles = [];
this.rerenderFull();
// 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();
}
/**
* Returns whether a given shape is pinned
* @param {string} key
* Updates all shapes after an upgrade has been purchased and removes the unused ones
*/
isShapePinned(key) {
if (!this.pinnedShapes) {
return false;
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 true;
return this.root.hubGoals.currentGoal.required;
}
if (key === blueprintShape) {
return true;
return null;
}
for (let i = 0; i < this.pinnedShapes.length; ++i) {
if (this.pinnedShapes[i].key === key) {
return true;
// 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 false;
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 old ones
// 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;
@ -75,28 +158,30 @@ export class HUDPinnedShapes extends BaseHUDPart {
}
this.handles = [];
this.internalPinShape(currentKey, currentGoal.required, false, "goal");
// Pin story goal
this.internalPinShape(currentKey, false, "goal");
// Pin blueprint shape as well
if (this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
this.internalPinShape(blueprintShape, null, false, "blueprint");
this.internalPinShape(blueprintShape, false, "blueprint");
}
// Pin manually pinned shapes
for (let i = 0; i < this.pinnedShapes.length; ++i) {
const key = this.pinnedShapes[i].key;
const key = this.pinnedShapes[i];
if (key !== currentKey) {
this.internalPinShape(key, this.pinnedShapes[i].goal);
this.internalPinShape(key);
}
}
}
/**
* Pins a shape
* Pins a new shape
* @param {string} key
* @param {number} goal
* @param {boolean} canUnpin
* @param {string=} className
*/
internalPinShape(key, goal, canUnpin = true, className = null) {
internalPinShape(key, canUnpin = true, className = null) {
const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key);
const element = makeDiv(this.element, null, ["shape"]);
@ -121,6 +206,7 @@ export class HUDPinnedShapes extends BaseHUDPart {
const amountLabel = makeDiv(element, null, ["amountLabel"], "");
const goal = this.findGoalValueForShape(key);
if (goal) {
makeDiv(element, null, ["goalLabel"], "/" + formatBigNumber(goal));
}
@ -129,18 +215,24 @@ export class HUDPinnedShapes extends BaseHUDPart {
key,
element,
amountLabel,
lastRenderedValue: -1,
lastRenderedValue: "",
});
}
/**
* 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);
if (currentValue !== handle.lastRenderedValue) {
handle.lastRenderedValue = currentValue;
handle.amountLabel.innerText = formatBigNumber(currentValue);
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);
}
}
}
@ -150,20 +242,15 @@ export class HUDPinnedShapes extends BaseHUDPart {
* @param {string} key
*/
unpinShape(key) {
for (let i = 0; i < this.pinnedShapes.length; ++i) {
if (this.pinnedShapes[i].key === key) {
this.pinnedShapes.splice(i, 1);
this.rerenderFull();
return;
}
}
arrayDeleteValue(this.pinnedShapes, key);
this.rerenderFull();
}
/**
* Requests to pin a new shape
* @param {ShapeDefinition} definition
* @param {number} goal
*/
pinNewShape(definition, goal) {
pinNewShape(definition) {
const key = definition.getHash();
if (key === this.root.hubGoals.currentGoal.definition.getHash()) {
// Can not pin current goal
@ -171,18 +258,16 @@ export class HUDPinnedShapes extends BaseHUDPart {
}
if (key === blueprintShape) {
// Can not pin the blueprint shape
return;
}
for (let i = 0; i < this.pinnedShapes.length; ++i) {
if (this.pinnedShapes[i].key === key) {
// Already pinned
this.pinnedShapes[i].goal = Math_max(this.pinnedShapes[i].goal, goal);
return;
}
// Check if its already pinned
if (this.pinnedShapes.indexOf(key) >= 0) {
return;
}
this.pinnedShapes.push({ key, goal });
this.pinnedShapes.push(key);
this.rerenderFull();
}
}

@ -139,7 +139,7 @@ export class HUDShop extends BaseHUDPart {
pinButton.classList.add("unpinned");
pinButton.classList.remove("pinned", "alreadyPinned");
} else {
this.root.hud.signals.shapePinRequested.dispatch(shapeDef, amount);
this.root.hud.signals.shapePinRequested.dispatch(shapeDef);
pinButton.classList.add("pinned");
pinButton.classList.remove("unpinned");
}

@ -9,10 +9,10 @@ import { SavegameSerializer } from "./savegame_serializer";
import { BaseSavegameInterface } from "./savegame_interface";
import { createLogger } from "../core/logging";
import { globalConfig } from "../core/config";
import { SavegameInterface_V1000 } from "./schemas/1000";
import { getSavegameInterface, savegameInterfaces } from "./savegame_interface_registry";
import { SavegameInterface_V1001 } from "./schemas/1001";
import { SavegameInterface_V1002 } from "./schemas/1002";
import { SavegameInterface_V1003 } from "./schemas/1003";
const logger = createLogger("savegame");
@ -44,7 +44,7 @@ export class Savegame extends ReadWriteProxy {
* @returns {number}
*/
static getCurrentVersion() {
return 1002;
return 1003;
}
/**
@ -93,6 +93,11 @@ export class Savegame extends ReadWriteProxy {
data.version = 1002;
}
if (data.version === 1002) {
SavegameInterface_V1003.migrate1002to1003(data);
data.version = 1003;
}
return ExplainedResult.good();
}

@ -3,12 +3,14 @@ import { SavegameInterface_V1000 } from "./schemas/1000";
import { createLogger } from "../core/logging";
import { SavegameInterface_V1001 } from "./schemas/1001";
import { SavegameInterface_V1002 } from "./schemas/1002";
import { SavegameInterface_V1003 } from "./schemas/1003";
/** @type {Object.<number, typeof BaseSavegameInterface>} */
export const savegameInterfaces = {
1000: SavegameInterface_V1000,
1001: SavegameInterface_V1001,
1002: SavegameInterface_V1002,
1003: SavegameInterface_V1003,
};
const logger = createLogger("savegame_interface_registry");

@ -0,0 +1,28 @@
import { createLogger } from "../../core/logging.js";
import { SavegameInterface_V1002 } from "./1002.js";
const schema = require("./1003.json");
const logger = createLogger("savegame_interface/1003");
export class SavegameInterface_V1003 extends SavegameInterface_V1002 {
getVersion() {
return 1003;
}
getSchemaUncached() {
return schema;
}
/**
* @param {import("../savegame_typedefs.js").SavegameData} data
*/
static migrate1002to1003(data) {
logger.log("Migrating 1002 to 1003");
const dump = data.dump;
if (!dump) {
return true;
}
dump.pinnedShapes = { shapes: [] };
}
}

@ -0,0 +1,5 @@
{
"type": "object",
"required": [],
"additionalProperties": true
}
Loading…
Cancel
Save