|
|
@@ -38,10 +38,11 @@ async function render() {
|
|
|
}
|
|
|
|
|
|
async function _render() {
|
|
|
- const [allPrices, {recipes, extractables}, buildingList, storage] = await Promise.all([
|
|
|
+ const [allPrices, {recipes, extractables}, buildingList, buildingPlanets, storage] = await Promise.all([
|
|
|
cachedFetchJSON('https://refined-prun.github.io/refined-prices/all.json') as Promise<RawPrice[]>,
|
|
|
recipeForMats(),
|
|
|
cachedFetchJSON('https://api.prunplanner.org/data/buildings/') as Promise<Building[]>,
|
|
|
+ fetchSites(),
|
|
|
fetchStorage(),
|
|
|
]);
|
|
|
const prices = Object.fromEntries(allPrices.filter((price) => price.ExchangeCode === cx)
|
|
|
@@ -54,7 +55,7 @@ async function _render() {
|
|
|
const analysisNodes: AnalysisNode[] = [];
|
|
|
let cost = 0;
|
|
|
for (const [mat, amount] of Object.entries(blueprint)) {
|
|
|
- const node = analyzeMat(mat, amount, production, extract, buy, prices, recipes, extractables, storage);
|
|
|
+ const node = analyzeMat(mat, amount, production, extract, buy, prices, recipes, extractables, storage.allItems);
|
|
|
cost += node.cost;
|
|
|
analysisNodes.push(node);
|
|
|
}
|
|
|
@@ -78,9 +79,9 @@ async function _render() {
|
|
|
renderTarget.append(
|
|
|
renderAnalysis(analysisNodes),
|
|
|
element('p', {textContent: `total cost: ${formatWhole(cost)}`}),
|
|
|
- renderProduction(expertiseGroups, production, storage, prices, recipes, buildings),
|
|
|
- renderMatList('extract', extract, storage),
|
|
|
- renderMatList('buy', buy, storage),
|
|
|
+ renderProduction(expertiseGroups, production, buildingPlanets, storage, prices, recipes, buildings),
|
|
|
+ renderMatList('extract', extract, storage.allItems),
|
|
|
+ renderMatList('buy', buy, storage.allItems),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -210,7 +211,7 @@ function renderAnalysisNode(node: AnalysisNode, level = 0): HTMLElement {
|
|
|
const amountText = element('span', {textContent: `${formatAmount(node.amount)}x${node.ticker} `});
|
|
|
const storageText = element('span', {textContent: `(${formatAmount(node.inStorage)})`});
|
|
|
const percent = Math.min(node.inStorage / node.amount, 1);
|
|
|
- storageText.style.color = `color-mix(in xyz, #0aa ${percent * 100}%, #f80 )`;
|
|
|
+ storageText.style.color = `color-mix(in xyz, #0aa ${percent * 100}%, #f80)`;
|
|
|
const acquisitionText = element('span', {textContent: ' ' + node.acquisition});
|
|
|
|
|
|
let el;
|
|
|
@@ -252,8 +253,9 @@ function buildingDailyCost(building: Building, prices: Record<string, RawPrice>)
|
|
|
return cost;
|
|
|
}
|
|
|
|
|
|
-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 {
|
|
|
+function renderProduction(expertiseGroups: Record<string, string[]>, production: Production,
|
|
|
+ buildingPlanets: Record<string, Set<string>>, storage: Storage, prices: Record<string, RawPrice>,
|
|
|
+ recipes: Record<string, Recipe>, buildings: Record<string, Building>): HTMLElement {
|
|
|
const section = element('section');
|
|
|
section.append(element('h2', {textContent: 'production'}));
|
|
|
|
|
|
@@ -286,6 +288,7 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
section.append(element('h3', {textContent: expertise.toLocaleLowerCase()}));
|
|
|
const imports: Record<string, number> = {};
|
|
|
const exportTo: Record<string, Record<string, number>> = {};
|
|
|
+ const planets = new Set<string>();
|
|
|
for (const building of productionBuildings) {
|
|
|
const buildingRow = element('div', {className: 'building-row'});
|
|
|
const mats = Object.entries(production[building]);
|
|
|
@@ -305,6 +308,9 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
buildingRow.prepend(document.createTextNode(
|
|
|
`${formatFixed(numBuildings, 1)}x${building} (${formatWhole(consumablesCost)}/d)`));
|
|
|
section.append(buildingRow);
|
|
|
+
|
|
|
+ for (const planet of buildingPlanets[building] ?? [])
|
|
|
+ planets.add(planet);
|
|
|
}
|
|
|
|
|
|
const importDetails = element('details');
|
|
|
@@ -312,7 +318,20 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
for (const [mat, amount] of Object.entries(imports)) {
|
|
|
if (recipes[mat] && buildings[recipes[mat].building_ticker].expertise == expertise)
|
|
|
continue;
|
|
|
- importDetails.append(element('div', {textContent: `${formatAmount(amount)}x${mat}`}));
|
|
|
+ let onSite = 0;
|
|
|
+ for (const planet of planets)
|
|
|
+ onSite += storage.planetItems[planet][mat] ?? 0;
|
|
|
+ const offSite = (storage.allItems[mat] ?? 0) - onSite;
|
|
|
+ const importDetail = element('div', {
|
|
|
+ textContent: `${formatAmount(amount)}x${mat} (${formatAmount(onSite)} on-site, ${formatAmount(offSite)} off-site)`});
|
|
|
+ if (onSite >= amount * 2)
|
|
|
+ importDetail.style.color = '#777';
|
|
|
+ else {
|
|
|
+ const shouldBring = amount - onSite;
|
|
|
+ if (shouldBring > 0)
|
|
|
+ importDetail.style.color = `color-mix(in xyz, #0aa ${Math.min(offSite / shouldBring, 1) * 100}%, #f80)`;
|
|
|
+ }
|
|
|
+ importDetails.append(importDetail);
|
|
|
}
|
|
|
section.append(importDetails);
|
|
|
|
|
|
@@ -331,11 +350,11 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
return section;
|
|
|
}
|
|
|
|
|
|
-function renderProductionBuildingMat(expertise: string, mat: string, amount: number, storage: Record<string, number>,
|
|
|
+function renderProductionBuildingMat(expertise: string, mat: string, amount: number, storage: Storage,
|
|
|
matInputs: Record<string, {upstreamMat: string, amount: number}[]>,
|
|
|
matConsumers: Record<string, {downstreamMat: string, expertise: string, amount: number}[]>,
|
|
|
imports: Record<string, number>, exportTo: Record<string, Record<string, number>>): HTMLElement {
|
|
|
- const inStorage = storage[mat] ?? 0;
|
|
|
+ const inStorage = storage.allItems[mat] ?? 0;
|
|
|
const wrapper = element('span');
|
|
|
|
|
|
const amountText = element('span', {textContent: `${formatAmount(amount)}x${mat}`});
|
|
|
@@ -345,18 +364,18 @@ function renderProductionBuildingMat(expertise: string, mat: string, amount: num
|
|
|
wrapper.append(storageText);
|
|
|
|
|
|
const percent = Math.min(inStorage / (amount * 2), 1);
|
|
|
- storageText.style.color = `color-mix(in xyz, #0aa ${percent * 100}%, #f80 )`;
|
|
|
+ storageText.style.color = `color-mix(in xyz, #0aa ${percent * 100}%, #f80)`;
|
|
|
if (percent >= 1)
|
|
|
amountText.style.color = '#777';
|
|
|
else {
|
|
|
- const produceable = Object.values(matInputs[mat]).every((input) => storage[input.upstreamMat] ?? 0 >= input.amount);
|
|
|
+ const produceable = Object.values(matInputs[mat]).every((input) => storage.allItems[input.upstreamMat] ?? 0 >= input.amount);
|
|
|
amountText.style.color = produceable ? '#0aa' : '#f80';
|
|
|
}
|
|
|
|
|
|
let tooltip = '';
|
|
|
const inputs = matInputs[mat];
|
|
|
if (inputs) {
|
|
|
- tooltip += inputs.map((i) => `${formatAmount(i.amount)}x${i.upstreamMat} (${storage[i.upstreamMat] ?? 0}) → ` +
|
|
|
+ tooltip += inputs.map((i) => `${formatAmount(i.amount)}x${i.upstreamMat} (${storage.allItems[i.upstreamMat] ?? 0}) → ` +
|
|
|
`${formatAmount(amount)}x${mat}`).join('\n') + '\n';
|
|
|
for (const input of inputs)
|
|
|
imports[input.upstreamMat] = (imports[input.upstreamMat] ?? 0) + input.amount;
|
|
|
@@ -378,17 +397,39 @@ function renderProductionBuildingMat(expertise: string, mat: string, amount: num
|
|
|
return wrapper;
|
|
|
}
|
|
|
|
|
|
-async function fetchStorage(): Promise<Record<string, number>> {
|
|
|
+async function fetchSites(): Promise<Record<string, Set<string>>> {
|
|
|
if (!apiKey.value)
|
|
|
return {};
|
|
|
- const users = await fetch('https://api.punoted.net/v1/storages/', {headers: {'X-Data-Token': apiKey.value}})
|
|
|
- .then((r) => r.json()) as PUNUserStore[];
|
|
|
- const items: Record<string, number> = {};
|
|
|
- for (const user of users)
|
|
|
+ const userSites: PUNUserSite[] = await fetch('https://api.punoted.net/v1/sites/?include_buildings=true',
|
|
|
+ {headers: {'X-Data-Token': apiKey.value}}).then((r) => r.json());
|
|
|
+
|
|
|
+ const buildingPlanets: Record<string, Set<string>> = {};
|
|
|
+ for (const user of userSites)
|
|
|
+ for (const site of user.Sites)
|
|
|
+ for (const building of site.Buildings) {
|
|
|
+ if (!buildingPlanets[building.BuildingTicker])
|
|
|
+ buildingPlanets[building.BuildingTicker] = new Set();
|
|
|
+ buildingPlanets[building.BuildingTicker].add(site.PlanetName);
|
|
|
+ }
|
|
|
+ return buildingPlanets;
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchStorage(): Promise<Storage> {
|
|
|
+ if (!apiKey.value)
|
|
|
+ return {allItems: {}, planetItems: {}};
|
|
|
+ const userStores: PUNUserStore[] = await fetch('https://api.punoted.net/v1/storages/',
|
|
|
+ {headers: {'X-Data-Token': apiKey.value}}).then((r) => r.json());
|
|
|
+
|
|
|
+ const allItems: Record<string, number> = {};
|
|
|
+ const planetItems: Record<string, Record<string, number>> = {};
|
|
|
+ for (const user of userStores)
|
|
|
for (const storage of user.Storages)
|
|
|
- for (const item of storage.StorageItems)
|
|
|
- items[item.MaterialTicker] = (items[item.MaterialTicker] ?? 0) + item.MaterialAmount;
|
|
|
- return items;
|
|
|
+ for (const item of storage.StorageItems) {
|
|
|
+ allItems[item.MaterialTicker] = (allItems[item.MaterialTicker] ?? 0) + item.MaterialAmount;
|
|
|
+ const planetStorage = planetItems[storage.Location] ??= {};
|
|
|
+ planetStorage[item.MaterialTicker] = (planetStorage[item.MaterialTicker] ?? 0) + item.MaterialAmount;
|
|
|
+ }
|
|
|
+ return {allItems, planetItems};
|
|
|
}
|
|
|
|
|
|
function element<K extends keyof HTMLElementTagNameMap>(tagName: K,
|
|
|
@@ -461,6 +502,7 @@ interface Building {
|
|
|
|
|
|
interface PUNUserStore {
|
|
|
Storages: Array<{
|
|
|
+ Location: string
|
|
|
StorageItems: Array<{
|
|
|
MaterialTicker: string
|
|
|
MaterialAmount: number
|
|
|
@@ -468,4 +510,12 @@ interface PUNUserStore {
|
|
|
}>
|
|
|
}
|
|
|
|
|
|
+interface PUNUserSite {
|
|
|
+ Sites: Array<{
|
|
|
+ PlanetName: string
|
|
|
+ Buildings: Array<{BuildingTicker: string}>
|
|
|
+ }>
|
|
|
+}
|
|
|
+
|
|
|
type Production = Record<string, Record<string, number>>;
|
|
|
+type Storage = {allItems: Record<string, number>, planetItems: Record<string, Record<string, number>>};
|