|
|
@@ -44,7 +44,7 @@ async function render() {
|
|
|
throw new Error('missing shipbuilding container');
|
|
|
main.innerHTML = '<p>Loading...</p>';
|
|
|
|
|
|
- const [allPrices, recipes, buildings, knownCompanies, companyProductionById] = await Promise.all([
|
|
|
+ const [allPrices, recipes, buildingList, knownCompanies, companyProductionById] = 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[]>,
|
|
|
@@ -53,6 +53,7 @@ async function render() {
|
|
|
]);
|
|
|
const prices = Object.fromEntries(allPrices.filter((price) => price.ExchangeCode === 'IC1')
|
|
|
.map((price) => [price.MaterialTicker, price]));
|
|
|
+ const buildings = Object.fromEntries(buildingList.map((b) => [b.building_ticker, b]));
|
|
|
|
|
|
const production: Production = {};
|
|
|
const extract: Record<string, number> = {};
|
|
|
@@ -72,7 +73,7 @@ async function render() {
|
|
|
requiredMats[mat] = (requiredMats[mat] ?? 0) + amount;
|
|
|
|
|
|
const expertiseGroups: Record<string, string[]> = {};
|
|
|
- for (const building of buildings) {
|
|
|
+ for (const building of buildingList) {
|
|
|
if (!(building.building_ticker in production))
|
|
|
continue;
|
|
|
if (!expertiseGroups[building.expertise])
|
|
|
@@ -84,7 +85,7 @@ async function render() {
|
|
|
main.append(
|
|
|
renderAnalysis(analysisNodes),
|
|
|
element('p', {textContent: `total cost: ${formatWhole(cost)}`}),
|
|
|
- renderProduction(expertiseGroups, production, prices, recipes),
|
|
|
+ renderProduction(expertiseGroups, production, prices, recipes, buildings),
|
|
|
renderMatList('extract', extract),
|
|
|
renderMatList('buy', buy),
|
|
|
renderShipbuilders(requiredMats, knownCompanies, companyProductionById),
|
|
|
@@ -239,14 +240,36 @@ function renderAnalysisNode(node: AnalysisNode, level = 0): HTMLElement {
|
|
|
return el;
|
|
|
}
|
|
|
|
|
|
-function renderProduction(expertiseGroups: Record<string, string[]>, production: Production,
|
|
|
- prices: Record<string, RawPrice>, recipes: Record<string, Recipe>): HTMLElement {
|
|
|
+const WORKER_CONSUMPTION: Record<'pioneers' | 'settlers' | 'technicians' | 'engineers' | 'scientists', Record<string, number>> = {
|
|
|
+ pioneers: {'COF': 0.5, 'DW': 4, 'RAT': 4, 'OVE': 0.5, 'PWO': 0.2},
|
|
|
+ settlers: {'DW': 5, 'RAT': 6, 'KOM': 1, 'EXO': 0.5, 'REP': 0.2, 'PT': 0.5},
|
|
|
+ technicians: {'DW': 7.5, 'RAT': 7, 'ALE': 1, 'MED': 0.5, 'SC': 0.1, 'HMS': 0.5, 'SCN': 0.1},
|
|
|
+ engineers: {'DW': 10, 'MED': 0.5, 'GIN': 1, 'FIM': 7, 'VG': 0.2, 'HSS': 0.2, 'PDA': 0.1},
|
|
|
+ scientists: {'DW': 10, 'MED': 0.5, 'WIN': 1, 'MEA': 7, 'NST': 0.1, 'LC': 0.2, 'WS': 0.05},
|
|
|
+};
|
|
|
+
|
|
|
+function buildingDailyCost(building: Building, prices: Record<string, RawPrice>): number {
|
|
|
+ let cost = 0;
|
|
|
+ for (const [workerType, mats] of Object.entries(WORKER_CONSUMPTION)) {
|
|
|
+ const workers = building[workerType as keyof typeof WORKER_CONSUMPTION];
|
|
|
+ for (const [mat, per100] of Object.entries(mats)) {
|
|
|
+ const price = prices[mat].VWAP30D;
|
|
|
+ if (price == null) throw new Error(`no price for ${mat}`);
|
|
|
+ cost += price * workers * per100 / 100;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return cost;
|
|
|
+}
|
|
|
+
|
|
|
+function renderProduction(expertiseGroups: Record<string, string[]>, production: Production, prices: Record<string, RawPrice>,
|
|
|
+ recipes: Record<string, Recipe>, buildings: Record<string, Building>): HTMLElement {
|
|
|
const section = element('section');
|
|
|
section.append(element('h2', {textContent: 'production'}));
|
|
|
|
|
|
- for (const [expertise, buildings] of Object.entries(expertiseGroups)) {
|
|
|
+ let totalConsumablesCost = 0;
|
|
|
+ for (const [expertise, productionBuildings] of Object.entries(expertiseGroups)) {
|
|
|
section.append(element('h3', {textContent: expertise}));
|
|
|
- for (const building of buildings) {
|
|
|
+ for (const building of productionBuildings) {
|
|
|
const buildingMats = element('div');
|
|
|
const mats = Object.entries(production[building]);
|
|
|
let buildingMins = 0;
|
|
|
@@ -265,12 +288,17 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
buildingMins += amount / outputPerRun * (recipe.time_ms / 1000 / 60);
|
|
|
}
|
|
|
const numBuildings = buildingMins / (24*60) / 5 / 1.605; // one ship every 5 days, 160.5% efficiency
|
|
|
- const buildingRow = element('div', {className: 'building-row', textContent: `${formatFixed(numBuildings, 1)}x${building}`});
|
|
|
+ const consumablesCost = buildingDailyCost(buildings[building], prices) * Math.round(Math.max(1, numBuildings));
|
|
|
+ totalConsumablesCost += consumablesCost;
|
|
|
+ const buildingRow = element('div', {className: 'building-row',
|
|
|
+ textContent: `${formatFixed(numBuildings, 1)}x${building} (${formatWhole(consumablesCost)}/d)`});
|
|
|
buildingRow.append(buildingMats);
|
|
|
section.append(buildingRow);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ section.append(element('h4', {textContent: `total consumables cost: ${formatWhole(totalConsumablesCost)}/day,
|
|
|
+ ${formatWhole(totalConsumablesCost * 5)}/ship`}));
|
|
|
return section;
|
|
|
}
|
|
|
|
|
|
@@ -319,12 +347,18 @@ interface RawPrice {
|
|
|
ExchangeCode: string
|
|
|
Ask: number | null
|
|
|
AverageTraded30D: number | null
|
|
|
+ VWAP30D: number | null
|
|
|
Supply: number
|
|
|
}
|
|
|
|
|
|
interface Building {
|
|
|
building_ticker: string
|
|
|
expertise: string
|
|
|
+ pioneers: number
|
|
|
+ settlers: number
|
|
|
+ technicians: number
|
|
|
+ engineers: number
|
|
|
+ scientists: number
|
|
|
}
|
|
|
|
|
|
type PMMGCompanyProduction = Record<string, Record<string, {amount: number}>>;
|