mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
* Suggesting Levantine as a new default namebase
Levantine namebase has 203 names of Phoenician, punic, hebrew and aramean origin. Names of places in real live.
the excerpt is: {name: "Levantine", i: 42, min: 4, max: 12, d: "krs-pn", m: 0, b: "namebase"}
This will allow to better represent some cultures.
- Carthaginian culture should have levantine namebase instead of berber.
Or maybe make two carthaginian, one with levantine and one with berber. Another option is to add Numidian culture with berber base.
_ In the namebase there are included some names for carthage. Keret Chadeshet and Kart-Hadasht are names for Carthage.
- This will allow phoenician and punic culture in an antique set.
- This will allow hebrew, and other levantine. (Urushalim is a name for Jerusalem).
There are questions with the number: i: 42, and what happens with old maps that have custom languages added in that spot.
* updated levantine namebase
Deleted the '-'. Reordered repeated letters.
* Added levantine cultures
Added Hebrew culture into: European set and all-world set.
Changed namebase of Carthaginian in Antique set from Berber (17) to Levantine (42) as per the conversation on discord thread FMG suggestions > Punic culture set.
This leaves the antique set with no native speaker of berber. If you want to have a culture that represent north-african people and berber different than carthage, we can add one.
Included Phoenician as a distinct culture with similar culture center formula than carthage "sort: i => n(i) / td(i, 20) / sf(i)" but with td(i, 20) because is the temperature of beirut and gives a difference with carthaginian.
Hebrew is included with " n(i) / td(i, 18) * sf(i) " because hebrew are pastoralists, and to differentiate the culture center from the phoenician that are near sea harbor.
562 lines
27 KiB
JavaScript
562 lines
27 KiB
JavaScript
"use strict";
|
|
|
|
window.Cultures = (function () {
|
|
let cells;
|
|
|
|
const generate = function () {
|
|
TIME && console.time("generateCultures");
|
|
cells = pack.cells;
|
|
cells.culture = new Uint16Array(cells.i.length); // cell cultures
|
|
let count = Math.min(+culturesInput.value, +culturesSet.selectedOptions[0].dataset.max);
|
|
|
|
const populated = cells.i.filter(i => cells.s[i]); // populated cells
|
|
if (populated.length < count * 25) {
|
|
count = Math.floor(populated.length / 50);
|
|
if (!count) {
|
|
WARN && console.warn(`There are no populated cells. Cannot generate cultures`);
|
|
pack.cultures = [{name: "Wildlands", i: 0, base: 1, shield: "round"}];
|
|
alertMessage.innerHTML = /* html */ ` The climate is harsh and people cannot live in this world.<br />
|
|
No cultures, states and burgs will be created.<br />
|
|
Please consider changing climate settings in the World Configurator`;
|
|
$("#alert").dialog({
|
|
resizable: false,
|
|
title: "Extreme climate warning",
|
|
buttons: {
|
|
Ok: function () {
|
|
$(this).dialog("close");
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
} else {
|
|
WARN && console.warn(`Not enough populated cells (${populated.length}). Will generate only ${count} cultures`);
|
|
alertMessage.innerHTML = /* html */ ` There are only ${populated.length} populated cells and it's insufficient livable area.<br />
|
|
Only ${count} out of ${culturesInput.value} requested cultures will be generated.<br />
|
|
Please consider changing climate settings in the World Configurator`;
|
|
$("#alert").dialog({
|
|
resizable: false,
|
|
title: "Extreme climate warning",
|
|
buttons: {
|
|
Ok: function () {
|
|
$(this).dialog("close");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const cultures = (pack.cultures = selectCultures(count));
|
|
const centers = d3.quadtree();
|
|
const colors = getColors(count);
|
|
const emblemShape = document.getElementById("emblemShape").value;
|
|
|
|
const codes = [];
|
|
cultures.forEach(function (c, i) {
|
|
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i]));
|
|
centers.add(cells.p[cell]);
|
|
c.i = i + 1;
|
|
delete c.odd;
|
|
delete c.sort;
|
|
c.color = colors[i];
|
|
c.type = defineCultureType(cell);
|
|
c.expansionism = defineCultureExpansionism(c.type);
|
|
c.origins = [0];
|
|
c.code = abbreviate(c.name, codes);
|
|
codes.push(c.code);
|
|
cells.culture[cell] = i + 1;
|
|
if (emblemShape === "random") c.shield = getRandomShield();
|
|
});
|
|
|
|
function placeCenter(v) {
|
|
let c,
|
|
spacing = (graphWidth + graphHeight) / 2 / count;
|
|
const sorted = [...populated].sort((a, b) => v(b) - v(a)),
|
|
max = Math.floor(sorted.length / 2);
|
|
do {
|
|
c = sorted[biased(0, max, 5)];
|
|
spacing *= 0.9;
|
|
} while (centers.find(cells.p[c][0], cells.p[c][1], spacing) !== undefined);
|
|
return c;
|
|
}
|
|
|
|
// the first culture with id 0 is for wildlands
|
|
cultures.unshift({name: "Wildlands", i: 0, base: 1, origins: [null], shield: "round"});
|
|
|
|
// make sure all bases exist in nameBases
|
|
if (!nameBases.length) {
|
|
ERROR && console.error("Name base is empty, default nameBases will be applied");
|
|
nameBases = Names.getNameBases();
|
|
}
|
|
|
|
cultures.forEach(c => (c.base = c.base % nameBases.length));
|
|
|
|
function selectCultures(c) {
|
|
let def = getDefault(c);
|
|
if (c === def.length) return def;
|
|
if (def.every(d => d.odd === 1)) return def.splice(0, c);
|
|
|
|
const count = Math.min(c, def.length);
|
|
const cultures = [];
|
|
|
|
for (let culture, rnd, i = 0; cultures.length < count && i < 200; i++) {
|
|
do {
|
|
rnd = rand(def.length - 1);
|
|
culture = def[rnd];
|
|
} while (!P(culture.odd));
|
|
cultures.push(culture);
|
|
def.splice(rnd, 1);
|
|
}
|
|
return cultures;
|
|
}
|
|
|
|
// set culture type based on culture center position
|
|
function defineCultureType(i) {
|
|
if (cells.h[i] < 70 && [1, 2, 4].includes(cells.biome[i])) return "Nomadic"; // high penalty in forest biomes and near coastline
|
|
if (cells.h[i] > 50) return "Highland"; // no penalty for hills and moutains, high for other elevations
|
|
const f = pack.features[cells.f[cells.haven[i]]]; // opposite feature
|
|
if (f.type === "lake" && f.cells > 5) return "Lake"; // low water cross penalty and high for growth not along coastline
|
|
if (
|
|
(cells.harbor[i] && f.type !== "lake" && P(0.1)) ||
|
|
(cells.harbor[i] === 1 && P(0.6)) ||
|
|
(pack.features[cells.f[i]].group === "isle" && P(0.4))
|
|
)
|
|
return "Naval"; // low water cross penalty and high for non-along-coastline growth
|
|
if (cells.r[i] && cells.fl[i] > 100) return "River"; // no River cross penalty, penalty for non-River growth
|
|
if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes
|
|
return "Generic";
|
|
}
|
|
|
|
function defineCultureExpansionism(type) {
|
|
let base = 1; // Generic
|
|
if (type === "Lake") base = 0.8;
|
|
else if (type === "Naval") base = 1.5;
|
|
else if (type === "River") base = 0.9;
|
|
else if (type === "Nomadic") base = 1.5;
|
|
else if (type === "Hunting") base = 0.7;
|
|
else if (type === "Highland") base = 1.2;
|
|
return rn(((Math.random() * powerInput.value) / 2 + 1) * base, 1);
|
|
}
|
|
|
|
TIME && console.timeEnd("generateCultures");
|
|
};
|
|
|
|
const add = function (center) {
|
|
const defaultCultures = getDefault();
|
|
let culture, base, name;
|
|
|
|
if (pack.cultures.length < defaultCultures.length) {
|
|
// add one of the default cultures
|
|
culture = pack.cultures.length;
|
|
base = defaultCultures[culture].base;
|
|
name = defaultCultures[culture].name;
|
|
} else {
|
|
// add random culture besed on one of the current ones
|
|
culture = rand(pack.cultures.length - 1);
|
|
name = Names.getCulture(culture, 5, 8, "");
|
|
base = pack.cultures[culture].base;
|
|
}
|
|
const code = abbreviate(
|
|
name,
|
|
pack.cultures.map(c => c.code)
|
|
);
|
|
const i = pack.cultures.length;
|
|
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex();
|
|
|
|
// define emblem shape
|
|
let shield = culture.shield;
|
|
const emblemShape = document.getElementById("emblemShape").value;
|
|
if (emblemShape === "random") shield = getRandomShield();
|
|
|
|
pack.cultures.push({
|
|
name,
|
|
color,
|
|
base,
|
|
center,
|
|
i,
|
|
expansionism: 1,
|
|
type: "Generic",
|
|
cells: 0,
|
|
area: 0,
|
|
rural: 0,
|
|
urban: 0,
|
|
origins: [0],
|
|
code,
|
|
shield
|
|
});
|
|
};
|
|
|
|
const getDefault = function (count) {
|
|
// generic sorting functions
|
|
const cells = pack.cells,
|
|
s = cells.s,
|
|
sMax = d3.max(s),
|
|
t = cells.t,
|
|
h = cells.h,
|
|
temp = grid.cells.temp;
|
|
const n = cell => Math.ceil((s[cell] / sMax) * 3); // normalized cell score
|
|
const td = (cell, goal) => {
|
|
const d = Math.abs(temp[cells.g[cell]] - goal);
|
|
return d ? d + 1 : 1;
|
|
}; // temperature difference fee
|
|
const bd = (cell, biomes, fee = 4) => (biomes.includes(cells.biome[cell]) ? 1 : fee); // biome difference fee
|
|
const sf = (cell, fee = 4) =>
|
|
cells.haven[cell] && pack.features[cells.f[cells.haven[cell]]].type !== "lake" ? 1 : fee; // not on sea coast fee
|
|
|
|
if (culturesSet.value === "european") {
|
|
return [
|
|
{name: "Shwazen", base: 0, odd: 1, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "swiss"},
|
|
{name: "Angshire", base: 1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield: "wedged"},
|
|
{name: "Luari", base: 2, odd: 1, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "french"},
|
|
{name: "Tallian", base: 3, odd: 1, sort: i => n(i) / td(i, 15), shield: "horsehead"},
|
|
{name: "Astellian", base: 4, odd: 1, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
|
{name: "Slovan", base: 5, odd: 1, sort: i => (n(i) / td(i, 6)) * t[i], shield: "polish"},
|
|
{name: "Norse", base: 6, odd: 1, sort: i => n(i) / td(i, 5), shield: "heater"},
|
|
{name: "Elladan", base: 7, odd: 1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "boeotian"},
|
|
{name: "Romian", base: 8, odd: 0.2, sort: i => n(i) / td(i, 15) / t[i], shield: "roman"},
|
|
{name: "Soumi", base: 9, odd: 1, sort: i => (n(i) / td(i, 5) / bd(i, [9])) * t[i], shield: "pavise"},
|
|
{name: "Portuzian", base: 13, odd: 1, sort: i => n(i) / td(i, 17) / sf(i), shield: "renaissance"},
|
|
{name: "Vengrian", base: 15, odd: 1, sort: i => (n(i) / td(i, 11) / bd(i, [4])) * t[i], shield: "horsehead2"},
|
|
{name: "Turchian", base: 16, odd: 0.05, sort: i => n(i) / td(i, 14), shield: "round"},
|
|
{name: "Euskati", base: 20, odd: 0.05, sort: i => (n(i) / td(i, 15)) * h[i], shield: "oldFrench"},
|
|
{name: "Keltan", base: 22, odd: 0.05, sort: i => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: "oval"},
|
|
{name: "Hebrew", base: 42, odd: 0.05, sort: i => n(i) / td(i, 18) * sf(i), shield: "oval"}
|
|
];
|
|
}
|
|
|
|
if (culturesSet.value === "oriental") {
|
|
return [
|
|
{name: "Koryo", base: 10, odd: 1, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
|
{name: "Hantzu", base: 11, odd: 1, sort: i => n(i) / td(i, 13), shield: "banner"},
|
|
{name: "Yamoto", base: 12, odd: 1, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
|
{name: "Turchian", base: 16, odd: 1, sort: i => n(i) / td(i, 12), shield: "round"},
|
|
{
|
|
name: "Berberan",
|
|
base: 17,
|
|
odd: 0.2,
|
|
sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i],
|
|
shield: "oval"
|
|
},
|
|
{name: "Eurabic", base: 18, odd: 1, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "oval"},
|
|
{name: "Efratic", base: 23, odd: 0.1, sort: i => (n(i) / td(i, 22)) * t[i], shield: "round"},
|
|
{name: "Tehrani", base: 24, odd: 1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "round"},
|
|
{name: "Maui", base: 25, odd: 0.2, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "vesicaPiscis"},
|
|
{name: "Carnatic", base: 26, odd: 0.5, sort: i => n(i) / td(i, 26), shield: "round"},
|
|
{name: "Vietic", base: 29, odd: 0.8, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: "banner"},
|
|
{name: "Guantzu", base: 30, odd: 0.5, sort: i => n(i) / td(i, 17), shield: "banner"},
|
|
{name: "Ulus", base: 31, odd: 1, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"}
|
|
];
|
|
}
|
|
|
|
if (culturesSet.value === "english") {
|
|
const getName = () => Names.getBase(1, 5, 9, "", 0);
|
|
return [
|
|
{name: getName(), base: 1, odd: 1, shield: "heater"},
|
|
{name: getName(), base: 1, odd: 1, shield: "wedged"},
|
|
{name: getName(), base: 1, odd: 1, shield: "swiss"},
|
|
{name: getName(), base: 1, odd: 1, shield: "oldFrench"},
|
|
{name: getName(), base: 1, odd: 1, shield: "swiss"},
|
|
{name: getName(), base: 1, odd: 1, shield: "spanish"},
|
|
{name: getName(), base: 1, odd: 1, shield: "hessen"},
|
|
{name: getName(), base: 1, odd: 1, shield: "fantasy5"},
|
|
{name: getName(), base: 1, odd: 1, shield: "fantasy4"},
|
|
{name: getName(), base: 1, odd: 1, shield: "fantasy1"}
|
|
];
|
|
}
|
|
|
|
if (culturesSet.value === "antique") {
|
|
return [
|
|
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 14) / t[i], shield: "roman"}, // Roman
|
|
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 15) / sf(i), shield: "roman"}, // Roman
|
|
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 16) / sf(i), shield: "roman"}, // Roman
|
|
{name: "Roman", base: 8, odd: 1, sort: i => n(i) / td(i, 17) / t[i], shield: "roman"}, // Roman
|
|
{name: "Hellenic", base: 7, odd: 1, sort: i => (n(i) / td(i, 18) / sf(i)) * h[i], shield: "boeotian"}, // Greek
|
|
{name: "Hellenic", base: 7, odd: 1, sort: i => (n(i) / td(i, 19) / sf(i)) * h[i], shield: "boeotian"}, // Greek
|
|
{name: "Macedonian", base: 7, odd: 0.5, sort: i => (n(i) / td(i, 12)) * h[i], shield: "round"}, // Greek
|
|
{name: "Celtic", base: 22, odd: 1, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [6, 8]), shield: "round"},
|
|
{name: "Germanic", base: 0, odd: 1, sort: i => n(i) / td(i, 10) ** 0.5 / bd(i, [6, 8]), shield: "round"},
|
|
{name: "Persian", base: 24, odd: 0.8, sort: i => (n(i) / td(i, 18)) * h[i], shield: "oval"}, // Iranian
|
|
{name: "Scythian", base: 24, odd: 0.5, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [4]), shield: "round"}, // Iranian
|
|
{name: "Cantabrian", base: 20, odd: 0.5, sort: i => (n(i) / td(i, 16)) * h[i], shield: "oval"}, // Basque
|
|
{name: "Estian", base: 9, odd: 0.2, sort: i => (n(i) / td(i, 5)) * t[i], shield: "pavise"}, // Finnic
|
|
{name: "Carthaginian", base: 42, odd: 0.3, sort: i => n(i) / td(i, 19) / sf(i), shield: "oval"}, // Levantine
|
|
{name: "Phoenician", base: 42, odd: 0.3, sort: i => n(i) / td(i, 20) / sf(i), shield: "oval"}, // Levantine
|
|
{name: "Mesopotamian", base: 23, odd: 0.2, sort: i => n(i) / td(i, 22) / bd(i, [1, 2, 3]), shield: "oval"}, // Mesopotamian
|
|
{name: "Hebrew", base: 42, odd: 0.2, sort: i => n(i) / td(i, 18) * sf(i), shield: "oval"} // Levantine
|
|
];
|
|
}
|
|
|
|
if (culturesSet.value === "highFantasy") {
|
|
return [
|
|
// fantasy races
|
|
{
|
|
name: "Quenian (Elfish)",
|
|
base: 33,
|
|
odd: 1,
|
|
sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i],
|
|
shield: "gondor"
|
|
}, // Elves
|
|
{
|
|
name: "Eldar (Elfish)",
|
|
base: 33,
|
|
odd: 1,
|
|
sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i],
|
|
shield: "noldor"
|
|
}, // Elves
|
|
{
|
|
name: "Trow (Dark Elfish)",
|
|
base: 34,
|
|
odd: 0.9,
|
|
sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i],
|
|
shield: "hessen"
|
|
}, // Dark Elves
|
|
{
|
|
name: "Lothian (Dark Elfish)",
|
|
base: 34,
|
|
odd: 0.3,
|
|
sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i],
|
|
shield: "wedged"
|
|
}, // Dark Elves
|
|
{name: "Dunirr (Dwarven)", base: 35, odd: 1, sort: i => n(i) + h[i], shield: "ironHills"}, // Dwarfs
|
|
{name: "Khazadur (Dwarven)", base: 35, odd: 1, sort: i => n(i) + h[i], shield: "erebor"}, // Dwarfs
|
|
{name: "Kobold (Goblin)", base: 36, odd: 1, sort: i => t[i] - s[i], shield: "moriaOrc"}, // Goblin
|
|
{name: "Uruk (Orkish)", base: 37, odd: 1, sort: i => h[i] * t[i], shield: "urukHai"}, // Orc
|
|
{
|
|
name: "Ugluk (Orkish)",
|
|
base: 37,
|
|
odd: 0.5,
|
|
sort: i => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]),
|
|
shield: "moriaOrc"
|
|
}, // Orc
|
|
{name: "Yotunn (Giants)", base: 38, odd: 0.7, sort: i => td(i, -10), shield: "pavise"}, // Giant
|
|
{name: "Rake (Drakonic)", base: 39, odd: 0.7, sort: i => -s[i], shield: "fantasy2"}, // Draconic
|
|
{name: "Arago (Arachnid)", base: 40, odd: 0.7, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
|
|
{name: "Aj'Snaga (Serpents)", base: 41, odd: 0.7, sort: i => n(i) / bd(i, [12], 10), shield: "fantasy1"}, // Serpents
|
|
// fantasy human
|
|
{name: "Anor (Human)", base: 32, odd: 1, sort: i => n(i) / td(i, 10), shield: "fantasy5"},
|
|
{name: "Dail (Human)", base: 32, odd: 1, sort: i => n(i) / td(i, 13), shield: "roman"},
|
|
{name: "Rohand (Human)", base: 16, odd: 1, sort: i => n(i) / td(i, 16), shield: "round"},
|
|
{
|
|
name: "Dulandir (Human)",
|
|
base: 31,
|
|
odd: 1,
|
|
sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i],
|
|
shield: "easterling"
|
|
}
|
|
];
|
|
}
|
|
|
|
if (culturesSet.value === "darkFantasy") {
|
|
return [
|
|
// common real-world English
|
|
{name: "Angshire", base: 1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield: "heater"},
|
|
{name: "Enlandic", base: 1, odd: 1, sort: i => n(i) / td(i, 12), shield: "heater"},
|
|
{name: "Westen", base: 1, odd: 1, sort: i => n(i) / td(i, 10), shield: "heater"},
|
|
{name: "Nortumbic", base: 1, odd: 1, sort: i => n(i) / td(i, 7), shield: "heater"},
|
|
{name: "Mercian", base: 1, odd: 1, sort: i => n(i) / td(i, 9), shield: "heater"},
|
|
{name: "Kentian", base: 1, odd: 1, sort: i => n(i) / td(i, 12), shield: "heater"},
|
|
// rare real-world western
|
|
{name: "Norse", base: 6, odd: 0.7, sort: i => n(i) / td(i, 5) / sf(i), shield: "oldFrench"},
|
|
{name: "Schwarzen", base: 0, odd: 0.3, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "gonfalon"},
|
|
{name: "Luarian", base: 2, odd: 0.3, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "oldFrench"},
|
|
{name: "Hetallian", base: 3, odd: 0.3, sort: i => n(i) / td(i, 15), shield: "oval"},
|
|
{name: "Astellian", base: 4, odd: 0.3, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
|
// rare real-world exotic
|
|
{
|
|
name: "Kiswaili",
|
|
base: 28,
|
|
odd: 0.05,
|
|
sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]),
|
|
shield: "vesicaPiscis"
|
|
},
|
|
{name: "Yoruba", base: 21, odd: 0.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield: "vesicaPiscis"},
|
|
{name: "Koryo", base: 10, odd: 0.05, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
|
{name: "Hantzu", base: 11, odd: 0.05, sort: i => n(i) / td(i, 13), shield: "banner"},
|
|
{name: "Yamoto", base: 12, odd: 0.05, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
|
{name: "Guantzu", base: 30, odd: 0.05, sort: i => n(i) / td(i, 17), shield: "banner"},
|
|
{
|
|
name: "Ulus",
|
|
base: 31,
|
|
odd: 0.05,
|
|
sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i],
|
|
shield: "banner"
|
|
},
|
|
{name: "Turan", base: 16, odd: 0.05, sort: i => n(i) / td(i, 12), shield: "round"},
|
|
{
|
|
name: "Berberan",
|
|
base: 17,
|
|
odd: 0.05,
|
|
sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i],
|
|
shield: "round"
|
|
},
|
|
{
|
|
name: "Eurabic",
|
|
base: 18,
|
|
odd: 0.05,
|
|
sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i],
|
|
shield: "round"
|
|
},
|
|
{name: "Slovan", base: 5, odd: 0.05, sort: i => (n(i) / td(i, 6)) * t[i], shield: "round"},
|
|
{
|
|
name: "Keltan",
|
|
base: 22,
|
|
odd: 0.1,
|
|
sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [6, 8]),
|
|
shield: "vesicaPiscis"
|
|
},
|
|
{name: "Elladan", base: 7, odd: 0.2, sort: i => (n(i) / td(i, 18) / sf(i)) * h[i], shield: "boeotian"},
|
|
{name: "Romian", base: 8, odd: 0.2, sort: i => n(i) / td(i, 14) / t[i], shield: "roman"},
|
|
// fantasy races
|
|
{name: "Eldar", base: 33, odd: 0.5, sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i], shield: "fantasy5"}, // Elves
|
|
{name: "Trow", base: 34, odd: 0.8, sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i], shield: "hessen"}, // Dark Elves
|
|
{name: "Durinn", base: 35, odd: 0.8, sort: i => n(i) + h[i], shield: "erebor"}, // Dwarven
|
|
{name: "Kobblin", base: 36, odd: 0.8, sort: i => t[i] - s[i], shield: "moriaOrc"}, // Goblin
|
|
{name: "Uruk", base: 37, odd: 0.8, sort: i => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: "urukHai"}, // Orc
|
|
{name: "Yotunn", base: 38, odd: 0.8, sort: i => td(i, -10), shield: "pavise"}, // Giant
|
|
{name: "Drake", base: 39, odd: 0.9, sort: i => -s[i], shield: "fantasy2"}, // Draconic
|
|
{name: "Rakhnid", base: 40, odd: 0.9, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
|
|
{name: "Aj'Snaga", base: 41, odd: 0.9, sort: i => n(i) / bd(i, [12], 10), shield: "fantasy1"} // Serpents
|
|
];
|
|
}
|
|
|
|
if (culturesSet.value === "random") {
|
|
return d3.range(count).map(function () {
|
|
const rnd = rand(nameBases.length - 1);
|
|
const name = Names.getBaseShort(rnd);
|
|
return {name, base: rnd, odd: 1, shield: getRandomShield()};
|
|
});
|
|
}
|
|
|
|
// all-world
|
|
return [
|
|
{name: "Shwazen", base: 0, odd: 0.7, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "hessen"},
|
|
{name: "Angshire", base: 1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield: "heater"},
|
|
{name: "Luari", base: 2, odd: 0.6, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "oldFrench"},
|
|
{name: "Tallian", base: 3, odd: 0.6, sort: i => n(i) / td(i, 15), shield: "horsehead2"},
|
|
{name: "Astellian", base: 4, odd: 0.6, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
|
{name: "Slovan", base: 5, odd: 0.7, sort: i => (n(i) / td(i, 6)) * t[i], shield: "round"},
|
|
{name: "Norse", base: 6, odd: 0.7, sort: i => n(i) / td(i, 5), shield: "heater"},
|
|
{name: "Elladan", base: 7, odd: 0.7, sort: i => (n(i) / td(i, 18)) * h[i], shield: "boeotian"},
|
|
{name: "Romian", base: 8, odd: 0.7, sort: i => n(i) / td(i, 15), shield: "roman"},
|
|
{name: "Soumi", base: 9, odd: 0.3, sort: i => (n(i) / td(i, 5) / bd(i, [9])) * t[i], shield: "pavise"},
|
|
{name: "Koryo", base: 10, odd: 0.1, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
|
{name: "Hantzu", base: 11, odd: 0.1, sort: i => n(i) / td(i, 13), shield: "banner"},
|
|
{name: "Yamoto", base: 12, odd: 0.1, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
|
{name: "Portuzian", base: 13, odd: 0.4, sort: i => n(i) / td(i, 17) / sf(i), shield: "spanish"},
|
|
{name: "Nawatli", base: 14, odd: 0.1, sort: i => h[i] / td(i, 18) / bd(i, [7]), shield: "square"},
|
|
{name: "Vengrian", base: 15, odd: 0.2, sort: i => (n(i) / td(i, 11) / bd(i, [4])) * t[i], shield: "wedged"},
|
|
{name: "Turchian", base: 16, odd: 0.2, sort: i => n(i) / td(i, 13), shield: "round"},
|
|
{
|
|
name: "Berberan",
|
|
base: 17,
|
|
odd: 0.1,
|
|
sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i],
|
|
shield: "round"
|
|
},
|
|
{name: "Eurabic", base: 18, odd: 0.2, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "round"},
|
|
{name: "Inuk", base: 19, odd: 0.05, sort: i => td(i, -1) / bd(i, [10, 11]) / sf(i), shield: "square"},
|
|
{name: "Euskati", base: 20, odd: 0.05, sort: i => (n(i) / td(i, 15)) * h[i], shield: "spanish"},
|
|
{name: "Yoruba", base: 21, odd: 0.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield: "vesicaPiscis"},
|
|
{
|
|
name: "Keltan",
|
|
base: 22,
|
|
odd: 0.05,
|
|
sort: i => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i],
|
|
shield: "vesicaPiscis"
|
|
},
|
|
{name: "Efratic", base: 23, odd: 0.05, sort: i => (n(i) / td(i, 22)) * t[i], shield: "diamond"},
|
|
{name: "Tehrani", base: 24, odd: 0.1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "round"},
|
|
{name: "Maui", base: 25, odd: 0.05, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "round"},
|
|
{name: "Carnatic", base: 26, odd: 0.05, sort: i => n(i) / td(i, 26), shield: "round"},
|
|
{name: "Inqan", base: 27, odd: 0.05, sort: i => h[i] / td(i, 13), shield: "square"},
|
|
{name: "Kiswaili", base: 28, odd: 0.1, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: "vesicaPiscis"},
|
|
{name: "Vietic", base: 29, odd: 0.1, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: "banner"},
|
|
{name: "Guantzu", base: 30, odd: 0.1, sort: i => n(i) / td(i, 17), shield: "banner"},
|
|
{name: "Ulus", base: 31, odd: 0.1, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"},
|
|
{name: "Hebrew", base: 42, odd: 0.2, sort: i => n(i) / td(i, 18) * sf(i), shield: "oval"} // Levantine
|
|
];
|
|
};
|
|
|
|
// expand cultures across the map (Dijkstra-like algorithm)
|
|
const expand = function () {
|
|
TIME && console.time("expandCultures");
|
|
cells = pack.cells;
|
|
|
|
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
|
pack.cultures.forEach(function (c) {
|
|
if (!c.i || c.removed) return;
|
|
queue.queue({e: c.center, p: 0, c: c.i});
|
|
});
|
|
|
|
const neutral = (cells.i.length / 5000) * 3000 * neutralInput.value; // limit cost for culture growth
|
|
const cost = [];
|
|
while (queue.length) {
|
|
const next = queue.dequeue(),
|
|
n = next.e,
|
|
p = next.p,
|
|
c = next.c;
|
|
const type = pack.cultures[c].type;
|
|
cells.c[n].forEach(function (e) {
|
|
const biome = cells.biome[e];
|
|
const biomeCost = getBiomeCost(c, biome, type);
|
|
const biomeChangeCost = biome === cells.biome[n] ? 0 : 20; // penalty on biome change
|
|
const heightCost = getHeightCost(e, cells.h[e], type);
|
|
const riverCost = getRiverCost(cells.r[e], e, type);
|
|
const typeCost = getTypeCost(cells.t[e], type);
|
|
const totalCost =
|
|
p + (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[c].expansionism;
|
|
|
|
if (totalCost > neutral) return;
|
|
|
|
if (!cost[e] || totalCost < cost[e]) {
|
|
if (cells.s[e] > 0) cells.culture[e] = c; // assign culture to populated cell
|
|
cost[e] = totalCost;
|
|
queue.queue({e, p: totalCost, c});
|
|
}
|
|
});
|
|
}
|
|
|
|
TIME && console.timeEnd("expandCultures");
|
|
};
|
|
|
|
function getBiomeCost(c, biome, type) {
|
|
if (cells.biome[pack.cultures[c].center] === biome) return 10; // tiny penalty for native biome
|
|
if (type === "Hunting") return biomesData.cost[biome] * 5; // non-native biome penalty for hunters
|
|
if (type === "Nomadic" && biome > 4 && biome < 10) return biomesData.cost[biome] * 10; // forest biome penalty for nomads
|
|
return biomesData.cost[biome] * 2; // general non-native biome penalty
|
|
}
|
|
|
|
function getHeightCost(i, h, type) {
|
|
const f = pack.features[cells.f[i]],
|
|
a = cells.area[i];
|
|
if (type === "Lake" && f.type === "lake") return 10; // no lake crossing penalty for Lake cultures
|
|
if (type === "Naval" && h < 20) return a * 2; // low sea/lake crossing penalty for Naval cultures
|
|
if (type === "Nomadic" && h < 20) return a * 50; // giant sea/lake crossing penalty for Nomads
|
|
if (h < 20) return a * 6; // general sea/lake crossing penalty
|
|
if (type === "Highland" && h < 44) return 3000; // giant penalty for highlanders on lowlands
|
|
if (type === "Highland" && h < 62) return 200; // giant penalty for highlanders on lowhills
|
|
if (type === "Highland") return 0; // no penalty for highlanders on highlands
|
|
if (h >= 67) return 200; // general mountains crossing penalty
|
|
if (h >= 44) return 30; // general hills crossing penalty
|
|
return 0;
|
|
}
|
|
|
|
function getRiverCost(r, i, type) {
|
|
if (type === "River") return r ? 0 : 100; // penalty for river cultures
|
|
if (!r) return 0; // no penalty for others if there is no river
|
|
return minmax(cells.fl[i] / 10, 20, 100); // river penalty from 20 to 100 based on flux
|
|
}
|
|
|
|
function getTypeCost(t, type) {
|
|
if (t === 1) return type === "Naval" || type === "Lake" ? 0 : type === "Nomadic" ? 60 : 20; // penalty for coastline
|
|
if (t === 2) return type === "Naval" || type === "Nomadic" ? 30 : 0; // low penalty for land level 2 for Navals and nomads
|
|
if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
|
|
return 0;
|
|
}
|
|
|
|
const getRandomShield = function () {
|
|
const type = rw(COA.shields.types);
|
|
return rw(COA.shields[type]);
|
|
};
|
|
|
|
return {generate, add, expand, getDefault, getRandomShield};
|
|
})();
|