removing agent for a new system
This commit is contained in:
parent
ff89ec62fd
commit
872a70ad8a
6 changed files with 1422 additions and 149 deletions
|
|
@ -41,9 +41,9 @@ scale: null
|
||||||
};
|
};
|
||||||
const legendByMode = {
|
const legendByMode = {
|
||||||
terrain: () => terrainInfo.map(t => `<span><i style="background: rgb(${t.color.join(",")})"></i>${t.name}</span>`).join(""),
|
terrain: () => terrainInfo.map(t => `<span><i style="background: rgb(${t.color.join(",")})"></i>${t.name}</span>`).join(""),
|
||||||
ethnicity: () => "<span>Agent and city colors show lineage. Land remains terrain-colored.</span>",
|
ethnicity: () => "<span>Color = tile-level dominant ethnicity. Stronger color marks higher local population; mixed areas are muted.</span>",
|
||||||
pressure: () => "<span><i style=\"background:#d24943\"></i>High local population pressure</span>",
|
pressure: () => "<span><i style=\"background:#d24943\"></i>High local population pressure</span>",
|
||||||
polities: () => "<span>Color = city-centered state. Uncolored cities are independent.</span>",
|
polities: () => "<span>Color = territorial state control. Brighter areas are stronger control; pale borders mark frontiers; contested areas are unstable.</span>",
|
||||||
technology: () => "<span><i style=\"background:#5fab5b\"></i>Farming knowledge</span><span><i style=\"background:#caa966\"></i>Metallurgy knowledge</span>",
|
technology: () => "<span><i style=\"background:#5fab5b\"></i>Farming knowledge</span><span><i style=\"background:#caa966\"></i>Metallurgy knowledge</span>",
|
||||||
pheromone: () => "<span><i style=\"background:#d8b156\"></i>Pheromone strength</span><span><i style=\"background:#ffd75a\"></i>Formal trade routes</span>",
|
pheromone: () => "<span><i style=\"background:#d8b156\"></i>Pheromone strength</span><span><i style=\"background:#ffd75a\"></i>Formal trade routes</span>",
|
||||||
resources: () => "<span><i style=\"background:#6bbc55\"></i>Regenerating local resource stock</span>"
|
resources: () => "<span><i style=\"background:#6bbc55\"></i>Regenerating local resource stock</span>"
|
||||||
|
|
|
||||||
32
config.js
32
config.js
|
|
@ -36,9 +36,9 @@ maxCities: 300,
|
||||||
agingStartYears: 180,
|
agingStartYears: 180,
|
||||||
agingDecayPerYear: 0.002,
|
agingDecayPerYear: 0.002,
|
||||||
agingOverCapAttrition: 0.035,
|
agingOverCapAttrition: 0.035,
|
||||||
agingRestoreBudgetShare: 0.18,
|
agingRestoreMinWear: 0.08,
|
||||||
agingRestoreCostPerWear: 70,
|
agingRestoreCostPerWear: 70,
|
||||||
agingRestoreMinWear: 0.08
|
agingRestoreBudgetShare: 0.18
|
||||||
}),
|
}),
|
||||||
render: Object.freeze({
|
render: Object.freeze({
|
||||||
graphThrottleMs: 1400,
|
graphThrottleMs: 1400,
|
||||||
|
|
@ -55,12 +55,34 @@ polity: Object.freeze({
|
||||||
minimumPerCapitaFood: 0.06,
|
minimumPerCapitaFood: 0.06,
|
||||||
logisticsDistance: 34,
|
logisticsDistance: 34,
|
||||||
charismaMin: 0.5,
|
charismaMin: 0.5,
|
||||||
charismaMax: 3.0,
|
charismaMax: 1.5,
|
||||||
charismaAverage: 1.0
|
charismaAverage: 1.0,
|
||||||
|
leaderTenureMinYears: 22,
|
||||||
|
leaderTenureMaxYears: 72
|
||||||
|
}),
|
||||||
|
campaign: Object.freeze({
|
||||||
|
checkIntervalYears: 3,
|
||||||
|
baseChance: 0.18,
|
||||||
|
minCooldownYears: 12,
|
||||||
|
maxCooldownYears: 45,
|
||||||
|
maxActiveCampaigns: 12,
|
||||||
|
frontierTargetChance: 0.35,
|
||||||
|
nonStateTargetChance: 0.30,
|
||||||
|
independentCityTargetChance: 0.20,
|
||||||
|
polityTargetChance: 0.15,
|
||||||
|
baseDurationYears: 8,
|
||||||
|
maxDurationYears: 25,
|
||||||
|
claimRadius: 8,
|
||||||
|
maxTargetDistance: 48
|
||||||
}),
|
}),
|
||||||
polityAccess: Object.freeze({
|
polityAccess: Object.freeze({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
noAccessPenalty: 0.17
|
maxSearchDepth: 8,
|
||||||
|
indirectPenalty: 0.012,
|
||||||
|
perHopPenalty: 0.006,
|
||||||
|
noAccessPenalty: 0.045,
|
||||||
|
lowLoyaltyTransitPenalty: 0.012,
|
||||||
|
foreignTransitPenalty: 0.008
|
||||||
}),
|
}),
|
||||||
culture: Object.freeze({
|
culture: Object.freeze({
|
||||||
spreadRadius: 3,
|
spreadRadius: 3,
|
||||||
|
|
|
||||||
176
render.js
176
render.js
|
|
@ -10,7 +10,14 @@ const image = renderImage;
|
||||||
const data = image.data;
|
const data = image.data;
|
||||||
const mode = els.viewMode.value;
|
const mode = els.viewMode.value;
|
||||||
const cityPolityColor = new Map();
|
const cityPolityColor = new Map();
|
||||||
|
const polityColors = new Map();
|
||||||
if (mode === "polities") {
|
if (mode === "polities") {
|
||||||
|
for (const polity of sim.polities) {
|
||||||
|
polityColors.set(polity.id, {
|
||||||
|
color: polity.color,
|
||||||
|
isGraphHover: graphState.hoverPolityId === polity.id
|
||||||
|
});
|
||||||
|
}
|
||||||
for (const city of sim.cities) {
|
for (const city of sim.cities) {
|
||||||
if (city.polityId === null) continue;
|
if (city.polityId === null) continue;
|
||||||
const polity = cityPolity(city);
|
const polity = cityPolity(city);
|
||||||
|
|
@ -29,15 +36,34 @@ const v = clamp(w.resource[i] / 30, 0, 1);
|
||||||
color = mix([28, 36, 40], [107, 188, 85], v);
|
color = mix([28, 36, 40], [107, 188, 85], v);
|
||||||
} else if (mode === "ethnicity") {
|
} else if (mode === "ethnicity") {
|
||||||
color = terrainInfo[w.terrain[i]].color;
|
color = terrainInfo[w.terrain[i]].color;
|
||||||
|
const dominant = w.dominantEthnicity?.[i] ?? -1;
|
||||||
|
const ethnicityColor = dominant >= 0 ? sim.ethnicities.get(dominant)?.color : null;
|
||||||
|
if (ethnicityColor) {
|
||||||
|
const populationStrength = clamp(Math.sqrt(Math.max(0, w.population?.[i] || w.pressure[i] || 0)) / 16, 0.18, 0.68);
|
||||||
|
const diversityDimming = clamp(w.cultureDiversity?.[i] || 0, 0, 1) * 0.18;
|
||||||
|
color = mix(color, ethnicityColor, clamp(populationStrength - diversityDimming, 0.12, 0.64));
|
||||||
|
}
|
||||||
if (w.city[i] >= 0) {
|
if (w.city[i] >= 0) {
|
||||||
const city = sim.getCityById(w.city[i]);
|
const city = sim.getCityById(w.city[i]);
|
||||||
if (city) color = mix(color, cityMajorityColor(city), 0.72);
|
if (city) color = mix(color, cityMajorityColor(city), 0.72);
|
||||||
}
|
}
|
||||||
} else if (mode === "pressure") {
|
} else if (mode === "pressure") {
|
||||||
const v = clamp(w.pressure[i] / 12, 0, 1);
|
const pressure = w.populationPressure?.[i] ?? (w.pressure[i] / 12);
|
||||||
|
const v = clamp(pressure, 0, 1.6) / 1.6;
|
||||||
color = mix(terrainInfo[w.terrain[i]].color, [210, 73, 67], v);
|
color = mix(terrainInfo[w.terrain[i]].color, [210, 73, 67], v);
|
||||||
} else if (mode === "polities") {
|
} else if (mode === "polities") {
|
||||||
color = terrainInfo[w.terrain[i]].color;
|
color = terrainInfo[w.terrain[i]].color;
|
||||||
|
const territory = w.polity?.[i] ?? -1;
|
||||||
|
if (territory >= 0) {
|
||||||
|
const polityColor = polityColors.get(territory);
|
||||||
|
if (polityColor) {
|
||||||
|
const strength = clamp(0.16 + (w.control?.[i] || 0) * 0.38, 0.18, 0.55);
|
||||||
|
color = mix(color, polityColor.color, polityColor.isGraphHover ? Math.min(0.72, strength + 0.18) : strength);
|
||||||
|
if (w.contested?.[i]) color = mix(color, [128, 74, 50], 0.30);
|
||||||
|
if (sim.isBorderTile?.(i)) color = mix(color, [232, 225, 185], 0.22);
|
||||||
|
if (graphState.hoverPolityId !== null && !polityColor.isGraphHover) color = mix(color, [16, 18, 19], 0.30);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (w.city[i] >= 0) {
|
if (w.city[i] >= 0) {
|
||||||
const polityColor = cityPolityColor.get(w.city[i]);
|
const polityColor = cityPolityColor.get(w.city[i]);
|
||||||
if (polityColor) {
|
if (polityColor) {
|
||||||
|
|
@ -62,6 +88,7 @@ data[p + 3] = 255;
|
||||||
ctx.putImageData(image, 0, 0);
|
ctx.putImageData(image, 0, 0);
|
||||||
renderDisasters();
|
renderDisasters();
|
||||||
drawTradeLinks();
|
drawTradeLinks();
|
||||||
|
drawCampaigns();
|
||||||
drawAgentsAndCities(mode);
|
drawAgentsAndCities(mode);
|
||||||
drawGraphPolityHighlight();
|
drawGraphPolityHighlight();
|
||||||
maybeRenderStateGraph();
|
maybeRenderStateGraph();
|
||||||
|
|
@ -137,6 +164,27 @@ ctx.fillRect(tile % w.size, Math.floor(tile / w.size), 1, 1);
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
function drawCampaigns() {
|
||||||
|
if (!sim?.campaigns?.length) return;
|
||||||
|
const w = sim.world;
|
||||||
|
ctx.save();
|
||||||
|
for (const campaign of sim.campaigns) {
|
||||||
|
if (campaign.status !== "active" || campaign.targetTile == null) continue;
|
||||||
|
const x = campaign.targetTile % w.size;
|
||||||
|
const y = Math.floor(campaign.targetTile / w.size);
|
||||||
|
const age = Math.max(0, sim.year - campaign.started) / Math.max(1, campaign.expires - campaign.started);
|
||||||
|
const pulse = 0.5 + Math.sin(age * Math.PI * 8) * 0.5;
|
||||||
|
const radius = 2.5 + clamp(campaign.progress, 0, 1) * 4 + pulse * 1.5;
|
||||||
|
ctx.globalAlpha = 0.55;
|
||||||
|
ctx.strokeStyle = campaign.type === "border_war" ? "rgba(236, 126, 111, 0.95)" : "rgba(239, 211, 119, 0.95)";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
strokeCircle(ctx, x + 0.5, y + 0.5, radius);
|
||||||
|
ctx.globalAlpha = 0.22;
|
||||||
|
ctx.fillStyle = campaign.type === "nonstate_subjugation" ? "rgba(180, 111, 73, 0.9)" : "rgba(239, 211, 119, 0.9)";
|
||||||
|
fillSquare(ctx, x, y, 1);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
function drawAgentsAndCities(mode) {
|
function drawAgentsAndCities(mode) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
if (mode !== "ethnicity") {
|
if (mode !== "ethnicity") {
|
||||||
|
|
@ -161,12 +209,8 @@ ctx.fillStyle = rgba(color, clamp(0.32 + tech * 0.68, 0.32, 1));
|
||||||
ctx.fillRect(a.x, a.y, 1, 1);
|
ctx.fillRect(a.x, a.y, 1, 1);
|
||||||
}
|
}
|
||||||
} else if (mode === "ethnicity") {
|
} else if (mode === "ethnicity") {
|
||||||
for (const a of sim.agents) {
|
// Phase 2: ethnicity view is driven by tile-level population/culture fields.
|
||||||
const e = sim.ethnicities.get(a.ethnicity);
|
// Individual agents remain in the simulation but no longer define this layer.
|
||||||
if (!e) continue;
|
|
||||||
ctx.fillStyle = rgb(e.color);
|
|
||||||
ctx.fillRect(a.x, a.y, 1, 1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = "#eeeccf";
|
ctx.fillStyle = "#eeeccf";
|
||||||
for (const tile of sim.tileAgents.keys()) {
|
for (const tile of sim.tileAgents.keys()) {
|
||||||
|
|
@ -273,7 +317,11 @@ const waterInfluence = waterInfluenceAt(w, x, y).toFixed(2);
|
||||||
const cityEthnicity = city ? dominantComposition(city.ethnicityComposition) : null;
|
const cityEthnicity = city ? dominantComposition(city.ethnicityComposition) : null;
|
||||||
const mismatch = agent ? sim.climateMismatch(agent.ethnicity, i) : 0;
|
const mismatch = agent ? sim.climateMismatch(agent.ethnicity, i) : 0;
|
||||||
const polity = cityPolity(city);
|
const polity = cityPolity(city);
|
||||||
|
const territoryId = w.polity?.[i] ?? -1;
|
||||||
|
const territoryPolity = territoryId >= 0 ? sim.getPolityById(territoryId) : null;
|
||||||
const regionalEthnicity = w.dominantEthnicity[i] >= 0 ? `E${w.dominantEthnicity[i]}` : "-";
|
const regionalEthnicity = w.dominantEthnicity[i] >= 0 ? `E${w.dominantEthnicity[i]}` : "-";
|
||||||
|
const tilePopulation = w.population?.[i] || 0;
|
||||||
|
const nearbyCampaigns = activeCampaignsNear(i);
|
||||||
els.tooltip.innerHTML = `
|
els.tooltip.innerHTML = `
|
||||||
<strong>${city ? `City #${city.id}` : agent ? "Agent group" : terrain.name}</strong>
|
<strong>${city ? `City #${city.id}` : agent ? "Agent group" : terrain.name}</strong>
|
||||||
${tooltipSection("Tile", [
|
${tooltipSection("Tile", [
|
||||||
|
|
@ -284,22 +332,30 @@ els.tooltip.innerHTML = `
|
||||||
tooltipRow("Minerals", w.mineral[i].toFixed(2)),
|
tooltipRow("Minerals", w.mineral[i].toFixed(2)),
|
||||||
tooltipRow("Temp / humid", `${w.temperature[i].toFixed(2)} / ${w.humidity[i].toFixed(2)}`),
|
tooltipRow("Temp / humid", `${w.temperature[i].toFixed(2)} / ${w.humidity[i].toFixed(2)}`),
|
||||||
tooltipRow("Water", waterInfluence),
|
tooltipRow("Water", waterInfluence),
|
||||||
tooltipRow("Pressure", w.pressure[i].toFixed(0)),
|
tooltipRow("Pressure", (w.populationPressure?.[i] ?? 0).toFixed(2)),
|
||||||
|
tooltipRow("Capacity", (w.populationCapacity?.[i] ?? 0).toFixed(1)),
|
||||||
tooltipRow("Pheromone", w.pheromone[i].toFixed(1)),
|
tooltipRow("Pheromone", w.pheromone[i].toFixed(1)),
|
||||||
tooltipRow("Route", w.tradeRoute[i], w.tradeRoute[i])
|
tooltipRow("Route", w.tradeRoute[i], w.tradeRoute[i])
|
||||||
])}
|
])}
|
||||||
|
${tooltipSection("Territory", [
|
||||||
|
tooltipRow("Territory", territoryPolity ? `#${territoryPolity.id}` : "Unclaimed"),
|
||||||
|
tooltipRow("Control", `${Math.round((w.control?.[i] || 0) * 100)}%`),
|
||||||
|
tooltipRow("Contested", w.contested?.[i] ? "yes" : "no"),
|
||||||
|
tooltipRow("Campaign", nearbyCampaigns, nearbyCampaigns),
|
||||||
|
tooltipRow("Dominant ethnicity", regionalEthnicity),
|
||||||
|
tooltipRow("Tile population", tilePopulation >= 10 ? Math.round(tilePopulation).toLocaleString() : tilePopulation.toFixed(1))
|
||||||
|
])}
|
||||||
${tooltipSection("City & State", [
|
${tooltipSection("City & State", [
|
||||||
tooltipRow("Population", city?.population.toLocaleString(), city),
|
tooltipRow("Population", city?.population.toLocaleString(), city),
|
||||||
tooltipRow("Age cap", city ? `${Math.floor(sim.cityAgePopulationLimit(city)).toLocaleString()} / ${Math.round((city.ageWear || 0) * 100)}% wear` : "", city),
|
|
||||||
tooltipRow("Food stock", city?.storedResources.toFixed(1), city),
|
tooltipRow("Food stock", city?.storedResources.toFixed(1), city),
|
||||||
tooltipRow("Supply stress", (city?.supplyStress || 0).toFixed(2), city),
|
tooltipRow("Supply stress", (city?.supplyStress || 0).toFixed(2), city),
|
||||||
tooltipRow("Trade", city ? `${city.tradeLinks.size} links, ${(city.tradeValue || 0).toFixed(2)} value` : "", city),
|
tooltipRow("Trade", city ? `${city.tradeLinks.size} links, ${(city.tradeValue || 0).toFixed(2)} value` : "", city),
|
||||||
tooltipRow("Knowledge", city ? `${knowledgeLevel(city, "farming").toFixed(2)} farm / ${knowledgeLevel(city, "metallurgy").toFixed(2)} metal` : "", city),
|
tooltipRow("Knowledge", city ? `${knowledgeLevel(city, "farming").toFixed(2)} farm / ${knowledgeLevel(city, "metallurgy").toFixed(2)} metal` : "", city),
|
||||||
tooltipRow("City majority", cityEthnicity != null ? `E${cityEthnicity}` : "", cityEthnicity != null),
|
tooltipRow("City majority", `E${cityEthnicity}`, cityEthnicity),
|
||||||
city ? cityEthnicityPie(city) : "",
|
city ? cityEthnicityPie(city) : "",
|
||||||
tooltipRow("State", polity ? `#${polity.id}` : "Independent", city),
|
tooltipRow("State", polity ? `#${polity.id}` : "Independent", city),
|
||||||
tooltipRow("Loyalty", city?.loyalty.toFixed(2), city),
|
tooltipRow("Loyalty", city?.loyalty.toFixed(2), city),
|
||||||
tooltipRow("Charisma", clamp(polity?.charisma ?? 1, SimConfig.polity?.charismaMin ?? 0.5, SimConfig.polity?.charismaMax ?? 3.0).toFixed(2), polity),
|
tooltipRow("Charisma", clamp(polity?.charisma ?? 1, 0.5, 1.5).toFixed(2), polity),
|
||||||
tooltipRow("Leader tenure", polity ? `${Math.floor((sim.year - (polity.leaderStarted ?? polity.founded ?? sim.year)) / WEEKS_PER_YEAR)}y` : "", polity),
|
tooltipRow("Leader tenure", polity ? `${Math.floor((sim.year - (polity.leaderStarted ?? polity.founded ?? sim.year)) / WEEKS_PER_YEAR)}y` : "", polity),
|
||||||
tooltipRow("Treasury", polity?.treasury.toFixed(1), polity),
|
tooltipRow("Treasury", polity?.treasury.toFixed(1), polity),
|
||||||
tooltipRow("Capital", polity && city ? polity.centerCityId === city.id ? "yes" : "no" : "", polity)
|
tooltipRow("Capital", polity && city ? polity.centerCityId === city.id ? "yes" : "no" : "", polity)
|
||||||
|
|
@ -329,44 +385,33 @@ els.tooltip.style.left = `${clamp(left, margin, Math.max(margin, hoverState.widt
|
||||||
els.tooltip.style.top = `${clamp(top, margin, Math.max(margin, hoverState.height - height - margin))}px`;
|
els.tooltip.style.top = `${clamp(top, margin, Math.max(margin, hoverState.height - height - margin))}px`;
|
||||||
}
|
}
|
||||||
function cityEthnicityPie(city) {
|
function cityEthnicityPie(city) {
|
||||||
if (!city) return "";
|
if (!city?.ethnicityComposition?.size) return "";
|
||||||
let entries = [...(city.ethnicityComposition || new Map())]
|
const entries = [...city.ethnicityComposition]
|
||||||
.map(([id, count]) => [id, Number(count)])
|
.filter(([, count]) => count > 0)
|
||||||
.filter(([, count]) => Number.isFinite(count) && count > 0)
|
|
||||||
.sort((a, b) => b[1] - a[1]);
|
.sort((a, b) => b[1] - a[1]);
|
||||||
if (!entries.length && city.population > 0) {
|
|
||||||
const tile = sim.world.idx(city.x, city.y);
|
|
||||||
const fallbackId = sim.world.dominantEthnicity[tile] > 0 ? sim.world.dominantEthnicity[tile] : null;
|
|
||||||
if (fallbackId != null) entries = [[fallbackId, city.population]];
|
|
||||||
}
|
|
||||||
const total = entries.reduce((sum, [, count]) => sum + count, 0);
|
const total = entries.reduce((sum, [, count]) => sum + count, 0);
|
||||||
if (total <= 0) return "";
|
if (total <= 0) return "";
|
||||||
const top = entries.slice(0, 5);
|
const top = entries.slice(0, 5);
|
||||||
const other = entries.slice(5).reduce((sum, [, count]) => sum + count, 0);
|
const other = entries.slice(5).reduce((sum, [, count]) => sum + count, 0);
|
||||||
const slices = other > 0 ? [...top, [0, other]] : top;
|
const slices = other > 0 ? [...top, [0, other]] : top;
|
||||||
const sliceColor = id => {
|
|
||||||
const ethnicity = id !== 0 ? sim.ethnicities.get(id) : null;
|
|
||||||
return ethnicity?.color || [122, 130, 126];
|
|
||||||
};
|
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
const gradient = slices.map(([id, count]) => {
|
const gradient = slices.map(([id, count]) => {
|
||||||
const start = cursor / total * 100;
|
const start = cursor / total * 100;
|
||||||
cursor += count;
|
cursor += count;
|
||||||
const end = cursor / total * 100;
|
const end = cursor / total * 100;
|
||||||
const color = sliceColor(id);
|
const ethnicity = id ? sim.ethnicities.get(id) : null;
|
||||||
|
const color = ethnicity?.color || [122, 130, 126];
|
||||||
return `rgb(${color.join(",")}) ${start.toFixed(2)}% ${end.toFixed(2)}%`;
|
return `rgb(${color.join(",")}) ${start.toFixed(2)}% ${end.toFixed(2)}%`;
|
||||||
}).join(", ");
|
}).join(", ");
|
||||||
const labels = slices.map(([id, count]) => {
|
const labels = slices.map(([id, count]) => {
|
||||||
const color = sliceColor(id);
|
const ethnicity = id ? sim.ethnicities.get(id) : null;
|
||||||
const label = id !== 0 ? `E${id}` : "Other";
|
const color = ethnicity?.color || [122, 130, 126];
|
||||||
|
const label = id ? `E${id}` : "Other";
|
||||||
return `<span class="ethnicity-pie-key"><i style="background: rgb(${color.join(",")})"></i>${label} ${Math.round(count / total * 100)}%</span>`;
|
return `<span class="ethnicity-pie-key"><i style="background: rgb(${color.join(",")})"></i>${label} ${Math.round(count / total * 100)}%</span>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
const background = slices.length === 1
|
|
||||||
? `rgb(${sliceColor(slices[0][0]).join(",")})`
|
|
||||||
: `conic-gradient(${gradient})`;
|
|
||||||
return `
|
return `
|
||||||
<div class="ethnicity-pie-wrap">
|
<div class="ethnicity-pie-wrap">
|
||||||
<div class="ethnicity-pie" style="background: ${background}"></div>
|
<div class="ethnicity-pie" style="background: conic-gradient(${gradient})"></div>
|
||||||
<div class="ethnicity-pie-labels">${labels}</div>
|
<div class="ethnicity-pie-labels">${labels}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -375,6 +420,21 @@ function hideTooltip() {
|
||||||
hoverState = null;
|
hoverState = null;
|
||||||
els.tooltip.hidden = true;
|
els.tooltip.hidden = true;
|
||||||
}
|
}
|
||||||
|
function activeCampaignsNear(tile) {
|
||||||
|
if (!sim?.campaigns?.length) return "";
|
||||||
|
const w = sim.world;
|
||||||
|
const x = tile % w.size;
|
||||||
|
const y = Math.floor(tile / w.size);
|
||||||
|
const labels = [];
|
||||||
|
for (const campaign of sim.campaigns) {
|
||||||
|
if (campaign.status !== "active" || campaign.targetTile == null) continue;
|
||||||
|
const cx = campaign.targetTile % w.size;
|
||||||
|
const cy = Math.floor(campaign.targetTile / w.size);
|
||||||
|
if (Math.abs(cx - x) + Math.abs(cy - y) > 3) continue;
|
||||||
|
labels.push(`${campaign.type.replaceAll("_", " ")} ${Math.round(clamp(campaign.progress, 0, 1) * 100)}%`);
|
||||||
|
}
|
||||||
|
return labels.slice(0, 2).join("; ");
|
||||||
|
}
|
||||||
function findAgentAt(x, y) {
|
function findAgentAt(x, y) {
|
||||||
const directIndex = sim.world.idx(x, y);
|
const directIndex = sim.world.idx(x, y);
|
||||||
return sim.tileAgents.get(directIndex)?.[0] || null;
|
return sim.tileAgents.get(directIndex)?.[0] || null;
|
||||||
|
|
@ -797,6 +857,60 @@ label: "new ethnicity",
|
||||||
color: "rgba(105, 181, 120, 0.95)",
|
color: "rgba(105, 181, 120, 0.95)",
|
||||||
draw: drawGraphDiamond,
|
draw: drawGraphDiamond,
|
||||||
summary: data => `E${data.ethnicity ?? "-"}, ${Math.round(data.population || 0).toLocaleString()} frontier groups`
|
summary: data => `E${data.ethnicity ?? "-"}, ${Math.round(data.population || 0).toLocaleString()} frontier groups`
|
||||||
|
},
|
||||||
|
campaign: {
|
||||||
|
label: "campaign",
|
||||||
|
color: "rgba(232, 205, 116, 0.95)",
|
||||||
|
draw: drawGraphTriangleUp,
|
||||||
|
summary: data => `${data.outcome || "event"} ${data.campaignType || data.targetType || "target"}`
|
||||||
|
},
|
||||||
|
expansion: {
|
||||||
|
label: "expansion",
|
||||||
|
color: "rgba(210, 194, 104, 0.95)",
|
||||||
|
draw: drawGraphDiamond,
|
||||||
|
summary: data => `${data.outcome || "event"} ${data.campaignType || "frontier"}, tile ${data.targetTile ?? "-"}`
|
||||||
|
},
|
||||||
|
colonization: {
|
||||||
|
label: "colonization",
|
||||||
|
color: "rgba(232, 205, 116, 0.95)",
|
||||||
|
draw: drawGraphDiamond,
|
||||||
|
summary: data => `${data.campaignType || "frontier"}, progress ${Math.round((data.progress || 0) * 100)}%`
|
||||||
|
},
|
||||||
|
subjugation: {
|
||||||
|
label: "subjugation",
|
||||||
|
color: "rgba(196, 122, 77, 0.95)",
|
||||||
|
draw: drawGraphTriangleUp,
|
||||||
|
summary: data => `${data.campaignType || "non-state"}, tile ${data.targetTile ?? "-"}`
|
||||||
|
},
|
||||||
|
annexation: {
|
||||||
|
label: "annexation",
|
||||||
|
color: "rgba(227, 179, 65, 0.95)",
|
||||||
|
draw: drawGraphDiamond,
|
||||||
|
summary: data => `city #${data.targetCityId ?? "-"}`
|
||||||
|
},
|
||||||
|
failedCampaign: {
|
||||||
|
label: "failed campaign",
|
||||||
|
color: "rgba(151, 110, 91, 0.95)",
|
||||||
|
draw: drawGraphTriangleDown,
|
||||||
|
summary: data => `${data.outcome || "failed"} ${data.campaignType || "campaign"}`
|
||||||
|
},
|
||||||
|
borderWar: {
|
||||||
|
label: "border war",
|
||||||
|
color: "rgba(236, 126, 111, 0.95)",
|
||||||
|
draw: drawGraphTriangleUp,
|
||||||
|
summary: data => `${data.outcome || "war"} vs S${data.targetPolityId ?? "-"}`
|
||||||
|
},
|
||||||
|
war: {
|
||||||
|
label: "war",
|
||||||
|
color: "rgba(202, 87, 78, 0.95)",
|
||||||
|
draw: drawGraphTriangleUp,
|
||||||
|
summary: data => `${data.outcome || "war"} ${data.campaignType || ""}`
|
||||||
|
},
|
||||||
|
collapse: {
|
||||||
|
label: "collapse",
|
||||||
|
color: "rgba(130, 136, 139, 0.95)",
|
||||||
|
draw: drawGraphTriangleDown,
|
||||||
|
summary: data => data.reason || "state ended"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function graphEventLabel(event) {
|
function graphEventLabel(event) {
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,15 @@ canvas#world {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip .status,
|
||||||
|
.tooltip .warning {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip .warning em {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
.ethnicity-pie-wrap {
|
.ethnicity-pie-wrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 44px 1fr;
|
grid-template-columns: 44px 1fr;
|
||||||
|
|
|
||||||
1
utils.js
1
utils.js
|
|
@ -77,7 +77,6 @@ function dominantComposition(composition) {
|
||||||
let bestId = null;
|
let bestId = null;
|
||||||
let bestCount = 0;
|
let bestCount = 0;
|
||||||
for (const [id, count] of composition) {
|
for (const [id, count] of composition) {
|
||||||
if (!Number.isFinite(count) || count <= 0) continue;
|
|
||||||
if (count > bestCount) {
|
if (count > bestCount) {
|
||||||
bestId = id;
|
bestId = id;
|
||||||
bestCount = count;
|
bestCount = count;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue