feat: multi-parental tree

This commit is contained in:
Azgaar 2022-06-05 01:27:58 +03:00
parent 1142be65c6
commit f4ef859af8
4 changed files with 370 additions and 114 deletions

View file

@ -60,7 +60,7 @@ window.Cultures = (function () {
c.color = colors[i]; c.color = colors[i];
c.type = defineCultureType(cell); c.type = defineCultureType(cell);
c.expansionism = defineCultureExpansionism(c.type); c.expansionism = defineCultureExpansionism(c.type);
c.origin = 0; c.origins = [0];
c.code = abbreviate(c.name, codes); c.code = abbreviate(c.name, codes);
codes.push(c.code); codes.push(c.code);
cells.culture[cell] = i + 1; cells.culture[cell] = i + 1;
@ -80,7 +80,7 @@ window.Cultures = (function () {
} }
// the first culture with id 0 is for wildlands // the first culture with id 0 is for wildlands
cultures.unshift({name: "Wildlands", i: 0, base: 1, origin: null, shield: "round"}); cultures.unshift({name: "Wildlands", i: 0, base: 1, origins: [null], shield: "round"});
// make sure all bases exist in nameBases // make sure all bases exist in nameBases
if (!nameBases.length) { if (!nameBases.length) {
@ -115,7 +115,11 @@ window.Cultures = (function () {
if (cells.h[i] > 50) return "Highland"; // no penalty for hills and moutains, high for other elevations 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 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 (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))) 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 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.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 if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes
@ -163,7 +167,22 @@ window.Cultures = (function () {
const emblemShape = document.getElementById("emblemShape").value; const emblemShape = document.getElementById("emblemShape").value;
if (emblemShape === "random") shield = getRandomShield(); 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, origin: 0, code, shield}); 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) { const getDefault = function (count) {
@ -180,7 +199,8 @@ window.Cultures = (function () {
return d ? d + 1 : 1; return d ? d + 1 : 1;
}; // temperature difference fee }; // temperature difference fee
const bd = (cell, biomes, fee = 4) => (biomes.includes(cells.biome[cell]) ? 1 : fee); // biome 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 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") { if (culturesSet.value === "european") {
return [ return [
@ -208,7 +228,13 @@ window.Cultures = (function () {
{name: "Hantzu", base: 11, odd: 1, sort: i => n(i) / td(i, 13), shield: "banner"}, {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: "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: "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: "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: "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: "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: "Tehrani", base: 24, odd: 1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "round"},
@ -259,15 +285,45 @@ window.Cultures = (function () {
if (culturesSet.value === "highFantasy") { if (culturesSet.value === "highFantasy") {
return [ return [
// fantasy races // 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: "Quenian (Elfish)",
{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 base: 33,
{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 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: "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: "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: "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: "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: "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: "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: "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: "Arago (Arachnid)", base: 40, odd: 0.7, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
@ -276,7 +332,13 @@ window.Cultures = (function () {
{name: "Anor (Human)", base: 32, odd: 1, sort: i => n(i) / td(i, 10), shield: "fantasy5"}, {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: "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: "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"} {
name: "Dulandir (Human)",
base: 31,
odd: 1,
sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i],
shield: "easterling"
}
]; ];
} }
@ -296,18 +358,48 @@ window.Cultures = (function () {
{name: "Hetallian", base: 3, odd: 0.3, sort: i => n(i) / td(i, 15), shield: "oval"}, {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"}, {name: "Astellian", base: 4, odd: 0.3, sort: i => n(i) / td(i, 16), shield: "spanish"},
// rare real-world exotic // 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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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"}, {name: "Romian", base: 8, odd: 0.2, sort: i => n(i) / td(i, 14) / t[i], shield: "roman"},
// fantasy races // fantasy races
@ -350,12 +442,24 @@ window.Cultures = (function () {
{name: "Nawatli", base: 14, odd: 0.1, sort: i => h[i] / td(i, 18) / bd(i, [7]), shield: "square"}, {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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "Maui", base: 25, odd: 0.05, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "round"},
@ -394,7 +498,8 @@ window.Cultures = (function () {
const heightCost = getHeightCost(e, cells.h[e], type); const heightCost = getHeightCost(e, cells.h[e], type);
const riverCost = getRiverCost(cells.r[e], e, type); const riverCost = getRiverCost(cells.r[e], e, type);
const typeCost = getTypeCost(cells.t[e], type); const typeCost = getTypeCost(cells.t[e], type);
const totalCost = p + (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[c].expansionism; const totalCost =
p + (biomeCost + biomeChangeCost + heightCost + riverCost + typeCost) / pack.cultures[c].expansionism;
if (totalCost > neutral) return; if (totalCost > neutral) return;

View file

@ -30,8 +30,18 @@ export function resolveVersionConflicts(version) {
.attr("stroke-dasharray", null) .attr("stroke-dasharray", null)
.attr("stroke-linecap", null) .attr("stroke-linecap", null)
.attr("filter", null); .attr("filter", null);
stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt"); stateBorders
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt"); .attr("opacity", 0.8)
.attr("stroke", "#56566d")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "2")
.attr("stroke-linecap", "butt");
provinceBorders
.attr("opacity", 0.8)
.attr("stroke", "#56566d")
.attr("stroke-width", 0.5)
.attr("stroke-dasharray", "1")
.attr("stroke-linecap", "butt");
// v1.0 added state relations, provinces, forms and full names // v1.0 added state relations, provinces, forms and full names
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6); provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
@ -47,7 +57,12 @@ export function resolveVersionConflicts(version) {
// v1.0 added zones layer // v1.0 added zones layer
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none"); zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt"); zones
.attr("opacity", 0.6)
.attr("stroke", null)
.attr("stroke-width", 0)
.attr("stroke-dasharray", null)
.attr("stroke-linecap", "butt");
addZones(); addZones();
if (!markers.selectAll("*").size()) { if (!markers.selectAll("*").size()) {
Markers.generate(); Markers.generate();
@ -55,9 +70,23 @@ export function resolveVersionConflicts(version) {
} }
// v1.0 add fogging layer (state focus) // v1.0 add fogging layer (state focus)
fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none"); fogging = viewbox
.insert("g", "#ruler")
.attr("id", "fogging-cont")
.attr("mask", "url(#fog)")
.append("g")
.attr("id", "fogging")
.style("display", "none");
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("fill", "white"); defs
.append("mask")
.attr("id", "fog")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "white");
// v1.0 changes states opacity bask to regions level // v1.0 changes states opacity bask to regions level
if (statesBody.attr("opacity")) { if (statesBody.attr("opacity")) {
@ -103,12 +132,24 @@ export function resolveVersionConflicts(version) {
if (!document.getElementById("freshwater")) { if (!document.getElementById("freshwater")) {
lakes.append("g").attr("id", "freshwater"); lakes.append("g").attr("id", "freshwater");
lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null); lakes
.select("#freshwater")
.attr("opacity", 0.5)
.attr("fill", "#a6c1fd")
.attr("stroke", "#5f799d")
.attr("stroke-width", 0.7)
.attr("filter", null);
} }
if (!document.getElementById("salt")) { if (!document.getElementById("salt")) {
lakes.append("g").attr("id", "salt"); lakes.append("g").attr("id", "salt");
lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null); lakes
.select("#salt")
.attr("opacity", 0.5)
.attr("fill", "#409b8a")
.attr("stroke", "#388985")
.attr("stroke-width", 0.7)
.attr("filter", null);
} }
// v1.1 added new lake and coast groups // v1.1 added new lake and coast groups
@ -116,14 +157,42 @@ export function resolveVersionConflicts(version) {
lakes.append("g").attr("id", "sinkhole"); lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen"); lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava"); lakes.append("g").attr("id", "lava");
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null); lakes
lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null); .select("#sinkhole")
lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)"); .attr("opacity", 1)
.attr("fill", "#5bc9fd")
.attr("stroke", "#53a3b0")
.attr("stroke-width", 0.7)
.attr("filter", null);
lakes
.select("#frozen")
.attr("opacity", 0.95)
.attr("fill", "#cdd4e7")
.attr("stroke", "#cfe0eb")
.attr("stroke-width", 0)
.attr("filter", null);
lakes
.select("#lava")
.attr("opacity", 0.7)
.attr("fill", "#90270d")
.attr("stroke", "#f93e0c")
.attr("stroke-width", 2)
.attr("filter", "url(#crumpled)");
coastline.append("g").attr("id", "sea_island"); coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island"); coastline.append("g").attr("id", "lake_island");
coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("filter", "url(#dropShadow)"); coastline
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null); .select("#sea_island")
.attr("opacity", 0.5)
.attr("stroke", "#1f3846")
.attr("stroke-width", 0.7)
.attr("filter", "url(#dropShadow)");
coastline
.select("#lake_island")
.attr("opacity", 1)
.attr("stroke", "#7c8eaf")
.attr("stroke-width", 0.35)
.attr("filter", null);
} }
// v1.1 features stores more data // v1.1 features stores more data
@ -203,7 +272,13 @@ export function resolveVersionConflicts(version) {
// v1.3 added militry layer // v1.3 added militry layer
armies = viewbox.insert("g", "#icons").attr("id", "armies"); armies = viewbox.insert("g", "#icons").attr("id", "armies");
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3); armies
.attr("opacity", 1)
.attr("fill-opacity", 1)
.attr("font-size", 6)
.attr("box-size", 3)
.attr("stroke", "#000")
.attr("stroke-width", 0.3);
turnButtonOn("toggleMilitary"); turnButtonOn("toggleMilitary");
Military.generate(); Military.generate();
} }
@ -212,12 +287,23 @@ export function resolveVersionConflicts(version) {
// v1.35 added dry lakes // v1.35 added dry lakes
if (!lakes.select("#dry").size()) { if (!lakes.select("#dry").size()) {
lakes.append("g").attr("id", "dry"); lakes.append("g").attr("id", "dry");
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null); lakes
.select("#dry")
.attr("opacity", 1)
.attr("fill", "#c9bfa7")
.attr("stroke", "#8e816f")
.attr("stroke-width", 0.7)
.attr("filter", null);
} }
// v1.4 added ice layer // v1.4 added ice layer
ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none"); ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none");
ice.attr("opacity", null).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)"); ice
.attr("opacity", null)
.attr("fill", "#e8f0f6")
.attr("stroke", "#e8f0f6")
.attr("stroke-width", 1)
.attr("filter", "url(#dropShadow05)");
drawIce(); drawIce();
// v1.4 added icon and power attributes for units // v1.4 added icon and power attributes for units
@ -530,4 +616,19 @@ export function resolveVersionConflicts(version) {
// v1.84.0 moved intial screen out of maon svg // v1.84.0 moved intial screen out of maon svg
svg.select("#initial").remove(); svg.select("#initial").remove();
} }
if (version < 1.86) {
// v1.86.0 added support of multi-origin culture and religion hierarchy trees
for (const culture of pack.cultures) {
const origin = culture.origin;
delete culture.origin;
culture.origins = [origin];
}
for (const religion of pack.religions) {
const origin = religion.origin;
delete religion.origin;
religion.origins = [origin];
}
}
} }

View file

@ -143,7 +143,9 @@ function culturesEditorAddLines() {
const rural = c.rural * populationRate; const rural = c.rural * populationRate;
const urban = c.urban * populationRate * urbanization; const urban = c.urban * populationRate * urbanization;
const population = rn(rural + urban); const population = rn(rural + urban);
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to edit`; const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(
urban
)}. Click to edit`;
totalArea += area; totalArea += area;
totalPopulation += population; totalPopulation += population;
@ -167,7 +169,9 @@ function culturesEditorAddLines() {
value="${c.name}" autocorrect="off" spellcheck="false" /> value="${c.name}" autocorrect="off" spellcheck="false" />
<span class="icon-cw placeholder"></span> <span class="icon-cw placeholder"></span>
<select class="cultureType placeholder">${getTypeOptions(c.type)}</select> <select class="cultureType placeholder">${getTypeOptions(c.type)}</select>
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select> <select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(
c.base
)}</select>
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="cultureCells hide" style="width: 4em">${c.cells}</div> <div data-tip="Cells count" class="cultureCells hide" style="width: 4em">${c.cells}</div>
<span class="icon-resize-full placeholder hide"></span> <span class="icon-resize-full placeholder hide"></span>
@ -175,7 +179,9 @@ function culturesEditorAddLines() {
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Culture area" class="cultureArea hide" style="width: 6em">${si(area)} ${unit}</div> <div data-tip="Culture area" class="cultureArea hide" style="width: 6em">${si(area)} ${unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide pointer" style="width: 5em">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide pointer" style="width: 5em">${si(
population
)}</div>
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span> <span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
${getShapeOptions(selectShape, c.shield)} ${getShapeOptions(selectShape, c.shield)}
</div>`; </div>`;
@ -199,8 +205,12 @@ function culturesEditorAddLines() {
<input data-tip="Culture name. Click and type to change" class="cultureName" style="width: 7em" <input data-tip="Culture name. Click and type to change" class="cultureName" style="width: 7em"
value="${c.name}" autocorrect="off" spellcheck="false" /> value="${c.name}" autocorrect="off" spellcheck="false" />
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span> <span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
<select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">${getTypeOptions(c.type)}</select> <select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">${getTypeOptions(
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select> c.type
)}</select>
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(
c.base
)}</select>
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="cultureCells hide" style="width: 4em">${c.cells}</div> <div data-tip="Cells count" class="cultureCells hide" style="width: 4em">${c.cells}</div>
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span> <span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
@ -216,7 +226,9 @@ function culturesEditorAddLines() {
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Culture area" class="cultureArea hide" style="width: 6em">${si(area)} ${unit}</div> <div data-tip="Culture area" class="cultureArea hide" style="width: 6em">${si(area)} ${unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
<div data-tip="${populationTip}" class="culturePopulation hide pointer" style="width: 5em">${si(population)}</div> <div data-tip="${populationTip}" class="culturePopulation hide pointer" style="width: 5em">${si(
population
)}</div>
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span> <span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
${getShapeOptions(selectShape, c.shield)} ${getShapeOptions(selectShape, c.shield)}
<span data-tip="Remove culture" class="icon-trash-empty hide"></span> <span data-tip="Remove culture" class="icon-trash-empty hide"></span>
@ -276,12 +288,14 @@ function getShapeOptions(selectShape, selected) {
const shapes = Object.keys(COA.shields.types) const shapes = Object.keys(COA.shields.types)
.map(type => Object.keys(COA.shields[type])) .map(type => Object.keys(COA.shields[type]))
.flat(); .flat();
const options = shapes.map(shape => `<option ${shape === selected ? "selected" : ""} value="${shape}">${capitalize(shape)}</option>`); const options = shapes.map(
shape => `<option ${shape === selected ? "selected" : ""} value="${shape}">${capitalize(shape)}</option>`
);
return `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureEmblems hide">${options}</select>`; return `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureEmblems hide">${options}</select>`;
} }
function cultureHighlightOn(event) { function cultureHighlightOn(event) {
const culture = +event.target.dataset.id; const culture = Number(event.id || event.target.dataset.id);
const $info = byId("cultureInfo"); const $info = byId("cultureInfo");
if ($info) { if ($info) {
d3.select("#hierarchy") d3.select("#hierarchy")
@ -314,7 +328,7 @@ function cultureHighlightOn(event) {
} }
function cultureHighlightOff(event) { function cultureHighlightOff(event) {
const culture = +event.target.dataset.id; const culture = Number(event.id || event.target.dataset.id);
const $info = byId("cultureInfo"); const $info = byId("cultureInfo");
if ($info) { if ($info) {
d3.select("#hierarchy") d3.select("#hierarchy")
@ -412,7 +426,14 @@ function cultureChangeEmblemsShape() {
}); });
pack.provinces.forEach(province => { pack.provinces.forEach(province => {
if (pack.cells.culture[province.center] !== culture || !province.i || province.removed || !province.coa || province.coa === "custom") return; if (
pack.cells.culture[province.center] !== culture ||
!province.i ||
province.removed ||
!province.coa ||
province.coa === "custom"
)
return;
if (shape === province.coa.shield) return; if (shape === province.coa.shield) return;
province.coa.shield = shape; province.coa.shield = shape;
rerenderCOA("provinceCOA" + province.i, province.coa); rerenderCOA("provinceCOA" + province.i, province.coa);
@ -438,8 +459,12 @@ function changePopulation() {
const burgs = pack.burgs.filter(b => !b.removed && b.culture === cultureId); const burgs = pack.burgs.filter(b => !b.removed && b.culture === cultureId);
alertMessage.innerHTML = /* html */ `Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban: alertMessage.innerHTML = /* html */ `Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"} /> <input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${
<p>Total population: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`; burgs.length ? "" : "disabled"
} />
<p>Total population: ${l(total)} <span id="totalPop">${l(
total
)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@ -522,9 +547,11 @@ function removeCulture(cultureId) {
}); });
cultures[cultureId].removed = true; cultures[cultureId].removed = true;
const origin = cultures[cultureId].origin; cultures
cultures.forEach(c => { .filter(c => c.i && !c.removed)
if (c.origin === cultureId) c.origin = origin; .forEach(c => {
c.origins = c.origins.filter(origin => origin !== cultureId);
if (!c.origins.length) c.origins = [0];
}); });
refreshCulturesEditor(); refreshCulturesEditor();
} }
@ -552,7 +579,12 @@ function cultureRemove() {
function drawCultureCenters() { function drawCultureCenters() {
const tooltip = "Drag to move the culture center (ancestral home)"; const tooltip = "Drag to move the culture center (ancestral home)";
debug.select("#cultureCenters").remove(); debug.select("#cultureCenters").remove();
const cultureCenters = debug.append("g").attr("id", "cultureCenters").attr("stroke-width", 2).attr("stroke", "#444444").style("cursor", "move"); const cultureCenters = debug
.append("g")
.attr("id", "cultureCenters")
.attr("stroke-width", 2)
.attr("stroke", "#444444")
.style("cursor", "move");
const data = pack.cultures.filter(c => c.i && !c.removed); const data = pack.cultures.filter(c => c.i && !c.removed);
cultureCenters cultureCenters
@ -623,17 +655,17 @@ function togglePercentageMode() {
function showHierarchy() { function showHierarchy() {
// build hierarchy tree // build hierarchy tree
pack.cultures[0].origin = null; pack.cultures[0].origins = [null];
const validCultures = pack.cultures.filter(c => !c.removed); const validCultures = pack.cultures.filter(c => !c.removed);
if (validCultures.length < 3) return tip("Not enough cultures to show hierarchy", false, "error"); if (validCultures.length < 3) return tip("Not enough cultures to show hierarchy", false, "error");
const root = d3 const root = d3
.stratify() .stratify()
.id(d => d.i) .id(d => d.i)
.parentId(d => d.origin)(validCultures); .parentId(d => d.origins[0])(validCultures);
const treeWidth = root.leaves().length; const treeWidth = root.leaves().length;
const treeHeight = root.height; const treeHeight = root.height;
const width = treeWidth * 40; const width = Math.max(treeWidth * 40, 300);
const height = treeHeight * 60; const height = treeHeight * 60;
const margin = {top: 10, right: 10, bottom: -5, left: 10}; const margin = {top: 10, right: 10, bottom: -5, left: 10};
@ -649,39 +681,59 @@ function showHierarchy() {
.attr("id", "hierarchy") .attr("id", "hierarchy")
.attr("width", width) .attr("width", width)
.attr("height", height) .attr("height", height)
.style("text-anchor", "middle"); .style("text-anchor", "middle")
.style("min-width", "300px");
const graph = svg.append("g").attr("transform", `translate(10, -45)`); const graph = svg.append("g").attr("transform", `translate(10, -45)`);
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
const primaryLinks = links.append("g");
const secondaryLinks = links.append("g").attr("stroke-dasharray", 1);
const nodes = graph.append("g"); const nodes = graph.append("g");
// render helper functions
const getLinkPath = d => {
const {
source: {x: sx, y: sy},
target: {x: tx, y: ty}
} = d;
return `M${sx},${sy} C${sx},${(sy * 3 + ty) / 4} ${tx},${(sy * 2 + ty) / 3} ${tx},${ty}`;
};
const getSecondaryLinks = root => {
const nodes = root.descendants();
const links = [];
for (const node of nodes) {
const origins = node.data.origins;
if (node.depth < 2) continue;
for (let i = 1; i < origins.length; i++) {
const source = nodes.find(n => n.data.i === origins[i]);
if (source) links.push({source, target: node});
}
}
return links;
};
const nodePathMap = {
undefined: "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0", // small circle
Generic: "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0", // circle
River: "M0,-14L14,0L0,14L-14,0Z", // diamond
Lake: "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z", // hexagon
Naval: "M-11,-11h22v22h-22Z", // square
Highland: "M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z", // concave square
Nomadic: "M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z", // octagon
Hunting: "M0,-14l14,11l-6,14h-16l-6,-14Z" // pentagon
};
const getNodePath = d => nodePathMap[d.data.type];
renderTree(); renderTree();
function renderTree() { function renderTree() {
treeLayout(root); treeLayout(root);
links
.selectAll("path") primaryLinks.selectAll("path").data(root.links()).enter().append("path").attr("d", getLinkPath);
.data(root.links()) secondaryLinks.selectAll("path").data(getSecondaryLinks(root)).enter().append("path").attr("d", getLinkPath);
.enter()
.append("path")
.attr("d", d => {
return (
"M" +
d.source.x +
"," +
d.source.y +
"C" +
d.source.x +
"," +
(d.source.y * 3 + d.target.y) / 4 +
" " +
d.target.x +
"," +
(d.source.y * 2 + d.target.y) / 3 +
" " +
d.target.x +
"," +
d.target.y
);
});
const node = nodes const node = nodes
.selectAll("g") .selectAll("g")
@ -691,42 +743,29 @@ function showHierarchy() {
.attr("data-id", d => d.data.i) .attr("data-id", d => d.data.i)
.attr("stroke", "#333333") .attr("stroke", "#333333")
.attr("transform", d => `translate(${d.x}, ${d.y})`) .attr("transform", d => `translate(${d.x}, ${d.y})`)
.on("mouseenter", () => cultureHighlightOn(event)) .on("mouseenter", cultureHighlightOn)
.on("mouseleave", () => cultureHighlightOff(event)) .on("mouseleave", cultureHighlightOff)
.call(d3.drag().on("start", d => dragToReorigin(d))); .call(d3.drag().on("start", dragToReorigin));
node node
.append("path") .append("path")
.attr("d", d => { .attr("d", getNodePath)
if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0"; .attr("fill", d => d.data.color || "#ffffff")
// small circle
else if (d.data.type === "Generic") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0";
// circle
else if (d.data.type === "River") return "M0,-14L14,0L0,14L-14,0Z";
// diamond
else if (d.data.type === "Lake") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z";
// hexagon
else if (d.data.type === "Naval") return "M-11,-11h22v22h-22Z"; // square
if (d.data.type === "Highland") return "M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z"; // concave square
if (d.data.type === "Nomadic") return "M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z"; // octagon
if (d.data.type === "Hunting") return "M0,-14l14,11l-6,14h-16l-6,-14Z"; // pentagon
return "M-11,-11h22v22h-22Z"; // square
})
.attr("fill", d => (d.data.i ? d.data.color : "#ffffff"))
.attr("stroke-dasharray", d => (d.data.cells ? "null" : "1")); .attr("stroke-dasharray", d => (d.data.cells ? "null" : "1"));
node node
.append("text") .append("text")
.attr("dy", ".35em") .attr("dy", ".35em")
.text(d => (d.data.i ? d.data.code : "")); .text(d => d.data.code || "");
} }
$("#alert").dialog({ $("#alert").dialog({
title: "Cultures tree", title: "Cultures tree",
width: fitContent(), width: fitContent(),
minWidth: "20vw",
resizable: false, resizable: false,
position: {my: "left center", at: "left+10 center", of: "svg"}, position: {my: "left center", at: "left+10 center", of: "svg"},
buttons: {}, buttons: null,
close: () => { close: () => {
alertMessage.innerHTML = ""; alertMessage.innerHTML = "";
} }
@ -745,14 +784,17 @@ function showHierarchy() {
originLine.remove(); originLine.remove();
const selected = graph.select("path.selected"); const selected = graph.select("path.selected");
if (!selected.size()) return; if (!selected.size()) return;
const culture = d.data.i; const cultureId = d.data.i;
const oldOrigin = d.data.origin; let newOrigin = Number(selected.datum().data.i);
let newOrigin = selected.datum().data.i; if (cultureId === newOrigin) return; // dragged to itself
if (newOrigin == oldOrigin) return; // already a child of the selected node if (d.data.origins.includes(newOrigin)) return; // already a child of the selected node
if (newOrigin == culture) newOrigin = 0; // move to top if (newOrigin && d.descendants().some(node => node.id === newOrigin)) return; // cannot be a child of its own child
if (newOrigin && d.descendants().some(node => node.id == newOrigin)) return; // cannot be a child of its own child
pack.cultures[culture].origin = d.data.origin = newOrigin; // change data const culture = pack.cultures[cultureId];
showHierarchy(); // update hierarchy if (culture.origins[0] === 0) culture.origins = [];
culture.origins.push(newOrigin);
showHierarchy();
}); });
} }
@ -853,7 +895,13 @@ function changeCultureForSelection(selection) {
// change of append new element // change of append new element
if (exists.size()) exists.attr("data-culture", cultureNew).attr("fill", color).attr("stroke", color); if (exists.size()) exists.attr("data-culture", cultureNew).attr("fill", color).attr("stroke", color);
else else
temp.append("polygon").attr("data-cell", i).attr("data-culture", cultureNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color); temp
.append("polygon")
.attr("data-cell", i)
.attr("data-culture", cultureNew)
.attr("points", getPackPolygon(i))
.attr("fill", color)
.attr("stroke", color);
}); });
} }
@ -921,7 +969,8 @@ function addCulture() {
const point = d3.mouse(this); const point = d3.mouse(this);
const center = findCell(point[0], point[1]); const center = findCell(point[0], point[1]);
if (pack.cells.h[center] < 20) return tip("You cannot place culture center into the water. Please click on a land cell", false, "error"); if (pack.cells.h[center] < 20)
return tip("You cannot place culture center into the water. Please click on a land cell", false, "error");
const occupied = pack.cultures.some(c => !c.removed && c.center === center); const occupied = pack.cultures.some(c => !c.removed && c.center === center);
if (occupied) return tip("This cell is already a culture center. Please select a different cell", false, "error"); if (occupied) return tip("This cell is already a culture center. Please select a different cell", false, "error");
@ -989,7 +1038,7 @@ async function uploadCulturesData() {
current.color = c.color; current.color = c.color;
current.expansionism = +c.expansionism; current.expansionism = +c.expansionism;
current.origin = +c.origin; current.origins = JSON.parse(c.origins);
if (cultureTypes.includes(c.type)) current.type = c.type; if (cultureTypes.includes(c.type)) current.type = c.type;
else current.type = "Generic"; else current.type = "Generic";

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.85.02"; // generator version, update each time const version = "1.86.00"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -28,6 +28,7 @@ const version = "1.85.02"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>Hierarchy tree: cultures and religions can have multiple parents</li>
<li>Heightmap selection screen</li> <li>Heightmap selection screen</li>
<li>Dialogs optimization for mobile</li> <li>Dialogs optimization for mobile</li>
<li>New heightmap template: Fractious</li> <li>New heightmap template: Fractious</li>