:)
This commit is contained in:
parent
7fa3696d27
commit
8ff52b3fbd
2 changed files with 69 additions and 43 deletions
106
script.js
106
script.js
|
|
@ -668,8 +668,8 @@ class Simulation {
|
|||
a.farmingWork = 0;
|
||||
a.lastFarmTile = to;
|
||||
const depositScale = w.terrain[to] === Terrain.WATER ? 0.35 : 1;
|
||||
w.pheromone[from] += (w.tradeRoute[from] ? 0.08 : 0.22) * depositScale;
|
||||
w.pheromone[to] += (w.tradeRoute[to] ? 0.06 : 0.15) * depositScale;
|
||||
w.pheromone[from] += (w.tradeRoute[from] ? 0.14 : 0.42) * depositScale;
|
||||
w.pheromone[to] += (w.tradeRoute[to] ? 0.10 : 0.30) * depositScale;
|
||||
a.settled = Math.max(0, a.settled - 1);
|
||||
} else {
|
||||
a.movedThisStep = false;
|
||||
|
|
@ -720,6 +720,7 @@ class Simulation {
|
|||
if (farming <= 0.02) return;
|
||||
if (agent.settled > 0) {
|
||||
agent.farmingWork = clamp((agent.farmingWork || 0) + farming * 0.015, 0, 1);
|
||||
agent.tech.farming = clamp(agent.tech.farming + farming * this.farmabilityAt(tile) * 0.00018, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -729,7 +730,7 @@ class Simulation {
|
|||
const settledFactor = clamp((agent.settled || 0) / 12, 0, 1);
|
||||
|
||||
if ((agent.tech?.farming || 0) <= 0.02) {
|
||||
const farmingBase = 0.000090;
|
||||
const farmingBase = 0.00016;
|
||||
const farmability = this.farmabilityAt(tile);
|
||||
const sedentary = getSedentary(agent.traits);
|
||||
const farmingChance =
|
||||
|
|
@ -737,11 +738,11 @@ class Simulation {
|
|||
(0.25 + farmability) *
|
||||
(0.4 + sedentary) *
|
||||
(0.3 + settledFactor);
|
||||
if (this.rng.next() < farmingChance) this.grantTechnologyAround(agent, "farming", 0.24);
|
||||
if (this.rng.next() < farmingChance) this.grantTechnologyAround(agent, "farming", 0.32);
|
||||
}
|
||||
|
||||
if ((agent.tech?.metallurgy || 0) <= 0.02) {
|
||||
const metallurgyBase = 0.000055;
|
||||
const metallurgyBase = 0.00011;
|
||||
const mineral = clamp(w.mineral[tile], 0, 1);
|
||||
const mineralTerrainBonus =
|
||||
w.terrain[tile] === Terrain.MINERAL || w.terrain[tile] === Terrain.MOUNTAIN
|
||||
|
|
@ -752,7 +753,7 @@ class Simulation {
|
|||
(0.2 + mineral) *
|
||||
mineralTerrainBonus *
|
||||
(0.5 + settledFactor);
|
||||
if (this.rng.next() < metallurgyChance) this.grantTechnologyAround(agent, "metallurgy", 0.20);
|
||||
if (this.rng.next() < metallurgyChance) this.grantTechnologyAround(agent, "metallurgy", 0.28);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -773,7 +774,7 @@ class Simulation {
|
|||
this.ensureAgentTech(agent);
|
||||
const farming = agent.tech.farming || 0;
|
||||
const metallurgy = agent.tech.metallurgy || 0;
|
||||
const cost = farming * 0.010 + metallurgy * 0.022;
|
||||
const cost = farming * 0.004 + metallurgy * 0.010;
|
||||
if (cost <= 0) return;
|
||||
|
||||
if (agent.resources >= cost) {
|
||||
|
|
@ -783,7 +784,7 @@ class Simulation {
|
|||
|
||||
const shortage = cost - Math.max(0, agent.resources);
|
||||
agent.resources = Math.max(0, agent.resources - cost);
|
||||
const decay = clamp(0.015 + shortage * 0.04, 0.015, 0.12);
|
||||
const decay = clamp(0.006 + shortage * 0.025, 0.006, 0.06);
|
||||
agent.tech.farming = Math.max(0, farming - decay);
|
||||
agent.tech.metallurgy = Math.max(0, metallurgy - decay * 1.15);
|
||||
if (agent.tech.farming < 0.02) agent.tech.farming = 0;
|
||||
|
|
@ -810,7 +811,7 @@ class Simulation {
|
|||
ethnocentrism * 0.0012;
|
||||
|
||||
if (sameEthnicity) chance += 0.004;
|
||||
if (w.tradeRoute[tile]) chance += 0.003;
|
||||
if (w.tradeRoute[tile]) chance += 0.006;
|
||||
if (w.city[tile] >= 0) chance += 0.003;
|
||||
chance = clamp(chance, 0.0008, 0.028);
|
||||
|
||||
|
|
@ -850,14 +851,14 @@ class Simulation {
|
|||
}
|
||||
}
|
||||
|
||||
if (farmingCount >= 3) {
|
||||
if (farmingCount >= 2) {
|
||||
const avgFarming = farmingSum / farmingCount;
|
||||
agent.tech.farming = clamp(agent.tech.farming + 0.0012 * farmingCount * avgFarming, 0, 1);
|
||||
agent.tech.farming = clamp(agent.tech.farming + 0.0024 * farmingCount * avgFarming, 0, 1);
|
||||
}
|
||||
|
||||
if (metallurgyCount >= 3) {
|
||||
if (metallurgyCount >= 2) {
|
||||
const avgMetallurgy = metallurgySum / metallurgyCount;
|
||||
agent.tech.metallurgy = clamp(agent.tech.metallurgy + 0.00085 * metallurgyCount * avgMetallurgy, 0, 1);
|
||||
agent.tech.metallurgy = clamp(agent.tech.metallurgy + 0.0018 * metallurgyCount * avgMetallurgy, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -885,6 +886,9 @@ class Simulation {
|
|||
|
||||
metallurgyGatherMultiplier(agent, tile) {
|
||||
const metallurgyLevel = agent.tech?.metallurgy || 0;
|
||||
if (metallurgyLevel > 0.02) {
|
||||
agent.tech.metallurgy = clamp(agent.tech.metallurgy + metallurgyLevel * clamp(this.world.mineral[tile], 0, 1) * 0.00012, 0, 1);
|
||||
}
|
||||
return 1 + metallurgyLevel * clamp(this.world.mineral[tile], 0, 1);
|
||||
}
|
||||
|
||||
|
|
@ -929,7 +933,10 @@ class Simulation {
|
|||
const childResources = a.resources * childShare;
|
||||
a.resources -= childResources;
|
||||
const childTraits = mutateTraits(a.traits, this.rng, 0.035);
|
||||
offspring.push(this.makeAgent(a.x, a.y, a.ethnicity, childTraits, childResources));
|
||||
const child = this.makeAgent(a.x, a.y, a.ethnicity, childTraits, childResources);
|
||||
child.tech.farming = clamp((a.tech?.farming || 0) * this.rng.range(0.75, 0.95), 0, 1);
|
||||
child.tech.metallurgy = clamp((a.tech?.metallurgy || 0) * this.rng.range(0.70, 0.92), 0, 1);
|
||||
offspring.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1029,7 +1036,7 @@ class Simulation {
|
|||
const w = this.world;
|
||||
for (let i = 0; i < w.count; i++) {
|
||||
w.resource[i] = Math.min(58, w.resource[i] + w.regen[i] * (1 + w.farmland[i] * 1.15));
|
||||
w.pheromone[i] *= 0.992;
|
||||
w.pheromone[i] *= 0.996;
|
||||
w.pressure[i] *= 0.88;
|
||||
}
|
||||
}
|
||||
|
|
@ -1061,20 +1068,20 @@ class Simulation {
|
|||
}
|
||||
|
||||
let foundedThisTick = 0;
|
||||
const canFoundCities = this.year >= years(180) && this.year % years(30) === 0;
|
||||
const foundingLimit = canFoundCities ? 1 + Math.floor(this.year / years(1200)) : 0;
|
||||
const canFoundCities = this.year >= years(80) && this.year % years(10) === 0;
|
||||
const foundingLimit = canFoundCities ? 1 + Math.floor(this.year / years(600)) : 0;
|
||||
const candidateEntries = [...candidates].sort((a, b) => {
|
||||
const sedentaryA = a[1].sedentary / Math.max(1, a[1].count);
|
||||
const sedentaryB = b[1].sedentary / Math.max(1, b[1].count);
|
||||
return (b[1].count * (0.7 + sedentaryB) + b[1].resources * 0.02) - (a[1].count * (0.7 + sedentaryA) + a[1].resources * 0.02);
|
||||
});
|
||||
for (const [i, group] of candidateEntries) {
|
||||
if (group.count < 6) continue;
|
||||
if (group.count < 3) continue;
|
||||
const avgSedentary = group.sedentary / group.count;
|
||||
let city = this.cities.find(c => Math.abs(c.x - (i % w.size)) + Math.abs(c.y - Math.floor(i / w.size)) < 7);
|
||||
if (!city && canFoundCities && foundedThisTick < foundingLimit && this.cities.length < 50) {
|
||||
const foundingChance = clamp((avgSedentary - 0.28) * 1.55 * 3, 0, 0.98);
|
||||
if (avgSedentary < 0.34 || this.rng.next() > foundingChance) continue;
|
||||
if (!city && canFoundCities && foundedThisTick < foundingLimit && this.cities.length < 80) {
|
||||
const foundingChance = clamp((avgSedentary - 0.18) * 2.4 * 3, 0.04, 0.98);
|
||||
if (avgSedentary < 0.22 || this.rng.next() > foundingChance) continue;
|
||||
city = this.createCity(i % w.size, Math.floor(i / w.size), group);
|
||||
this.cities.push(city);
|
||||
foundedThisTick++;
|
||||
|
|
@ -1097,9 +1104,14 @@ class Simulation {
|
|||
|
||||
this.cities = this.cities.filter(c => {
|
||||
c.age++;
|
||||
c.strength *= 0.996;
|
||||
if (c.population < 10) c.strength -= 0.02;
|
||||
if (c.population <= 0 || c.strength <= 0.12) return false;
|
||||
c.strength *= 0.992;
|
||||
const foodPerCapita = c.storedResources / Math.max(1, c.population);
|
||||
if (c.age > 20) {
|
||||
if (c.activeVisitors < 2 && foodPerCapita < 0.04) c.strength -= 0.035;
|
||||
else if (c.activeVisitors < 5 && foodPerCapita < 0.02) c.strength -= 0.015;
|
||||
if (c.population < 25 && foodPerCapita < 0.05) c.strength -= 0.018;
|
||||
}
|
||||
if (c.population <= 0 || c.strength <= 0.06) return false;
|
||||
const radius = c.agriculturalRadius;
|
||||
for (let dy = -radius; dy <= radius; dy++) {
|
||||
for (let dx = -radius; dx <= radius; dx++) {
|
||||
|
|
@ -1121,7 +1133,7 @@ class Simulation {
|
|||
}
|
||||
|
||||
createCity(x, y, seedGroup = null) {
|
||||
const seedPopulation = seedGroup ? Math.max(8, seedGroup.count * 3) : 8;
|
||||
const seedPopulation = seedGroup ? Math.max(14, seedGroup.count * 5) : 10;
|
||||
const seedSedentary = seedGroup ? seedGroup.sedentary / Math.max(1, seedGroup.count) : 0.5;
|
||||
const composition = new Map();
|
||||
if (seedGroup) {
|
||||
|
|
@ -1136,14 +1148,14 @@ class Simulation {
|
|||
x,
|
||||
y,
|
||||
population: seedPopulation,
|
||||
storedResources: 24 + (seedGroup?.resources || 0) * 0.4,
|
||||
storedResources: 34 + (seedGroup?.resources || 0) * 0.55,
|
||||
ethnicityComposition: composition,
|
||||
pheromoneOutput: 0,
|
||||
agriculturalRadius: 2,
|
||||
tradeLinks: new Set(),
|
||||
activeVisitors: 0,
|
||||
age: 0,
|
||||
strength: 2,
|
||||
strength: 3,
|
||||
sedentaryCulture: seedSedentary,
|
||||
polityId: null,
|
||||
loyalty: 0.5,
|
||||
|
|
@ -1202,13 +1214,14 @@ class Simulation {
|
|||
const extraction = Math.min(w.resource[i], (0.07 + w.fertility[i] * 0.18 + w.mineral[i] * 0.045) * pull);
|
||||
w.resource[i] -= extraction;
|
||||
w.farmland[i] = Math.max(w.farmland[i], pull);
|
||||
w.pheromone[i] += city.pheromoneOutput * pull * 0.04;
|
||||
w.pheromone[i] += city.pheromoneOutput * pull * 0.09;
|
||||
harvested += extraction;
|
||||
}
|
||||
}
|
||||
|
||||
city.storedResources += harvested;
|
||||
const upkeep = city.population * 0.010;
|
||||
const supportRatio = city.activeVisitors / Math.max(1, city.population);
|
||||
const upkeep = city.population * (0.010 + Math.max(0, 0.025 - supportRatio) * 0.09);
|
||||
city.storedResources -= upkeep;
|
||||
if (city.storedResources > city.population * 0.16 && city.population > 0) {
|
||||
const prosperity = clamp(city.storedResources / Math.max(1, city.population) - 0.12, 0, 0.8);
|
||||
|
|
@ -1217,6 +1230,12 @@ class Simulation {
|
|||
city.storedResources -= births * 0.55;
|
||||
addBirthsToComposition(city.ethnicityComposition, births);
|
||||
}
|
||||
if (city.age > 20 && city.activeVisitors < 2 && city.storedResources < city.population * 0.03 && city.population > 0) {
|
||||
const attrition = Math.max(1, Math.ceil(city.population * (city.activeVisitors === 0 ? 0.025 : 0.010)));
|
||||
city.population -= attrition;
|
||||
removeFromComposition(city.ethnicityComposition, attrition);
|
||||
city.strength -= city.activeVisitors === 0 ? 0.030 : 0.012;
|
||||
}
|
||||
if (city.storedResources < 0) {
|
||||
const deficit = Math.abs(city.storedResources);
|
||||
const dominant = dominantComposition(city.ethnicityComposition);
|
||||
|
|
@ -1797,7 +1816,7 @@ class Simulation {
|
|||
reinforcePolityTradeRoutes() {
|
||||
const w = this.world;
|
||||
for (const polity of this.polities) {
|
||||
if (this.rng.next() > 0.32) continue;
|
||||
if (this.rng.next() > 0.48) continue;
|
||||
const center = this.getCityById(polity.centerCityId);
|
||||
if (!center) continue;
|
||||
const candidates = this.getPolityCities(polity)
|
||||
|
|
@ -1844,24 +1863,24 @@ class Simulation {
|
|||
const c1 = this.cities[a];
|
||||
const c2 = this.cities[b];
|
||||
const d = Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y);
|
||||
if (d > 34) continue;
|
||||
if (d > 44) continue;
|
||||
const path = this.findTerrainRoute(c1.x, c1.y, c2.x, c2.y);
|
||||
if (!this.isValidRoutePath(path, c2.x, c2.y)) continue;
|
||||
const strength = this.routeStrengthForPath(path);
|
||||
if (strength < 0.12) continue;
|
||||
if (strength < 0.08) continue;
|
||||
if (!this.canPayRouteConstructionCost(c1, c2, path)) continue;
|
||||
candidates.push({ c1, c2, strength, path });
|
||||
}
|
||||
}
|
||||
|
||||
candidates.sort((a, b) => b.strength - a.strength);
|
||||
const maxLinks = Math.max(1, Math.floor(this.cities.length / 3));
|
||||
const maxLinks = Math.max(1, Math.floor(this.cities.length / 2));
|
||||
const supportedRoutes = new Set();
|
||||
for (const candidate of candidates) {
|
||||
if (this.tradeLinks.length >= maxLinks) break;
|
||||
const { c1, c2, strength, path } = candidate;
|
||||
const c1Limit = c1.population > 90 ? 2 : 1;
|
||||
const c2Limit = c2.population > 90 ? 2 : 1;
|
||||
const c1Limit = c1.population > 90 ? 3 : 2;
|
||||
const c2Limit = c2.population > 90 ? 3 : 2;
|
||||
if (c1.tradeLinks.size >= c1Limit || c2.tradeLinks.size >= c2Limit) continue;
|
||||
if (!this.payRouteConstructionCost(c1, c2, path)) continue;
|
||||
c1.tradeLinks.add(c2.id);
|
||||
|
|
@ -1870,12 +1889,12 @@ class Simulation {
|
|||
this.exchangeCityResources(c1, c2, strength, path);
|
||||
for (const tile of path) {
|
||||
supportedRoutes.add(tile);
|
||||
w.tradeRoute[tile] = Math.min(255, w.tradeRoute[tile] + 12);
|
||||
w.tradeRoute[tile] = Math.min(255, w.tradeRoute[tile] + 18);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < w.count; i++) {
|
||||
if (w.tradeRoute[i] && !supportedRoutes.has(i)) w.tradeRoute[i] = Math.max(0, w.tradeRoute[i] - 10);
|
||||
if (w.tradeRoute[i] && !supportedRoutes.has(i)) w.tradeRoute[i] = Math.max(0, w.tradeRoute[i] - 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2475,10 +2494,15 @@ function renderTooltip() {
|
|||
${ethnicity ? `<span><b>Lineage pop</b><em>${ethnicity.population}</em></span>` : ""}
|
||||
`;
|
||||
els.tooltip.hidden = false;
|
||||
const left = Math.min(hoverState.left, hoverState.width - 280);
|
||||
const top = Math.min(hoverState.top, hoverState.height - 230);
|
||||
els.tooltip.style.left = `${Math.max(8, left)}px`;
|
||||
els.tooltip.style.top = `${Math.max(8, top)}px`;
|
||||
const margin = 8;
|
||||
const width = els.tooltip.offsetWidth;
|
||||
const height = els.tooltip.offsetHeight;
|
||||
let left = hoverState.left;
|
||||
let top = hoverState.top;
|
||||
if (left + width + margin > hoverState.width) left = hoverState.left - width - 32;
|
||||
if (top + height + margin > hoverState.height) top = hoverState.height - height - margin;
|
||||
els.tooltip.style.left = `${clamp(left, margin, Math.max(margin, hoverState.width - width - margin))}px`;
|
||||
els.tooltip.style.top = `${clamp(top, margin, Math.max(margin, hoverState.height - height - margin))}px`;
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
|
|
|
|||
|
|
@ -284,7 +284,9 @@ canvas#world {
|
|||
position: absolute;
|
||||
z-index: 3;
|
||||
min-width: 180px;
|
||||
max-width: 260px;
|
||||
max-width: min(300px, calc(100% - 16px));
|
||||
max-height: calc(100% - 16px);
|
||||
overflow: auto;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #435057;
|
||||
border-radius: 6px;
|
||||
|
|
@ -292,7 +294,7 @@ canvas#world {
|
|||
color: var(--text);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
pointer-events: none;
|
||||
pointer-events: auto;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue