This commit is contained in:
Azgaar 2020-04-23 19:44:32 +03:00
parent e7b4d0e39a
commit 72d124d95d
26 changed files with 713 additions and 498 deletions

View file

@ -0,0 +1,50 @@
"use strict";
function showBattleScreen(attacker, defender) {
if (customization) return;
closeDialogs(".stable");
const battle = {name:"Battle", attackers:[attacker], defenders:[defender]};
const battleAttackers = document.getElementById("battleAttackers");
const battleDefenders = document.getElementById("battleDefenders");
addHeaders();
addRegiment(battleAttackers, attacker);
addRegiment(battleDefenders, defender);
$("#battleScreen").dialog({
title: battle.name, resizable: false, width: fitContent(), close: closeBattleScreen,
position: {my: "center", at: "center", of: "#map"}
});
if (modules.showBattleScreen) return;
modules.showBattleScreen = true;
// add listeners
//document.getElementById("regimentNameRestore").addEventListener("click", restoreName);
function addHeaders() {
document.getElementById("battleScreen").querySelectorAll("th").forEach(el => el.remove());
const attackers = battleAttackers.querySelector("tr");
const defenders = battleDefenders.querySelector("tr");
let headers = "<th></th>";
for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, ' '));
headers += `<th data-tip="${label}">${u.icon}</th>`;
}
headers += "<th>Total</th>";
attackers.insertAdjacentHTML("beforebegin", headers);
defenders.insertAdjacentHTML("beforebegin", headers);
}
function addRegiment(div, regiment) {
const reg = document.createElement("div");
reg.innerHTML = regiment.name;
div.append(reg);
}
function closeBattleScreen() {
}
}

View file

