فهرست منبع

gov: needs, base count, POPI

raylu 1 هفته پیش
والد
کامیت
aabb2c2f78
1فایلهای تغییر یافته به همراه107 افزوده شده و 3 حذف شده
  1. 107 3
      ts/gov.ts

+ 107 - 3
ts/gov.ts

@@ -42,8 +42,13 @@ async function render(planetName: string, pop: Pop) {
 	loader.style.display = 'block';
 	loader.style.display = 'block';
 	renderTarget.innerHTML = '';
 	renderTarget.innerHTML = '';
 
 
-	const planet: Planet = await cachedFetchJSON(
-			`https://api.fnar.net/planet/${encodeURIComponent(planetName)}?include_population_reports=true`);
+	const encodedPlanetName = encodeURIComponent(planetName);
+	const [planet, siteCounts, infras]: [Planet, SiteCount[], Infrastructure[]] = await Promise.all([
+		cachedFetchJSON(`https://api.fnar.net/planet/${encodedPlanetName}?include_population_reports=true`),
+		cachedFetchJSON(`https://api.fnar.net/planet/sitecount?planet=${encodedPlanetName}`),
+		cachedFetchJSON(`https://api.fnar.net/infrastructure?infrastructure=${encodedPlanetName}&include_upkeeps=true`)
+	]);
+
 	let lastPOPR = null;
 	let lastPOPR = null;
 	for (const report of planet.PopulationReports) {
 	for (const report of planet.PopulationReports) {
 		if (!lastPOPR || report.SimulationPeriod > lastPOPR.SimulationPeriod)
 		if (!lastPOPR || report.SimulationPeriod > lastPOPR.SimulationPeriod)
@@ -55,6 +60,17 @@ async function render(planetName: string, pop: Pop) {
 	}
 	}
 	const lastPOPRts = Math.floor(new Date(lastPOPR.ReportTimestamp).getTime() / 1000);
 	const lastPOPRts = Math.floor(new Date(lastPOPR.ReportTimestamp).getTime() / 1000);
 	const nextPOPRts = lastPOPRts + 7 * 24 * 60 * 60;
 	const nextPOPRts = lastPOPRts + 7 * 24 * 60 * 60;
+
+	const siteCount = siteCounts[0].Count;
+
+	const totalNeeds: Record<Need, number> = {
+		'safety': calcTotalNeeds(lastPOPR, 'safety'),
+		'health': calcTotalNeeds(lastPOPR, 'health'),
+		'comfort': calcTotalNeeds(lastPOPR, 'comfort'),
+		'culture': calcTotalNeeds(lastPOPR, 'culture'),
+		'education': calcTotalNeeds(lastPOPR, 'education'),
+	};
+
 	renderTarget.innerHTML = `last POPR: ${lastPOPR.ReportTimestamp} (${lastPOPRts})
 	renderTarget.innerHTML = `last POPR: ${lastPOPR.ReportTimestamp} (${lastPOPRts})
 	<br>next POPR: ${new Date(nextPOPRts * 1000).toISOString()} (${nextPOPRts})
 	<br>next POPR: ${new Date(nextPOPRts * 1000).toISOString()} (${nextPOPRts})
 
 
@@ -83,11 +99,51 @@ async function render(planetName: string, pop: Pop) {
 			<td>${unemployed(lastPOPR.NextPopulationEngineer, lastPOPR.PopulationDifferenceEngineer, lastPOPR.OpenJobsEngineer, lastPOPR.UnemploymentRateEngineer)}</td>
 			<td>${unemployed(lastPOPR.NextPopulationEngineer, lastPOPR.PopulationDifferenceEngineer, lastPOPR.OpenJobsEngineer, lastPOPR.UnemploymentRateEngineer)}</td>
 			<td>${unemployed(lastPOPR.NextPopulationScientist, lastPOPR.PopulationDifferenceScientist, lastPOPR.OpenJobsScientist, lastPOPR.UnemploymentRateScientist)}</td>
 			<td>${unemployed(lastPOPR.NextPopulationScientist, lastPOPR.PopulationDifferenceScientist, lastPOPR.OpenJobsScientist, lastPOPR.UnemploymentRateScientist)}</td>
 		</tr>
 		</tr>
-	</table>`;
+	</table>
+
+	<h2>needs</h2>
+	${siteCount} bases
+	<table>
+		<tr>
+			<th></th>
+			<th>safety</th>
+			<th>health</th>
+			<th>comfort</th>
+			<th>culture</th>
+			<th>education</th>
+		</tr>
+		<tr>
+			<td>last POPR</td>
+			<td>${formatPct(lastPOPR.NeedFulfillmentSafety)}</td>
+			<td>${formatPct(lastPOPR.NeedFulfillmentHealth)}</td>
+			<td>${formatPct(lastPOPR.NeedFulfillmentComfort)}</td>
+			<td>${formatPct(lastPOPR.NeedFulfillmentCulture)}</td>
+			<td>${formatPct(lastPOPR.NeedFulfillmentEducation)}</td>
+		</tr>
+		<tr>
+			<td>total needed</td>
+			<td>${formatNum(totalNeeds['safety'])}</td>
+			<td>${formatNum(totalNeeds['health'])}</td>
+			<td>${formatNum(totalNeeds['comfort'])}</td>
+			<td>${formatNum(totalNeeds['culture'])}</td>
+			<td>${formatNum(totalNeeds['education'])}</td>
+		</tr>
+	</table>
+
+	<h2>POPI</h2>
+	<table>
+		<tr>
+			${infras.map(infra => popiFilled(infra)).join('')}
+		</tr>
+	</table>
+	`;
 
 
 	loader.style.display = 'none';
 	loader.style.display = 'none';
 }
 }
 
 
+const formatNum = new Intl.NumberFormat(undefined, {maximumFractionDigits: 2}).format;
+const formatPct = new Intl.NumberFormat(undefined, {style: 'percent', maximumFractionDigits: 2}).format;
+
 function formatDelta(n: number, withPlus: boolean = true): string {
 function formatDelta(n: number, withPlus: boolean = true): string {
 	if (n > 0)
 	if (n > 0)
 		return `<span class="positive">${withPlus ? '+' : ''}${n}</span>`;
 		return `<span class="positive">${withPlus ? '+' : ''}${n}</span>`;
@@ -113,7 +169,40 @@ function unemployed(people: number, change: number, openJobs: number, unemployme
 	return formatDelta(Math.round(nextUnemployed), false);
 	return formatDelta(Math.round(nextUnemployed), false);
 }
 }
 
 
+function calcTotalNeeds(popReport: POPR, need: Need): number {
+	return popReport.NextPopulationPioneer * NEEDS.pio[need]
+		+ popReport.NextPopulationSettler * NEEDS.set[need]
+		+ popReport.NextPopulationTechnician * NEEDS.tec[need]
+		+ popReport.NextPopulationEngineer * NEEDS.eng[need]
+		+ popReport.NextPopulationScientist * NEEDS.sci[need];
+}
+
+function popiFilled(infra: Infrastructure): string {
+	if (infra.CurrentLevel === 0)
+		return '';
+
+	let filled = 0;
+	for (const upkeep of infra.Upkeeps) {
+		const nextConsumptionAmount = upkeep.StoreCapacity / 30 * upkeep.Duration; // # capacity is always 30 days
+		if (upkeep.Stored >= nextConsumptionAmount)
+			filled++;
+	}
+	return `<tr>
+		<td>${infra.Type} T${infra.CurrentLevel}</td>
+		<td>${filled}/${infra.Upkeeps.length}</td>
+	</tr>`;
+}
+
+const NEEDS: Record<Pop, Record<Need, number>> = {
+	'pio': {'safety': 0.25, 'health': 0.15, 'comfort': 0.03, 'culture': 0.02, 'education': 0.01},
+	'set': {'safety': 0.30, 'health': 0.20, 'comfort': 0.03, 'culture': 0.03, 'education': 0.03},
+	'tec': {'safety': 0.20, 'health': 0.30, 'comfort': 0.20, 'culture': 0.10, 'education': 0.05},
+	'eng': {'safety': 0.10, 'health': 0.15, 'comfort': 0.35, 'culture': 0.20, 'education': 0.10},
+	'sci': {'safety': 0.10, 'health': 0.10, 'comfort': 0.20, 'culture': 0.25, 'education': 0.30},
+}
+
 type Pop = 'pio' | 'set' | 'tec' | 'eng' | 'sci';
 type Pop = 'pio' | 'set' | 'tec' | 'eng' | 'sci';
+type Need = 'safety' | 'health' | 'comfort' | 'culture' | 'education';
 
 
 interface Planet {
 interface Planet {
 	PopulationReports: POPR[]
 	PopulationReports: POPR[]
@@ -122,6 +211,11 @@ interface Planet {
 interface POPR {
 interface POPR {
 	SimulationPeriod: number;
 	SimulationPeriod: number;
 	ReportTimestamp: string;
 	ReportTimestamp: string;
+	NeedFulfillmentSafety: number;
+	NeedFulfillmentHealth: number;
+	NeedFulfillmentComfort: number;
+	NeedFulfillmentCulture: number;
+	NeedFulfillmentEducation: number;
 	NextPopulationPioneer: number;
 	NextPopulationPioneer: number;
 	NextPopulationSettler: number;
 	NextPopulationSettler: number;
 	NextPopulationTechnician: number;
 	NextPopulationTechnician: number;
@@ -143,3 +237,13 @@ interface POPR {
 	UnemploymentRateEngineer: number;
 	UnemploymentRateEngineer: number;
 	UnemploymentRateScientist: number;
 	UnemploymentRateScientist: number;
 }
 }
+
+interface SiteCount {
+	Count: number;
+}
+
+interface Infrastructure {
+	Type: string;
+	CurrentLevel: number;
+	Upkeeps: {Stored: number, StoreCapacity: number, 'Duration': number}[];
+}