|
|
@@ -76,7 +76,7 @@ async function _render() {
|
|
|
renderTarget.append(
|
|
|
renderAnalysis(analysisNodes),
|
|
|
element('p', {textContent: `total cost: ${formatWhole(cost)}`}),
|
|
|
- renderProduction(expertiseGroups, production, prices, recipes, buildings),
|
|
|
+ renderProduction(expertiseGroups, production, storage, prices, recipes, buildings),
|
|
|
renderMatList('extract', extract),
|
|
|
renderMatList('buy', buy),
|
|
|
);
|
|
|
@@ -218,23 +218,29 @@ function buildingDailyCost(building: Building, prices: Record<string, RawPrice>)
|
|
|
return cost;
|
|
|
}
|
|
|
|
|
|
-function renderProduction(expertiseGroups: Record<string, string[]>, production: Production, prices: Record<string, RawPrice>,
|
|
|
- recipes: Record<string, Recipe>, buildings: Record<string, Building>): HTMLElement {
|
|
|
+function renderProduction(expertiseGroups: Record<string, string[]>, production: Production, storage: Record<string, number>,
|
|
|
+ prices: Record<string, RawPrice>, recipes: Record<string, Recipe>, buildings: Record<string, Building>): HTMLElement {
|
|
|
const section = element('section');
|
|
|
section.append(element('h2', {textContent: 'production'}));
|
|
|
|
|
|
+ const matInputs: Record<string, {upstreamMat: string, amount: number}[]> = {};
|
|
|
// mat → list of {outputMat, expertise, amount} that consume it as an input
|
|
|
const matConsumers: Record<string, {downstreamMat: string, expertise: string, amount: number}[]> = {};
|
|
|
for (const [expertise, productionBuildings] of Object.entries(expertiseGroups)) {
|
|
|
for (const building of productionBuildings) {
|
|
|
for (const [mat, totalAmount] of Object.entries(production[building])) {
|
|
|
const recipe = recipes[mat];
|
|
|
+ if (!matInputs[mat])
|
|
|
+ matInputs[mat] = [];
|
|
|
+
|
|
|
const outputPerRun = recipe.outputs.find((o) => o.material_ticker === mat)!.material_amount;
|
|
|
for (const input of recipe.inputs) {
|
|
|
const ticker = input.material_ticker;
|
|
|
+ const amount = input.material_amount * totalAmount / outputPerRun;
|
|
|
+ matInputs[mat].push({upstreamMat: ticker, amount});
|
|
|
+
|
|
|
if (!matConsumers[ticker])
|
|
|
matConsumers[ticker] = [];
|
|
|
- const amount = input.material_amount * totalAmount / outputPerRun;
|
|
|
matConsumers[ticker].push({downstreamMat: mat, expertise, amount});
|
|
|
}
|
|
|
}
|
|
|
@@ -249,26 +255,10 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
const buildingMats = element('div');
|
|
|
const mats = Object.entries(production[building]);
|
|
|
let buildingMins = 0;
|
|
|
- for (const [index, [mat, amount]] of mats.entries()) {
|
|
|
- const traded = prices[mat]?.AverageTraded30D ?? 0;
|
|
|
- const span = element('span', {textContent: `${formatAmount(amount)}x${mat}`});
|
|
|
- span.style.color = traded > amount * 2 ? '#0cc' : '#c70';
|
|
|
- const consumers = matConsumers[mat];
|
|
|
- if (consumers) {
|
|
|
- span.dataset.tooltip = consumers
|
|
|
- .map((c) => `${formatAmount(c.amount)}x${mat} → ${c.downstreamMat} (${c.expertise.toLocaleLowerCase()})`)
|
|
|
- .join('\n');
|
|
|
- for (const consumer of consumers) {
|
|
|
- if (consumer.expertise == expertise) // we aren't shipping it anywhere
|
|
|
- continue;
|
|
|
- if (!shipTo[consumer.expertise])
|
|
|
- shipTo[consumer.expertise] = {};
|
|
|
- shipTo[consumer.expertise][mat] = (shipTo[consumer.expertise][mat] ?? 0) + consumer.amount;
|
|
|
- }
|
|
|
- }
|
|
|
- buildingMats.append(span);
|
|
|
- if (index < mats.length - 1)
|
|
|
- buildingMats.append(document.createTextNode(' '));
|
|
|
+ for (const [mat, amount] of mats) {
|
|
|
+ buildingMats.append(document.createTextNode(' '));
|
|
|
+ buildingMats.append(renderProductionBuildingMat(expertise, mat, amount, storage,
|
|
|
+ matInputs, matConsumers, shipTo));
|
|
|
|
|
|
const recipe = recipes[mat];
|
|
|
const outputPerRun = recipe.outputs.find((o) => o.material_ticker === mat)!.material_amount;
|
|
|
@@ -298,6 +288,37 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
return section;
|
|
|
}
|
|
|
|
|
|
+function renderProductionBuildingMat(expertise: string, mat: string, amount: number, storage: Record<string, number>,
|
|
|
+ matInputs: Record<string, {upstreamMat: string, amount: number}[]>,
|
|
|
+ matConsumers: Record<string, {downstreamMat: string, expertise: string, amount: number}[]>,
|
|
|
+ shipTo: Record<string, Record<string, number>>): HTMLElement {
|
|
|
+ const inStorage = storage[mat] ?? 0;
|
|
|
+ const span = element('span', {textContent: `${formatAmount(amount)}x${mat} (${inStorage})`});
|
|
|
+ const percent = Math.min(inStorage / (amount * 2), 1);
|
|
|
+ span.style.color = `color-mix(in xyz, #0aa ${percent * 100}%, #f80 )`;
|
|
|
+
|
|
|
+ let tooltip = '';
|
|
|
+ const inputs = matInputs[mat];
|
|
|
+ if (inputs)
|
|
|
+ tooltip += inputs.map((i) => `${formatAmount(i.amount)}x${i.upstreamMat} (${storage[i.upstreamMat] ?? 0}) → ` +
|
|
|
+ `${formatAmount(amount)}x${mat}`).join('\n') + '\n';
|
|
|
+ const consumers = matConsumers[mat];
|
|
|
+ if (consumers) {
|
|
|
+ tooltip += consumers
|
|
|
+ .map((c) => `${formatAmount(c.amount)}x${mat} → ${c.downstreamMat} (${c.expertise.toLocaleLowerCase()})`)
|
|
|
+ .join('\n');
|
|
|
+ for (const consumer of consumers) {
|
|
|
+ if (consumer.expertise == expertise) // we aren't shipping it anywhere
|
|
|
+ continue;
|
|
|
+ if (!shipTo[consumer.expertise])
|
|
|
+ shipTo[consumer.expertise] = {};
|
|
|
+ shipTo[consumer.expertise][mat] = (shipTo[consumer.expertise][mat] ?? 0) + consumer.amount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ span.dataset.tooltip = tooltip;
|
|
|
+ return span;
|
|
|
+}
|
|
|
+
|
|
|
async function fetchStorage(): Promise<Record<string, number>> {
|
|
|
if (!apiKey.value)
|
|
|
return {};
|