|
@@ -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}[];
|
|
|
|
|
+}
|