Procházet zdrojové kódy

production: figure out what's ready to import

raylu před 2 týdny
rodič
revize
10c8bea1b1
1 změnil soubory, kde provedl 72 přidání a 22 odebrání
  1. 72 22
      ts/production.ts

+ 72 - 22
ts/production.ts

@@ -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>>};