diff --git a/data/resources.js b/data/resources.js
index ddbff606..472295f9 100644
--- a/data/resources.js
+++ b/data/resources.js
@@ -528,8 +528,8 @@ window.FMG.data.resourceModels = {
Any_forest: 'biome(5, 6, 7, 8, 9)',
Temperate_and_boreal_forests: 'biome(6, 8, 9)',
Hills: 'minHeight(40) || (minHeight(30) && nth(10))',
- Mountains: 'minHeight(60) || (minHeight(40) && nth(10))',
- Mountains_and_wetlands: 'minHeight(60) || (biome(12) && nth(8))',
+ Mountains: 'minHeight(60) || (minHeight(20) && nth(10))',
+ Mountains_and_wetlands: 'minHeight(60) || (biome(12) && nth(7)) || (minHeight(20) && nth(10))',
Headwaters: 'river() && minHeight(40)',
More_habitable: 'minHabitability(20) && habitability()',
Marine_and_rivers: 'shore(-1) && (type("ocean", "freshwater", "salt") || (river() && shore(1, 2)))',
diff --git a/index.html b/index.html
index 279bc44a..97bd4748 100644
--- a/index.html
+++ b/index.html
@@ -2019,7 +2019,7 @@
-
+
diff --git a/main.js b/main.js
index 1bd85405..c784b792 100644
--- a/main.js
+++ b/main.js
@@ -1445,7 +1445,6 @@ function rankCells() {
const flMax = d3.max(cells.fl) + d3.max(cells.conf); // to normalize flux
const areaMean = d3.mean(cells.area); // to adjust population by cell area
const getResValue = (i) => (cells.resource[i] ? Resources.get(cells.resource[i])?.value : 0); // get bonus resource scope
- const resBonuses = [];
const POP_BALANCER = 1.5; // to ballance population to desired number
for (const i of cells.i) {
@@ -1476,20 +1475,12 @@ function rankCells() {
const cellRes = getResValue(i);
const neibRes = d3.mean(cells.c[i].map((c) => getResValue(c)));
const resBonus = (cellRes ? cellRes + 10 : 0) + neibRes;
- resBonuses.push(resBonus);
// cell rural population is suitability adjusted by cell area
cells.pop[i] = s > 0 ? (s * POP_BALANCER * cells.area[i]) / areaMean : 0;
cells.s[i] = s + resBonus;
-
- // debug.append('text').attr('x', cells.p[i][0]).attr('y', cells.p[i][1]).text(cells.s[i]);
}
- console.log(resBonuses);
- console.log(d3.max(resBonuses));
- console.log(d3.mean(resBonuses));
- console.log(d3.median(resBonuses.map((v) => rn(v))));
-
TIME && console.timeEnd('rankCells');
}
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index 8efbdb71..371581dd 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -229,12 +229,12 @@ window.BurgsAndStates = (function () {
const {cells, burgs} = pack;
for (const burg of burgs) {
- if (!burg.i || burg.removed) continue;
- if (newburg && newburg.i !== burg.i) continue;
- const {cell, state, pop, capital} = burg;
+ const {i, removed, cell, state, pop, capital, tradeCenter} = burg;
+ if (!i || removed) continue;
+ if (newburg && newburg.i !== i) continue;
burg.citadel = capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0;
- burg.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0;
+ burg.plaza = tradeCenter === i;
burg.walls = capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0;
burg.shanty = pop > 30 || (pop > 20 && P(0.75)) || (burg.walls && P(0.75)) ? 1 : 0;
const religion = cells.religion[cell];
diff --git a/modules/production-generator.js b/modules/production-generator.js
index 61c846b8..56da2fa1 100644
--- a/modules/production-generator.js
+++ b/modules/production-generator.js
@@ -102,11 +102,11 @@ window.Production = (function () {
queue.queue({...occupation, basePriority: newBasePriority, priority: newPriority});
}
+ burg.produced = {};
for (const resourceId in productionPull) {
- const production = Math.ceil(productionPull[resourceId]);
- productionPull[resourceId] = production;
+ const production = productionPull[resourceId];
+ burg.produced[resourceId] = Math.ceil(production);
}
- burg.production = productionPull;
}
};
diff --git a/modules/trade-generator.js b/modules/trade-generator.js
index 4cdf1780..89944532 100644
--- a/modules/trade-generator.js
+++ b/modules/trade-generator.js
@@ -2,23 +2,95 @@
window.Trade = (function () {
const defineCenters = () => {
- const {cells} = pack;
+ TIME && console.time('defineCenters');
+ pack.trade = {centers: [], deals: []};
+ const {burgs, trade} = pack;
+
+ // min distance between trade centers
+ let minSpacing = (((graphWidth + graphHeight) * 2) / burgs.length ** 0.7) | 0;
+
+ const tradeScore = burgs.map(({i, removed, capital, port, population, produced}) => {
+ if (!i || removed) return {i: 0, score: 0};
+ const totalProduction = d3.sum(Object.values(produced));
+ let score = Math.round(totalProduction - population);
+ if (capital) score *= 2;
+ if (port) score *= 3;
+ return {i, score};
+ });
+
+ const candidatesSorted = tradeScore.sort((a, b) => b.score - a.score);
+ const centersTree = d3.quadtree();
+
+ for (const {i} of candidatesSorted) {
+ if (!i) continue;
+ const {x, y} = burgs[i];
+
+ const tradeCenter = centersTree.find(x, y, minSpacing);
+ if (tradeCenter) {
+ const centerBurg = tradeCenter[2];
+ burgs[i].tradeCenter = centerBurg;
+
+ const {x: x2, y: y2} = burgs[centerBurg];
+ debug.append('line').attr('x1', x).attr('y1', y).attr('x2', x2).attr('y2', y2).attr('stroke', 'black').attr('stroke-width', 0.2);
+ } else {
+ trade.centers.push({i: trade.centers.length, burg: i, x, y});
+ centersTree.add([x, y, i]);
+ burgs[i].tradeCenter = i;
+ }
+
+ minSpacing += 1;
+ }
+
+ for (const {i, score} of candidatesSorted) {
+ if (!i) continue;
+ const {x, y} = burgs[i];
+ debug.append('text').attr('x', x).attr('y', y).style('font-size', 4).text(score);
+ }
+
+ TIME && console.timeEnd('defineCenters');
};
const exportGoods = () => {
- for (const burg of pack.burgs) {
- if (!burg.i || burg.removed) continue;
- const {population, production: resourcePool} = burg;
- const localUsage = Math.ceil(population);
+ const {burgs, states, trade} = pack;
+ const DEFAULT_TRANSPORT_DIST = (graphWidth + graphHeight) / 20;
- const surplus = {};
- for (const resourceId in resourcePool) {
- const production = resourcePool[resourceId];
- const extraProduction = production - localUsage;
- if (extraProduction > 0) surplus[resourceId] = extraProduction;
+ for (const tradeCenter of trade.centers) {
+ const {i: centerId, burg: centerBurg, x: x0, y: y0} = tradeCenter;
+ const goods = {};
+
+ for (const burg of burgs) {
+ const {i, removed, tradeCenter, produced, population, state, x, y} = burg;
+ if (!i || removed || tradeCenter !== centerBurg) continue;
+ const consumption = Math.ceil(population);
+
+ const distance = Math.hypot(x - x0, y - y0);
+ const transportFee = (distance / DEFAULT_TRANSPORT_DIST) ** 0.8 || 0.02;
+ const salesTax = states[state].salesTax || 0.1;
+
+ for (const resourceId in produced) {
+ const production = produced[resourceId];
+ const quantity = production - consumption;
+ if (quantity < 1) continue;
+
+ const {value} = Resources.get(+resourceId);
+
+ const basePrice = value * quantity;
+ const transportCost = rn((value * quantity) ** 0.5 * transportFee, 2);
+ const netPrice = basePrice - transportCost;
+
+ const stateIncome = rn(netPrice * salesTax, 2);
+ const burgIncome = rn(netPrice - stateIncome, 2);
+
+ if (burgIncome < 1 || burgIncome < basePrice / 4) continue;
+
+ trade.deals.push({resourceId: +resourceId, quantity, exporter: i, tradeCenter: centerId, basePrice, transportCost, stateIncome, burgIncome});
+
+ if (!goods[resourceId]) goods[resourceId] = quantity;
+ else goods[resourceId] += quantity;
+ }
}
- burg.export = surplus;
+ tradeCenter.goods = goods;
}
};
diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js
index 72d8d31e..809f00f6 100644
--- a/modules/ui/burg-editor.js
+++ b/modules/ui/burg-editor.js
@@ -98,8 +98,10 @@ function editBurg(id) {
else document.getElementById('burgShanty').classList.add('inactive');
// economics block
- document.getElementById('burgProduction').innerHTML = getProduction(b.production);
- document.getElementById('burgExport').innerHTML = getProduction(b.export);
+ document.getElementById('burgProduction').innerHTML = getProduction(b.produced);
+ const deals = pack.trade.deals;
+ document.getElementById('burgExport').innerHTML = getExport(deals.filter((deal) => deal.exporter === b.i));
+ document.getElementById('burgImport').innerHTML = '';
//toggle lock
updateBurgLockIcon();
@@ -119,12 +121,12 @@ function editBurg(id) {
document.getElementById('burgEmblem').setAttribute('href', '#' + coaID);
}
- function getProduction(resources) {
+ function getProduction(pool) {
let html = '';
- for (const resourceId in resources) {
+ for (const resourceId in pool) {
const {name, unit, icon} = Resources.get(+resourceId);
- const production = resources[resourceId];
+ const production = pool[resourceId];
const unitName = production > 1 ? unit + 's' : unit;
html += `
@@ -136,6 +138,24 @@ function editBurg(id) {
return html;
}
+ function getExport(dealsArray) {
+ if (!dealsArray.length) return 'no';
+
+ const totalIncome = d3.sum(dealsArray.map((deal) => deal.burgIncome));
+ const exported = dealsArray.map((deal) => {
+ const {resourceId, quantity, burgIncome} = deal;
+ const {name, unit, icon} = Resources.get(resourceId);
+ const unitName = quantity > 1 ? unit + 's' : unit;
+
+ return `
+
+ ${quantity}
+ `;
+ });
+
+ return `${totalIncome}: ${exported.join('')}`;
+ }
+
// [-1; 31] °C, source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
function getTemperatureLikeness(temperature) {
if (temperature < -15) return 'nowhere in the real-world';