import { GameRoot } from "../../root"; import { ShapeDefinition } from "../../shape_definition"; import { enumAnalyticsDataSource } from "../../production_analytics"; import { formatBigNumber, clamp } from "../../../core/utils"; import { globalConfig } from "../../../core/config"; import { makeOffscreenBuffer } from "../../../core/buffer_utils"; /** @enum {string} */ export const enumDisplayMode = { icons: "icons", detailed: "detailed", }; /** * Simple wrapper for a shape definition within the shape statistics */ export class HUDShapeStatisticsHandle { /** * @param {GameRoot} root * @param {ShapeDefinition} definition * @param {IntersectionObserver} intersectionObserver */ constructor(root, definition, intersectionObserver) { this.definition = definition; this.root = root; this.intersectionObserver = intersectionObserver; this.visible = false; } initElement() { this.element = document.createElement("div"); this.element.setAttribute("data-shape-key", this.definition.getHash()); this.counter = document.createElement("span"); this.counter.classList.add("counter"); this.element.appendChild(this.counter); } /** * Sets whether the shape handle is visible currently * @param {boolean} visibility */ setVisible(visibility) { if (visibility === this.visible) { return; } this.visible = visibility; if (visibility) { if (!this.shapeCanvas) { // Create elements this.shapeCanvas = this.definition.generateAsCanvas(100); this.shapeCanvas.classList.add("icon"); this.element.appendChild(this.shapeCanvas); } } else { // Drop elements this.cleanupChildElements(); } } /** * * @param {enumDisplayMode} displayMode * @param {enumAnalyticsDataSource} dataSource * @param {boolean=} forced */ update(displayMode, dataSource, forced = false) { if (!this.element) { return; } if (!this.visible && !forced) { return; } switch (dataSource) { case enumAnalyticsDataSource.stored: { this.counter.innerText = formatBigNumber( this.root.hubGoals.storedShapes[this.definition.getHash()] || 0 ); break; } case enumAnalyticsDataSource.delivered: case enumAnalyticsDataSource.produced: { let rate = (this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) / globalConfig.analyticsSliceDurationSeconds) * 60; this.counter.innerText = formatBigNumber(rate) + " / m"; break; } } if (displayMode === enumDisplayMode.detailed) { const graphDpi = globalConfig.statisticsGraphDpi; const w = 300; const h = 40; if (!this.graphCanvas) { const [canvas, context] = makeOffscreenBuffer(w * graphDpi, h * graphDpi, { smooth: true, reusable: false, label: "statgraph-" + this.definition.getHash(), }); context.scale(graphDpi, graphDpi); canvas.classList.add("graph"); this.graphCanvas = canvas; this.graphContext = context; this.element.appendChild(this.graphCanvas); } this.graphContext.clearRect(0, 0, w, h); this.graphContext.fillStyle = "#bee0db"; this.graphContext.strokeStyle = "#66ccbc"; this.graphContext.lineWidth = 1.5; const sliceWidth = w / globalConfig.statisticsGraphSlices; let values = []; let maxValue = 1; for (let i = 0; i < globalConfig.statisticsGraphSlices - 1; ++i) { const value = this.root.productionAnalytics.getPastShapeRate( dataSource, this.definition, globalConfig.statisticsGraphSlices - i - 1 ); if (value > maxValue) { maxValue = value; } values.push(value); } this.graphContext.beginPath(); this.graphContext.moveTo(0.75, h + 5); for (let i = 0; i < values.length; ++i) { const yValue = clamp((1 - values[i] / maxValue) * h, 0.75, h - 0.75); const x = i * sliceWidth; if (i === 0) { this.graphContext.lineTo(0.75, yValue); } this.graphContext.lineTo(x, yValue); if (i === values.length - 1) { this.graphContext.lineTo(w + 100, yValue); this.graphContext.lineTo(w + 100, h + 5); } } this.graphContext.closePath(); this.graphContext.stroke(); this.graphContext.fill(); } else { if (this.graphCanvas) { this.graphCanvas.remove(); delete this.graphCanvas; delete this.graphContext; } } } /** * Attaches the handle * @param {HTMLElement} parent */ attach(parent) { if (!this.element) { this.initElement(); } if (this.element.parentElement !== parent) { parent.appendChild(this.element); this.intersectionObserver.observe(this.element); } } /** * Detaches the handle */ detach() { if (this.element && this.element.parentElement) { this.element.parentElement.removeChild(this.element); this.intersectionObserver.unobserve(this.element); } } /** * Cleans up all child elements */ cleanupChildElements() { if (this.shapeCanvas) { this.shapeCanvas.remove(); delete this.shapeCanvas; } if (this.graphCanvas) { this.graphCanvas.remove(); delete this.graphCanvas; delete this.graphContext; } } /** * Destroys the handle */ destroy() { this.cleanupChildElements(); if (this.element) { this.intersectionObserver.unobserve(this.element); this.element.remove(); delete this.element; // Remove handle delete this.counter; } } }