@ -215,6 +215,8 @@ function editBiomes() {
function addCustomBiome() {
const b = biomesData, i = biomesData.i.length;
if (i > 254) {tip("Maximum number of biomes reached (255), data cleansing is required", false, "error"); return;}
b.i.push(i);
b.color.push(getRandomColor());
b.habitability.push(50);

View file

@ -294,13 +294,14 @@ function editBurg(id) {
function openMFCG(seed) {
if (!seed && burg.MFCGlink) {openURL(burg.MFCGlink); return;}
const cells = pack.cells;
const name = elSelected.text();
const size = Math.max(Math.min(rn(burg.population), 65), 6);
const s = burg.MFCG || defSeed;
const cell = burg.cell;
const hub = +pack.cells.road[cell] > 50;
const river = pack.cells.r[cell] ? 1 : 0;
const hub = +cells.road[cell] > 50;
const river = cells.r[cell] ? 1 : 0;
const coast = +burg.port;
const citadel = +burg.citadel;
@ -309,8 +310,31 @@ function editBurg(id) {
const temple = +burg.temple;
const shanty = +burg.shanty;
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : "";
function getSeaDirections(i) {
const p1 = cells.p[i];
const p2 = cells.p[cells.haven[i]];
let deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90;
if (deg < 0) deg += 360;
let norm = rn(normalize(deg, 0, 360) * 8) / 4;
if (norm === 2) norm = 0;
return "sea="+norm;
// debug.selectAll("*").remove();
// pack.burgs.filter(b => b.port).forEach(b => {
// var p1 = pack.cells.p[b.cell];
// var p2 = pack.cells.p[pack.cells.haven[b.cell]];
// var deg = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180 / Math.PI - 90;
// if (deg < 0) deg += 360;
// var norm = rn(normalize(deg, 0, 360) * 8) / 4;
// if (norm === 2) norm = 0;
// debug.append("line").attr("x1", p1[0]).attr("y1", p1[1]).attr("x2", p2[0]).attr("y2", p2[1]).attr("stroke", "red").attr("stroke-width", .2);
// debug.append("circle").attr("cx", b.x).attr("cy", b.y).attr("r", .4);
// debug.append("text").attr("x", b.x+1).attr("y", b.y).attr("font-size", 2).text(rn(norm, 2));
// });
}
const site = "http://fantasycities.watabou.ru/";
const url = `${site}?name=${name}&size=${size}&seed=${s}&hub=${hub}&random=0&continuous=0&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}`;
const url = `${site}?name=${name}&size=${size}&seed=${s}&hub=${hub}&random=0&continuous=0&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
openURL(url);
}
}

View file

@ -26,6 +26,7 @@ function clicked() {
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
else if (grand.id === "burgLabels") editBurg();
else if (grand.id === "burgIcons") editBurg();
else if (parent.id === "ice") editIce();
else if (parent.id === "terrain") editReliefIcon();
else if (parent.id === "markers") editMarker();
else if (grand.id === "coastline") editCoastline();
@ -581,4 +582,43 @@ function highlightElement(element) {
let y = box.y + box.height / 2;
if (tr[1]) y += tr[1];
if (scale >= 2) zoomTo(x, y, scale, 1600);
}
function selectIcon(initial, callback) {
if (!callback) return;
$("#iconSelector").dialog();
const table = document.getElementById("iconTable");
const input = document.getElementById("iconInput");
input.value = initial;
if (!table.innerHTML) {
const icons = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","⚒️","🛡️","👑","⚜️",
"☠️","🎆","🗡️","🔪","⛏️","🔥","🩸","💧","🐾","🎪","🏰","🏯","⛓️","❤️","💘","💜","📜","🔔",
"🔱","💎","🌈","🌠","✨","💥","☀️","🌙","⚡","❄️","♨️","🎲","🚨","🌉","🗻","🌋","🧱",
"⚖️","✂️","🎵","👗","🎻","🎨","🎭","⛲","💉","📖","📕","🎁","💍","⏳","🕸️","⚗️","☣️","☢️",
"🔰","🎖️","🚩","🏳️","🏴","💪","✊","👊","🤜","🤝","🙏","🧙","🧙‍♀️","💂","🤴","🧛","🧟","🧞","🧝","👼",
"👻","👺","👹","🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
"🦅","🕊️","🐓","🦇","🦜","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏","🐭","🐁","🐀",
"🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐠","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀",
"🌳","🌲","🎄","🌴","🍂","🍁","🌵","☘️","🍀","🌿","🌱","🌾","🍄","🌽","🌸","🌹","🌻",
"🍒","🍏","🍇","🍉","🍅","🍓","🥔","🥕","🥩","🍗","🍞","🍻","🍺","🍲","🍷"
];
let row = "";
for (let i=0; i < icons.length; i++) {
if (i%17 === 0) row = table.insertRow(i/17|0);
const cell = row.insertCell(i%17);
cell.innerHTML = icons[i];
}
}
table.onclick = e => {if (e.target.tagName === "TD") {input.value = e.target.innerHTML; callback(input.value)}};
table.onmouseover = e => {if (e.target.tagName === "TD") tip(`Click to select ${e.target.innerHTML} icon`)};
$("#iconSelector").dialog({width: fitContent(), title: "Select Icon",
buttons: {
Apply: function() {callback(input.value||""); $(this).dialog("close")},
Close: function() {callback(initial); $(this).dialog("close")}}
});
}

View file

@ -106,6 +106,7 @@ function showMapTooltip(point, e, i, g) {
if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;}
if (group === "coastline") {tip("Click to edit the coastline"); return;}
if (group === "zones") {tip(path[path.length-8].dataset.description); return;}
if (group === "ice") {tip("Click to edit the Ice"); return;}
// covering elements
if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else
@ -403,6 +404,7 @@ document.addEventListener("keyup", event => {
else if (key === 85) toggleRoutes(); // "U" to toggle Routes layer
else if (key === 84) toggleTemp(); // "T" to toggle Temperature layer
else if (key === 78) togglePopulation(); // "N" to toggle Population layer
else if (key === 74) toggleIce(); // "J" to toggle Ice layer
else if (key === 65) togglePrec(); // "A" to toggle Precipitation layer
else if (key === 76) toggleLabels(); // "L" to toggle Labels layer
else if (key === 73) toggleIcons(); // "I" to toggle Icons layer

View file

@ -965,7 +965,7 @@ function editHeightmap() {
closeDialogs("#imageConverter");
$("#imageConverter").dialog({
title: "Image Converter", minHeight: "auto", width: "19.5em", resizable: false,
title: "Image Converter", maxHeight: svgHeight*.75, minHeight: "auto", width: "19.5em", resizable: false,
position: {my: "right top", at: "right-10 top+10", of: "svg"},
beforeClose: closeImageConverter
});

105
modules/ui/ice-editor.js Normal file
View file

@ -0,0 +1,105 @@
"use strict";
function editIce() {
if (customization) return;
closeDialogs(".stable");
if (!layerIsOn("toggleIce")) toggleIce();
elSelected = d3.select(d3.event.target);
const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
document.getElementById("iceRandomize").style.display = type === "Glacier" ? "none" : "inline-block";
document.getElementById("iceSize").style.display = type === "Glacier" ? "none" : "inline-block";
if (type === "Iceberg") document.getElementById("iceSize").value = +elSelected.attr("size");
ice.selectAll("*").classed("draggable", true).call(d3.drag().on("drag", dragElement));
$("#iceEditor").dialog({
title: "Edit "+type, resizable: false,
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
close: closeEditor
});
if (modules.editIce) return;
modules.editIce = true;
// add listeners
document.getElementById("iceEditStyle").addEventListener("click", () => editStyle("ice"));
document.getElementById("iceRandomize").addEventListener("click", randomizeShape);
document.getElementById("iceSize").addEventListener("input", changeSize);
document.getElementById("iceNew").addEventListener("click", toggleAdd);
document.getElementById("iceRemove").addEventListener("click", removeIce);
function randomizeShape() {
const c = grid.points[+elSelected.attr("cell")];
const s = +elSelected.attr("size");
const i = ra(grid.cells.i), cn = grid.points[i];
const poly = getGridPolygon(i).map(p => [p[0]-cn[0], p[1]-cn[1]]);
const points = poly.map(p => [rn(c[0] + p[0] * s, 2), rn(c[1] + p[1] * s, 2)]);
elSelected.attr("points", points);
}
function changeSize() {
const c = grid.points[+elSelected.attr("cell")];
const s = +elSelected.attr("size");
const flat = elSelected.attr("points").split(",").map(el => +el);
const pairs = [];
while (flat.length) pairs.push(flat.splice(0,2));
const poly = pairs.map(p => [(p[0]-c[0]) / s, (p[1]-c[1]) / s]);
const size = +this.value;
const points = poly.map(p => [rn(c[0] + p[0] * size, 2), rn(c[1] + p[1] * size, 2)]);
elSelected.attr("points", points).attr("size", size);
}
function toggleAdd() {
document.getElementById("iceNew").classList.toggle("pressed");
if (document.getElementById("iceNew").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", addIcebergOnClick);
tip("Click on map to create an iceberg. Hold Shift to add multiple", true);
} else {
clearMainTip();
viewbox.on("click", clicked).style("cursor", "default");
}
}
function addIcebergOnClick() {
const point = d3.mouse(this);
const i = findGridCell(point[0], point[1]);
const c = grid.points[i];
const s = +document.getElementById("iceSize").value;
const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) / s)|0, (p[1] + (c[1]-p[1]) / s)|0]);
const iceberg = ice.append("polygon").attr("points", points).attr("cell", i).attr("size", s);
iceberg.call(d3.drag().on("drag", dragElement));
if (d3.event.shiftKey === false) toggleAdd();
}
function removeIce() {
const type = elSelected.attr("type") ? "Glacier" : "Iceberg";
alertMessage.innerHTML = `Are you sure you want to remove the ${type}?`;
$("#alert").dialog({resizable: false, title: "Remove "+type,
buttons: {
Remove: function() {
$(this).dialog("close");
elSelected.remove();
$("#iceEditor").dialog("close");
},
Cancel: function() {$(this).dialog("close");}
}
});
}
function dragElement() {
const tr = parseTransform(this.getAttribute("transform"));
const dx = +tr[0] - d3.event.x, dy = +tr[1] - d3.event.y;
d3.event.on("drag", function() {
const x = d3.event.x, y = d3.event.y;
this.setAttribute("transform", `translate(${(dx+x)},${(dy+y)})`);
});
}
function closeEditor() {
ice.selectAll("*").classed("draggable", false).call(d3.drag().on("drag", null));
clearMainTip();
iceNew.classList.remove("pressed");
unselect();
}
}

View file

@ -32,10 +32,10 @@ function getDefaultPresets() {
"cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"provinces": ["toggleBorders", "toggleIcons", "toggleProvinces", "toggleRivers", "toggleScaleBar"],
"biomes": ["toggleBiomes", "toggleRivers", "toggleScaleBar"],
"biomes": ["toggleBiomes", "toggleIce", "toggleRivers", "toggleScaleBar"],
"heightmap": ["toggleHeight", "toggleRivers"],
"physical": ["toggleCoordinates", "toggleHeight", "toggleRivers", "toggleScaleBar"],
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"poi": ["toggleBorders", "toggleHeight", "toggleIce", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
"military": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
"landmass": ["toggleScaleBar"]
}
@ -345,7 +345,7 @@ function drawBiomes() {
const edgeVerticle = cells.v[i].find(v => vertices.c[v].some(i => cells.biome[i] !== b));
const chain = connectVertices(edgeVerticle, b);
if (chain.length < 3) continue;
const points = chain.map(v => vertices.p[v]);
const points = clipPoly(chain.map(v => vertices.p[v]), 1);
paths[b] += "M" + points.join("L") + "Z";
}
@ -456,38 +456,77 @@ function drawCells() {
cells.append("path").attr("d", path);
}
function drawSeaIce() {
const seaIce = viewbox.append("g").attr("id", "seaIce").attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("filter", "url(#dropShadow05)");//.attr("opacity", .8);
for (const i of grid.cells.i) {
const t = grid.cells.temp[i] ;
if (t > 2) continue;
if (t > -5 && grid.cells.h[i] >= 20) continue;
if (t < -5) drawpolygon(i);
if (P(normalize(t, -5.5, 2.5))) continue; // t[-5; 2]
const size = t < -14 ? 0 : t > -6 ? (7 + t) / 10 : (15 + t) / 100; // [0; 1], where 0 = full size, 1 = zero size
resizePolygon(i, rn(size * (.2 + rand() * .9), 2));
function toggleIce() {
if (!layerIsOn("toggleIce")) {
turnButtonOn("toggleIce");
$('#ice').fadeIn();
if (event && isCtrlClick(event)) editStyle("ice");
} else {
if (event && isCtrlClick(event)) {editStyle("ice"); return;}
$('#ice').fadeOut();
turnButtonOff("toggleIce");
}
}
// -9: .06
// -8: .07
// -7: .08
// -6: .09
function drawIce() {
const cells = grid.cells, vertices = grid.vertices, n = cells.i.length, temp = cells.temp, h = cells.h;
const used = new Uint8Array(cells.i.length);
Math.seedrandom(seed);
// -5: .2
// -4: .3
// -3: .4
// -2: .5
// -1: .6
// 0: .7
const shieldMin = -6; // min temp to form ice shield (glacier)
const icebergMax = 2; // max temp to form an iceberg
function drawpolygon(i) {
seaIce.append("polygon").attr("points", getGridPolygon(i));
for (const i of grid.cells.i) {
const t = temp[i];
if (t > icebergMax) continue; // too warm: no ice
if (t > shieldMin && h[i] >= 20) continue; // non-glacier land: no ice
if (t <= shieldMin) {
// very cold: ice shield
if (used[i]) continue; // already rendered
const onborder = cells.c[i].some(n => temp[n] > shieldMin);
if (!onborder) continue; // need to start from onborder cell
const vertex = cells.v[i].find(v => vertices.c[v].some(i => temp[i] > shieldMin));
const chain = connectVertices(vertex);
if (chain.length < 3) continue;
const points = clipPoly(chain.map(v => vertices.p[v]));
ice.append("polygon").attr("points", points).attr("type", "iceShield");
continue;
}
// mildly cold: iceberd
if (P(normalize(t, -7, 2.5))) continue; // t[-5; 2] cold: skip some cells
if (grid.features[cells.f[i]].type === "lake") continue; // lake: no icebers
let size = (6.5 + t) / 10; // iceberg size: 0 = full size, 1 = zero size
if (cells.t[i] === -1) size *= 1.3; // coasline: smaller icebers
size = Math.min(size * (.4 + rand() * 1.2), .95); // randomize iceberd size
resizePolygon(i, size);
}
function resizePolygon(i, s) {
const c = grid.points[i];
const points = getGridPolygon(i).map(p => [p[0] + (c[0]-p[0]) * s, p[1] + (c[1]-p[1]) * s]);
seaIce.append("polygon").attr("points", points);
const points = getGridPolygon(i).map(p => [(p[0] + (c[0]-p[0]) * s)|0, (p[1] + (c[1]-p[1]) * s)|0]);
ice.append("polygon").attr("points", points).attr("cell", i).attr("size", rn(1-s, 2));
}
// connect vertices to chain
function connectVertices(start) {
const chain = []; // vertices chain to form a path
for (let i=0, current = start; i === 0 || current !== start && i < 20000; i++) {
const prev = last(chain); // previous vertex in chain
chain.push(current); // add current vertex to sequence
const c = vertices.c[current]; // cells adjacent to vertex
c.filter(c => temp[c] <= shieldMin).forEach(c => used[c] = 1);
const c0 = c[0] >= n || temp[c[0]] > shieldMin;
const c1 = c[1] >= n || temp[c[1]] > shieldMin;
const c2 = c[2] >= n || temp[c[2]] > shieldMin;
const v = vertices.v[current]; // neighboring vertices
if (v[0] !== prev && c0 !== c1) current = v[0];
else if (v[1] !== prev && c1 !== c2) current = v[1];
else if (v[2] !== prev && c0 !== c2) current = v[2];
if (current === chain[chain.length - 1]) {console.error("Next vertex is not found"); break;}
}
return chain;
}
}

View file

@ -27,7 +27,7 @@ function editMarker() {
document.getElementById("markerIconSize").addEventListener("input", changeIconSize);
document.getElementById("markerIconShiftX").addEventListener("input", changeIconShiftX);
document.getElementById("markerIconShiftY").addEventListener("input", changeIconShiftY);
document.getElementById("markerIconCustom").addEventListener("input", applyCustomUnicodeIcon);
document.getElementById("markerIconSelect").addEventListener("click", selectMarkerIcon);
document.getElementById("markerStyle").addEventListener("click", toggleStyleSection);
document.getElementById("markerSize").addEventListener("input", changeMarkerSize);
@ -73,13 +73,7 @@ function editMarker() {
markerIconFill.value = icon.attr("fill");
markerToggleBubble.className = symbol.select("circle").attr("opacity") === "0" ? "icon-info" : "icon-info-circled";
const table = document.getElementById("markerIconTable");
let selected = table.getElementsByClassName("selected");
if (selected.length) selected[0].removeAttribute("class");
selected = document.querySelectorAll("#markerIcon" + icon.text().codePointAt());
if (selected.length) selected[0].className = "selected";
markerIconCustom.value = selected.length ? "" : icon.text();
markerIconSelect.innerHTML = icon.text();
}
function toggleGroupSection() {
@ -162,242 +156,20 @@ function editMarker() {
if (markerIconSection.style.display === "inline-block") {
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "inline-block");
markerIconSection.style.display = "none";
markerIconSelect.style.display = "none";
} else {
markerEditor.querySelectorAll("button:not(#markerIcon)").forEach(b => b.style.display = "none");
markerIconSection.style.display = "inline-block";
if (!markerIconTable.innerHTML) drawIconsList();
markerIconSelect.style.display = "inline-block";
}
}
function drawIconsList() {
const icons = [
// emoticons in FF:
["2693", "⚓", "Anchor"],
["26EA", "⛪", "Church"],
["1F3EF", "🏯", "Japanese Castle"],
["1F3F0", "🏰", "Castle"],
["1F5FC", "🗼", "Tower"],
["1F3E0", "🏠", "House"],
["1F3AA", "🎪", "Tent"],
["1F3E8", "🏨", "Hotel"],
["1F4B0", "💰", "Money bag"],
["1F6A8", "🚨", "Revolving Light"],
["1F309", "🌉", "Bridge at Night"],
["1F5FB", "🗻", "Mountain"],
["1F30B", "🌋", "Volcano"],
["270A", "✊", "Raised Fist"],
["1F44A", "👊", "Oncoming Fist"],
["1F4AA", "💪", "Flexed Biceps"],
["1F47C", "👼", "Baby Angel"],
["1F40E", "🐎", "Horse"],
["1F434", "🐴", "Horse Face"],
["1F42E", "🐮", "Cow"],
["1F43A", "🐺", "Wolf Face"],
["1F435", "🐵", "Monkey face"],
["1F437", "🐷", "Pig face"],
["1F414", "🐔", "Chicken"],
["1F411", "🐑", "Ewe"],
["1F42B", "🐫", "Camel"],
["1F418", "🐘", "Elephant"],
["1F422", "🐢", "Turtle"],
["1F40C", "🐌", "Snail"],
["1F40D", "🐍", "Snake"],
["1F41D", "🐝", "Honeybee"],
["1F41C", "🐜", "Ant"],
["1F41B", "🐛", "Bug"],
["1F426", "🐦", "Bird"],
["1F438", "🐸", "Frog Face"],
["1F433", "🐳", "Whale"],
["1F42C", "🐬", "Dolphin"],
["1F420", "🐟", "Fish"],
["1F480", "💀", "Skull"],
["1F432", "🐲", "Dragon Head"],
["1F479", "👹", "Ogre"],
["1F47A", "👺", "Goblin"],
["1F47B", "👻", "Ghost"],
["1F47E", "👾", "Alien"],
["1F383", "🎃", "Jack-O-Lantern"],
["1F384", "🎄", "Christmas Tree"],
["1F334", "🌴", "Palm"],
["1F335", "🌵", "Cactus"],
["2618", "☘️", "Shamrock"],
["1F340", "🍀", "Four Leaf Clover"],
["1F341", "🍁", "Maple Leaf"],
["1F33F", "🌿", "Herb"],
["1F33E", "🌾", "Sheaf"],
["1F344", "🍄", "Mushroom"],
["1F374", "🍴", "Fork and knife"],
["1F372", "🍲", "Food"],
["1F35E", "🍞", "Bread"],
["1F357", "🍗", "Poultry leg"],
["1F347", "🍇", "Grapes"],
["1F34F", "🍏", "Apple"],
["1F352", "🍒", "Cherries"],
["1F36F", "🍯", "Honey pot"],
["1F37A", "🍺", "Beer"],
["1F37B", "🍻", "Beers"],
["1F377", "🍷", "Wine glass"],
["1F3BB", "🎻", "Violin"],
["1F3B8", "🎸", "Guitar"],
["1F52A", "🔪", "Knife"],
["1F52B", "🔫", "Pistol"],
["1F4A3", "💣", "Bomb"],
["1F4A5", "💥", "Collision"],
["1F4A8", "💨", "Dashing away"],
["1F301", "🌁", "Foggy"],
["2744", "❄️", "Snowflake"],
["26A1", "⚡", "Electricity"],
["1F320", "🌠", "Shooting star"],
["1F319", "🌙", "Crescent moon"],
["1F525", "🔥", "Fire"],
["1F4A7", "💧", "Droplet"],
["1F30A", "🌊", "Wave"],
["23F0", "⏰", "Alarm Clock"],
["231B", "⌛", "Hourglass"],
["1F3C6", "🏆", "Goblet"],
["26F2", "⛲", "Fountain"],
["26F5", "⛵", "Sailboat"],
["26FA", "⛺", "Campfire"],
["2764", "❤", "Red Heart"],
["1F498", "💘", "Heart With Arrow"],
["1F489", "💉", "Syringe"],
["1F4D5", "📕", "Closed Book"],
["1F4D6", "📚", "Books"],
["1F381", "🎁", "Wrapped Gift"],
["1F3AF", "🎯", "Archery"],
["1F52E", "🔮", "Magic ball"],
["1F3AD", "🎭", "Performing arts"],
["1F3A8", "🎨", "Artist palette"],
["1F457", "👗", "Dress"],
["1F392", "🎒", "Backpack"],
["1F451", "👑", "Crown"],
["1F48D", "💍", "Ring"],
["1F48E", "💎", "Gem"],
["1F514", "🔔", "Bell"],
["1F3B2", "🎲", "Die"],
// black and white icons in FF:
["26A0", "⚠", "Alert"],
["2317", "⌗", "Hash"],
["2318", "⌘", "POI"],
["2307", "⌇", "Wavy"],
["27F1", "⟱", "Downwards Quadruple"],
["21E6", "⇦", "Left arrow"],
["21E7", "⇧", "Top arrow"],
["21E8", "⇨", "Right arrow"],
["21E9", "⇩", "Left arrow"],
["21F6", "⇶", "Three arrows"],
["2699", "⚙", "Gear"],
["269B", "⚛", "Atom"],
["2680", "⚀", "Die1"],
["2681", "⚁", "Die2"],
["2682", "⚂", "Die3"],
["2683", "⚃", "Die4"],
["2684", "⚄", "Die5"],
["2685", "⚅", "Die6"],
["26B4", "⚴", "Pallas"],
["26B5", "⚵", "Juno"],
["26B6", "⚶", "Vesta"],
["26B7", "⚷", "Chiron"],
["26B8", "⚸", "Lilith"],
["263F", "☿", "Mercury"],
["2640", "♀", "Venus"],
["2641", "♁", "Earth"],
["2642", "♂", "Mars"],
["2643", "♃", "Jupiter"],
["2644", "♄", "Saturn"],
["2645", "♅", "Uranus"],
["2646", "♆", "Neptune"],
["2647", "♇", "Pluto"],
["26B3", "⚳", "Ceres"],
["2654", "♔", "Chess king"],
["2655", "♕", "Chess queen"],
["2656", "♖", "Chess rook"],
["2657", "♗", "Chess bishop"],
["2658", "♘", "Chess knight"],
["2659", "♙", "Chess pawn"],
["2660", "♠", "Spade"],
["2663", "♣", "Club"],
["2665", "♥", "Heart"],
["2666", "♦", "Diamond"],
["2698", "⚘", "Flower"],
["2625", "☥", "Ankh"],
["2626", "☦", "Orthodox"],
["2627", "☧", "Chi Rho"],
["2628", "☨", "Lorraine"],
["2629", "☩", "Jerusalem"],
["2670", "♰", "Syriac cross"],
["2020", "†", "Dagger"],
["262A", "☪", "Muslim"],
["262D", "☭", "Soviet"],
["262E", "☮", "Peace"],
["262F", "☯", "Yin yang"],
["26A4", "⚤", "Heterosexuality"],
["26A2", "⚢", "Female homosexuality"],
["26A3", "⚣", "Male homosexuality"],
["26A5", "⚥", "Male and female"],
["26AD", "⚭", "Rings"],
["2690", "⚐", "White flag"],
["2691", "⚑", "Black flag"],
["263C", "☼", "Sun"],
["263E", "☾", "Moon"],
["2668", "♨", "Hot springs"],
["2600", "☀", "Black sun"],
["2601", "☁", "Cloud"],
["2602", "☂", "Umbrella"],
["2603", "☃", "Snowman"],
["2604", "☄", "Comet"],
["2605", "★", "Black star"],
["2606", "☆", "White star"],
["269D", "⚝", "Outlined star"],
["2618", "☘", "Shamrock"],
["21AF", "↯", "Lightning"],
["269C", "⚜", "FleurDeLis"],
["2622", "☢", "Radiation"],
["2623", "☣", "Biohazard"],
["2620", "☠", "Skull"],
["2638", "☸", "Dharma"],
["2624", "☤", "Caduceus"],
["2695", "⚕", "Aeculapius staff"],
["269A", "⚚", "Hermes staff"],
["2697", "⚗", "Alembic"],
["266B", "♫", "Music"],
["2702", "✂", "Scissors"],
["2696", "⚖", "Scales"],
["2692", "⚒", "Hammer and pick"],
["2694", "⚔", "Swords"]
];
const table = document.getElementById("markerIconTable");
table.addEventListener("click", selectIcon, false);
table.addEventListener("mouseover", hoverIcon, false);
let row = "";
for (let i=0; i < icons.length; i++) {
if (i%16 === 0) row = table.insertRow(0);
const cell = row.insertCell(0);
const icon = String.fromCodePoint(parseInt(icons[i][0], 16));
cell.innerHTML = icon;
cell.id = "markerIcon" + icon.codePointAt();
cell.dataset.desc = icons[i][2];
}
}
function selectIcon(e) {
if (e.target !== e.currentTarget) {
const table = document.getElementById("markerIconTable");
const selected = table.getElementsByClassName("selected");
if (selected.length) selected[0].removeAttribute("class");
e.target.className = "selected";
function selectMarkerIcon() {
selectIcon(this.innerHTML, v => {
this.innerHTML = v;
const id = elSelected.attr("data-id");
const icon = e.target.innerHTML;
d3.select("#defs-markers").select(id).select("text").text(icon);
}
e.stopPropagation();
}
function hoverIcon(e) {
if (e.target !== e.currentTarget) tip(e.target.innerHTML + " " + e.target.dataset.desc);
e.stopPropagation();
d3.select("#defs-markers").select(id).select("text").text(v);
});
}
function changeIconSize() {

View file

@ -183,7 +183,7 @@ function overviewMilitary() {
position: {my: "center", at: "center", of: "svg"},
buttons: {
Apply: applyMilitaryOptions,
Add: () => addUnitLine({name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, type: "melee"}),
Add: () => addUnitLine({icon: "🛡️", name: "custom"+militaryOptionsTable.rows.length, rural: .2, urban: .5, crew: 1, power: 1, type: "melee"}),
Restore: restoreDefaultUnits,
Cancel: function() {$(this).dialog("close");}
}, open: function() {
@ -200,19 +200,20 @@ function overviewMilitary() {
}
function addUnitLine(u) {
const row = `<tr>
const row = document.createElement("tr");
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon||" "}</button></td>
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
<td><input data-tip="Enter average number of people in crew" type="number" min=1 step=1 value="${u.crew}"></td>
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types.map(t => `<option ${u.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ")}</select></td>
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? "checked" : ""}>
<label for="${u.name}Separate" class="checkbox-label"></label>
</td>
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>
</tr>`;
table.insertAdjacentHTML("beforeend", row);
<label for="${u.name}Separate" class="checkbox-label"></label></td>
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
row.querySelector("button").addEventListener("click", function(e) {selectIcon(this.innerHTML, v => this.innerHTML = v)});
table.appendChild(row);
}
function restoreDefaultUnits() {
@ -230,8 +231,14 @@ function overviewMilitary() {
$("#militaryOptions").dialog("close");
options.military = unitLines.map((r, i) => {
const [name, rural, urban, crew, type, separate] = Array.from(r.querySelectorAll("input, select")).map(d => d.type === "checkbox" ? d.checked : d.value);
return {name:names[i], rural:+rural||0, urban:+urban||0, crew:+crew||0, type, separate:+separate||0};
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll("input, select, button")).map(d => {
let value = d.value;
if (d.type === "number") value = +d.value || 0;
if (d.type === "checkbox") value = +d.checked || 0;
if (d.type === "button") value = d.innerHTML || "";
return value;
});
return {icon, name:names[i], rural, urban, crew, power, type, separate};
});
localStorage.setItem("military", JSON.stringify(options.military));
Military.generate();

View file

@ -14,8 +14,7 @@ function editRegiment(selector) {
$("#regimentEditor").dialog({
title: "Edit Regiment", resizable: false, close: closeEditor,
position: {my: "left top", at: "left+10 top+10", of: "#map"},
close: closeEditor
position: {my: "left top", at: "left+10 top+10", of: "#map"}
});
if (modules.editRegiment) return;
@ -27,6 +26,7 @@ function editRegiment(selector) {
document.getElementById("regimentName").addEventListener("change", changeName);
document.getElementById("regimentEmblem").addEventListener("input", changeEmblem);
document.getElementById("regimentEmblemSelect").addEventListener("click", selectEmblem);
document.getElementById("regimentAttack").addEventListener("click", toggleAttack);
document.getElementById("regimentRegenerateLegend").addEventListener("click", regenerateLegend);
document.getElementById("regimentLegend").addEventListener("click", editLegend);
document.getElementById("regimentSplit").addEventListener("click", splitRegiment);
@ -92,50 +92,15 @@ function editRegiment(selector) {
elSelected.dataset.name = reg.name = document.getElementById("regimentName").value = name;
}
function selectEmblem() {
selectIcon(regimentEmblem.value, v => {regimentEmblem.value = v; changeEmblem()});
}
function changeEmblem() {
const emblem = document.getElementById("regimentEmblem").value;
regiment().icon = elSelected.querySelector(".regimentIcon").innerHTML = emblem;
}
function selectEmblem() {
const emblems = ["⚔️","🏹","🐴","💣","🌊","🎯","⚓","🔮","📯","🛡️","👑",
"☠️","🎆","🗡️","⛏️","🔥","🐾","🎪","🏰","⚜️","⛓️","❤️","📜","🔱","🌈","🌠","💥","☀️","🍀",
"🔰","🕸️","⚗️","☣️","☢️","🎖️","⚕️","☸️","✡️","🚩","🏳️","🏴","🌈","💪","✊","👊","🤜","🤝","🙏","🧙","💂","🤴","🧛","🧟","🧞","🧝",
"🦄","🐲","🐉","🐎","🦓","🐺","🦊","🐱","🐈","🦁","🐯","🐅","🐆","🐕","🦌","🐵","🐒","🦍",
"🦅","🕊️","🐓","🦇","🐦","🦉","🐮","🐄","🐂","🐃","🐷","🐖","🐗","🐏","🐑","🐐","🐫","🦒","🐘","🦏",
"🐭","🐁","🐀","🐹","🐰","🐇","🦔","🐸","🐊","🐢","🦎","🐍","🐳","🐬","🦈","🐙","🦑","🐌","🦋","🐜","🐝","🐞","🦗","🕷️","🦂","🦀"];
alertMessage.innerHTML = "";
const container = document.createElement("div");
container.style.userSelect = "none";
container.style.cursor = "pointer";
container.style.fontSize = "2em";
container.style.width = "47vw";
container.innerHTML = emblems.map(i => `<span>${i}</span>`).join("");
container.addEventListener("mouseover", e => showTip(e), false);
container.addEventListener("click", e => clickEmblem(e), false);
alertMessage.appendChild(container);
$("#alert").dialog({
resizable: false, width: fitContent(), title: "Select emblem",
buttons: {
Emojipedia: function() {openURL("https://emojipedia.org/");},
Close: function() {$(this).dialog("close");}
}
});
function showTip(e) {
if (e.target.tagName !== "SPAN") return;
tip(`Click to select ${e.target.innerHTML} emblem`);
}
function clickEmblem(e) {
if (e.target.tagName !== "SPAN") return;
document.getElementById("regimentEmblem").value = e.target.innerHTML;
changeEmblem();
}
}
function changeUnit() {
const u = this.dataset.u;
const reg = regiment();
@ -200,6 +165,60 @@ function editRegiment(selector) {
toggleAdd();
}
function toggleAttack() {
document.getElementById("regimentAttack").classList.toggle("pressed");
if (document.getElementById("regimentAttack").classList.contains("pressed")) {
viewbox.style("cursor", "crosshair").on("click", attackRegimentOnClick);
tip("Click on another regiment to initiate battle", true);
armies.selectAll(":scope > g").classed("draggable", false);
} else {
clearMainTip();
armies.selectAll(":scope > g").classed("draggable", true);
viewbox.on("click", clicked).style("cursor", "default");
}
}
function attackRegimentOnClick() {
const target = d3.event.target, regSelected = target.parentElement, army = regSelected.parentElement;
const oldState = +elSelected.dataset.state, newState = +regSelected.dataset.state;
if (army.parentElement.id !== "armies") {tip("Please click on a regiment to attack", false, "error"); return;}
if (regSelected === elSelected) {tip("Regiment cannot attack itself", false, "error"); return;}
if (oldState === newState) {tip("Cannot attack fraternal regiment", false, "error"); return;}
const attacker = regiment();
const defender = pack.states[regSelected.dataset.state].military.find(r => r.i == regSelected.dataset.id);
if (!attacker.a || !defender.a) {tip("Regiment has no troops to battle", false, "error"); return;}
// move attacked to defender
const duration = Math.hypot(attacker.x - defender.x, attacker.y - defender.y) * 6;
const x = attacker.x = defender.x;
const y = attacker.y = defender.y + 8;
const size = +armies.attr("box-size");
const w = attacker.n ? size * 4 : size * 6;
const h = size * 2;
const x1 = x => rn(x - w / 2, 2);
const y1 = y => rn(y - size, 2);
const move = d3.transition().duration(duration).ease(d3.easeSinInOut);
const attack = d3.transition().delay(duration).duration(800).ease(d3.easeSinInOut).on("end", () => showBattleScreen(attacker, defender));
d3.select(elSelected.querySelector("rect")).transition(move).attr("x", x1(x)).attr("y", y1(y));
d3.select(elSelected.querySelector("text")).transition(move).attr("x", x).attr("y", y);
d3.select(elSelected.querySelectorAll("rect")[1]).transition(move).attr("x", x1(x)-h).attr("y", y1(y));
d3.select(elSelected.querySelector(".regimentIcon")).transition(move).attr("x", x1(x)-size).attr("y", y);
// battle icon
svg.append("text").attr("x", window.innerWidth/2).attr("y", window.innerHeight/2)
.text("⚔️").attr("font-size", 0).attr("opacity", 1)
.style("dominant-baseline", "central").style("text-anchor", "middle")
.transition(attack).attr("font-size", 1000).attr("opacity", 0).remove();
tip("", false);
$("#regimentEditor").dialog("close");
}
function toggleAttach() {
document.getElementById("regimentAttach").classList.toggle("pressed");
if (document.getElementById("regimentAttach").classList.contains("pressed")) {
@ -289,7 +308,7 @@ function editRegiment(selector) {
const x1 = x => rn(x - w / 2, 2);
const y1 = y => rn(y - size, 2);
const baseRect = this.querySelectorAll("rect")[0];
const baseRect = this.querySelector("rect");
const text = this.querySelector("text");
const iconRect = this.querySelectorAll("rect")[1];
const icon = this.querySelector(".regimentIcon");
@ -330,6 +349,7 @@ function editRegiment(selector) {
armies.selectAll("g>g").call(d3.drag().on("drag", null));
viewbox.selectAll("g#regimentBase").remove();
document.getElementById("regimentAdd").classList.remove("pressed");
document.getElementById("regimentAttack").classList.remove("pressed");
document.getElementById("regimentAttach").classList.remove("pressed");
restoreDefaultEvents();
elSelected = null;

File diff suppressed because one or more lines are too long

View file

@ -65,6 +65,7 @@ function processFeatureRegeneration(event, button) {
if (button === "regenerateProvinces") regenerateProvinces(); else
if (button === "regenerateReligions") regenerateReligions(); else
if (button === "regenerateMilitary") regenerateMilitary(); else
if (button === "regenerateIce") regenerateIce(); else
if (button === "regenerateMarkers") regenerateMarkers(event); else
if (button === "regenerateZones") regenerateZones(event);
}
@ -86,7 +87,7 @@ function recalculatePopulation() {
if (!b.i || b.removed) return;
const i = b.cell;
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i]) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
b.population = rn(Math.max((pack.cells.s[i] + pack.cells.road[i] / 2) / 8 + b.i / 1000 + i % 100 / 1000, .1), 3);
if (b.capital) b.population = b.population * 1.3; // increase capital population
if (b.port) b.population = b.population * 1.3; // increase port population
b.population = rn(b.population * gauss(2,3,.6,20,3), 3);
@ -249,6 +250,12 @@ function regenerateMilitary() {
if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click();
}
function regenerateIce() {
if (!layerIsOn("toggleIce")) toggleIce();
ice.selectAll("*").remove();
drawIce();
}
function regenerateMarkers(event) {
if (isCtrlClick(event)) prompt("Please provide markers number multiplier", {default:1, step:.01, min:0, max:100}, v => addNumberOfMarkers(v));
else addNumberOfMarkers(gauss(1, .5, .3, 5, 2));