From a2b93f1396b79a9bfc0b173e49962e5db075d436 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 26 Apr 2020 20:30:07 +0300 Subject: [PATCH] v1.4.03 --- index.css | 24 ++++----- index.html | 35 ++++++------- libs/lineclip.js | 11 +++-- libs/three.min.js | 2 +- main.js | 2 + modules/burgs-and-states.js | 2 +- modules/names-generator.js | 87 +++++++++++++++++++-------------- modules/ocean-layers.js | 15 ++++-- modules/save-and-load.js | 5 +- modules/ui/elevation-profile.js | 18 ++----- modules/ui/general.js | 12 +++-- modules/ui/layers.js | 2 +- modules/ui/namesbase-editor.js | 14 +----- modules/ui/options.js | 17 ++----- modules/ui/states-editor.js | 8 ++- modules/utils.js | 6 +-- 16 files changed, 130 insertions(+), 130 deletions(-) diff --git a/index.css b/index.css index 1d538f85..a2b315c7 100644 --- a/index.css +++ b/index.css @@ -326,13 +326,13 @@ div.tab > button#optionsHide { .tab { overflow: hidden; border-bottom: 1px solid #5d4651; - height: 2.3em; + height: 2.2em; } #options p { font-style: italic; font-weight: bold; - margin-bottom: 0; + margin: .8em 0 0 0; } #aboutContent { @@ -346,6 +346,7 @@ div.tab > button#optionsHide { #aboutContent a { color: #1d1b1c; font-weight: bold; + text-decoration: underline; } #optionsContent span { @@ -572,10 +573,9 @@ input[type="color"]::-webkit-color-swatch-wrapper { } .tab > button.options { - /* width: 23.25%; */ width: 18.6%; height: 100%; - padding: 7px 0px; + padding: 6px 0px; } button.options { @@ -943,18 +943,18 @@ body button.noicon { } .drag-trigger { - border-left: 12px solid transparent; - border-right: 12px solid #916e7f; - border-top: 12px solid transparent; + border-left: 1em solid transparent; + border-right: 1em solid #000; + border-top: 1em solid transparent; position: absolute; - right: 0; - top: 100%; - margin-top: -12px; + right: -1px; + bottom: -1px; + opacity: .3; } .drag-trigger:hover { cursor: move; - border-right-color: #5e4fa2; + opacity: .6; } .tint { @@ -1684,7 +1684,7 @@ ul.share-buttons li { } ul.share-buttons img { - width: 18px; + width: 2em; } input[type="checkbox"] { diff --git a/index.html b/index.html index c0618b86..a1eb751e 100644 --- a/index.html +++ b/index.html @@ -1912,22 +1912,21 @@

Fantasy Map Generator is a free open source tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the quick start tutorial, Q&A and hotkeys for guidance.

-

Join our Discord server and Reddit community to ask questions, get help and share created maps. You may support the project on Patreon.

-

The project is under active development. Creator and main maintainer: Azgaar. For older versions see the changelog. To track the development progress see the devboard. Please report bugs here. You can also contact me directly via email.

-

Special thanks to all supporters on Patreon!

-

Patreon Supporters: Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, - Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, - Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, - Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, - Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), - Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI, - Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren, - Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie, - Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220, - Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany, Huw Williams, Joseph Hamilton, - FlippantFeline, Tamashi Toh, kms, Stephen Herron, MidnightMoon, Whakomatic x, Barished, Aaron bateson, Brice Moss, Diklyquill, - PatronUser, Michael Greiner, Steven Bennett, Jacob Harrington, Miguel C., Reya C., Giant Monster Games, Noirbard, Brian Drennen, - Ben Craigie, Alex Smolin and many others!

+

Join our Discord server and Reddit community to ask questions, get help and share maps.

+

The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the devboard. For older versions see the changelog. Please report bugs here. You can also contact me directly via email.

+
+ +
+
+ + + + +
SUPPORT ON PATREON +
+
+
+

Special thanks to all supporters on Patreon!

Generated examples: diff --git a/libs/lineclip.js b/libs/lineclip.js index 31a55d28..b43da33b 100644 --- a/libs/lineclip.js +++ b/libs/lineclip.js @@ -48,13 +48,13 @@ function lineclip(points, bbox, result) { } // Sutherland-Hodgeman polygon clipping algorithm -function polygonclip(points, bbox, secure = false) { +function polygonclip(points, bbox, secure = 0) { var result, edge, prev, prevInside, inter, i, p, inside; // clip against each side of the clip rectangle for (edge = 1; edge <= 8; edge *= 2) { result = []; - prev = points[points.length - 1]; + prev = points[points.length-1]; prevInside = !(bitCode(prev, bbox) & edge); for (i = 0; i < points.length; i++) { @@ -62,10 +62,10 @@ function polygonclip(points, bbox, secure = false) { inside = !(bitCode(p, bbox) & edge); inter = inside !== prevInside; // segment goes through the clip window - if (secure && inter && inside && i) result.push(points[i-1]); // add previous point in secure mode (to get a correct d3 curve) - if (inter) result.push(intersect(prev, p, edge, bbox)); // add an intersection point + const pi = intersect(prev, p, edge, bbox); + if (inter) result.push(pi); // add an intersection point + if (secure && inter) result.push(pi, pi); // add additional intersection points to secure correct d3 curve if (inside) result.push(p); // add a point if it's inside - else if (secure && prevInside) result.push(p); // // add an outside point if in secure mode (to get a correct d3 curve) prev = p; prevInside = inside; @@ -73,6 +73,7 @@ function polygonclip(points, bbox, secure = false) { points = result; if (!points.length) break; } + //result.forEach(p => debug.append("circle").attr("cx", p[0]).attr("cy", p[1]).attr("r", .6).attr("fill", "red")); return result; } diff --git a/libs/three.min.js b/libs/three.min.js index 6dc8ad26..0652e62b 100644 --- a/libs/three.min.js +++ b/libs/three.min.js @@ -88,7 +88,7 @@ var t=a.getRenderTarget();return{isWebGL2:c.isWebGL2,shaderID:h,precision:m,inst emissiveMap:!!b.emissiveMap,emissiveMapEncoding:d(b.emissiveMap,a.gammaInput),bumpMap:!!b.bumpMap,normalMap:!!b.normalMap,objectSpaceNormalMap:1===b.normalMapType,tangentSpaceNormalMap:0===b.normalMapType,clearcoatNormalMap:!!b.clearcoatNormalMap,displacementMap:!!b.displacementMap,roughnessMap:!!b.roughnessMap,metalnessMap:!!b.metalnessMap,specularMap:!!b.specularMap,alphaMap:!!b.alphaMap,gradientMap:!!b.gradientMap,sheen:!!b.sheen,combine:b.combine,vertexTangents:b.normalMap&&b.vertexTangents,vertexColors:b.vertexColors, vertexUvs:!!b.map||!!b.bumpMap||!!b.normalMap||!!b.specularMap||!!b.alphaMap||!!b.emissiveMap||!!b.roughnessMap||!!b.metalnessMap||!!b.clearcoatNormalMap,fog:!!q,useFog:b.fog,fogExp2:q&&q.isFogExp2,flatShading:b.flatShading,sizeAttenuation:b.sizeAttenuation,logarithmicDepthBuffer:c.logarithmicDepthBuffer,skinning:b.skinning&&0= 20; if (!startFromEdge && cells.t[i] !== -1 && cells.t[i] !== 1) continue; // non-edge cell diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index 8b77bdbc..95637a24 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -691,7 +691,7 @@ const naval = states[f].type === "Naval" && states[t].type === "Naval" && cells.f[states[f].center] !== cells.f[states[t].center]; const neib = naval ? false : states[f].neighbors.includes(t); - const neibOfNeib = naval || neib ? false : states[f].neighbors.map(n => states[n].neighbors).join().includes(t); + const neibOfNeib = naval || neib ? false : states[f].neighbors.map(n => states[n].neighbors).join("").includes(t); let status = naval ? rw(navals) : neib ? rw(neibs) : neibOfNeib ? rw(neibsOfNeibs) : rw(far); diff --git a/modules/names-generator.js b/modules/names-generator.js index 8a950260..98776011 100644 --- a/modules/names-generator.js +++ b/modules/names-generator.js @@ -8,25 +8,41 @@ // calculate Markov chain for a namesbase const calculateChain = function(string) { - const chain = []; - const d = string.toLowerCase().replace(/,/g, " "); + const chain = [], array = string.split(","); - for (let i = -1, str = ""; i < d.length - 2; i += str.length, str = "") { - let v = 0, f = " "; + for (const n of array) { + let name = n.trim().toLowerCase(); + const basic = !(/[^\u0000-\u007f]/.test(name)); // basic chars and English rules can be applied - for (let c=i+1; str.length < 5; c++) { - if (d[c] === undefined) break; - str += d[c]; - if (str === " ") break; - if (d[c] !== "o" && d[c] !== "e" && vowel(d[c]) && d[c+1] === d[c]) break; - if (d[c+2] === " ") {str += d[c+1]; break;} - if (vowel(d[c])) v++; - if (v && vowel(d[c+2])) break; + // split word into pseudo-syllables + for (let i=-1, syllable = ""; i < name.length; i += (syllable.length||1), syllable = "") { + let prev = name[i] || ""; // pre-onset letter + let v = 0; // 0 if no vowels in syllable + + for (let c=i+1; name[c] && syllable.length < 5; c++) { + const that = name[c], next = name[c+1]; // next char + syllable += that; + if (syllable === " " || syllable === "-") break; // syllable starts with space or hyphen + if (!next || next === " " || next === "-") break; // no need to check + + if (vowel(that)) v = 1; // check if letter is vowel + + // do not split some diphthongs + if (that === "y" && next === "e") continue; // 'ye' + if (basic) { // English-like + if (that === "o" && next === "o") continue; // 'oo' + if (that === "e" && next === "e") continue; // 'ee' + if (that === "a" && next === "e") continue; // 'ae' + if (that === "c" && next === "h") continue; // 'ch' + } + + if (vowel(that) === next) break; // two same vowels in a row + if (v && vowel(name[c+2])) break; // syllable has vowel and additional vowel is expected soon + } + + if (chain[prev] === undefined) chain[prev] = []; + chain[prev].push(syllable); } - - if (i >= 0) f = d[i]; - if (chain[f] === undefined) chain[f] = []; - chain[f].push(str); } return chain; @@ -39,12 +55,12 @@ const clearChains = () => chains = []; // generate name using Markov's chain - const getBase = function(base, min, max, dupl, multi) { + const getBase = function(base, min, max, dupl) { if (base === undefined) {console.error("Please define a base"); return;} if (!chains[base]) updateChain(base); const data = chains[base]; - if (!data || data[" "] === undefined) { + if (!data || data[""] === undefined) { tip("Namesbase " + base + " is incorrect. Please check in namesbase editor", false, "error"); console.error("Namebase " + base + " is incorrect!"); return "ERROR"; @@ -53,31 +69,26 @@ if (!min) min = nameBases[base].min; if (!max) max = nameBases[base].max; if (dupl !== "") dupl = nameBases[base].d; - if (!multi) multi = nameBases[base].m; - let v = data[" "], cur = v[rand(v.length-1)], w = ""; - for (let i=0; i < 21; i++) { - if (cur === " " && Math.random() > multi) { - if (w.length < min) {cur = ""; w = ""; v = data[" "];} else break; + let v = data[""], cur = ra(v), w = ""; + for (let i=0; i < 20; i++) { + if (cur === "") { // end of word + if (w.length < min) {cur = ""; w = ""; v = data[""];} else break; } else { - if ((w+cur).length > max) { + if (w.length + cur.length > max) { // word too long if (w.length < min) w += cur; break; - } else if (cur === " " && w.length+1 < min) { - cur = ""; - v = data[" "]; - } else { - v = data[cur.slice(-1)] || data[" "]; - } + } else v = data[last(cur)] || data[""]; } w += cur; - cur = v[rand(v.length - 1)]; + cur = ra(v); } // parse word to get a final name const l = last(w); // last letter - if (l === "'" || l === " ") w = w.slice(0,-1); // not allow apostrophe and space at the end + if (l === "'" || l === " " || l === "-") w = w.slice(0,-1); // not allow some characters at the end + const basic = !(/[^\u0000-\u007f]/.test(w)); // true if word has only basic characters let name = [...w].reduce(function(r, c, i, d) { if (c === d[i+1] && !dupl.includes(c)) return r; // duplication is not allowed @@ -86,8 +97,8 @@ if (r.slice(-1) === " ") return r + c.toUpperCase(); // capitalize letter after space if (r.slice(-1) === "-") return r + c.toUpperCase(); // capitalize letter after hyphen if (c === "a" && d[i+1] === "e") return r; // "ae" => "e" - if (i+1 < d.length && !vowel(c) && !vowel(d[i-1]) && !vowel(d[i+1])) return r; // remove consonant between 2 consonants - if (i+2 < d.length && c === d[i+1] && c === d[i+2]) return r; // remove tree same letters in a row + if (basic && i+1 < d.length && !vowel(c) && !vowel(d[i-1]) && !vowel(d[i+1])) return r; // remove consonant between 2 consonants + if (i+2 < d.length && c === d[i+1] && c === d[i+2]) return r; // remove three same letters in a row return r + c; }, ""); @@ -95,7 +106,7 @@ if (name.split(" ").some(part => part.length < 2)) name = name.split(" ").map((p,i) => i ? p.toLowerCase() : p).join(""); if (name.length < 2) { - console.error("Name is too short! Random name to be selected"); + console.error("Name is too short! Random name will be selected"); name = ra(nameBases[base].b.split(",")); } @@ -103,10 +114,10 @@ } // generate name for culture - const getCulture = function(culture, min, max, dupl, multi) { + const getCulture = function(culture, min, max, dupl) { if (culture === undefined) {console.error("Please define a culture"); return;} const base = pack.cultures[culture].base; - return getBase(base, min, max, dupl, multi); + return getBase(base, min, max, dupl); } // generate short name for culture @@ -206,7 +217,7 @@ } const getNameBases = function() { - // name, min length, max length, letters to allow duplication, multi-word name rate + // name, min length, max length, letters to allow duplication, multi-word name rate [deprecated] return [ // real-world bases by Azgaar: {name: "German", i: 0, min: 5, max: 12, d: "lt", m: 0, b: "Achern,Aichhalden,Aitern,Albbruck,Alpirsbach,Altensteig,Althengstett,Appenweier,Auggen,Wildbad,Badenen,Badenweiler,Baiersbronn,Ballrechten,Bellingen,Berghaupten,Bernau,Biberach,Biederbach,Binzen,Birkendorf,Birkenfeld,Bischweier,Blumberg,Bollen,Bollschweil,Bonndorf,Bosingen,Braunlingen,Breisach,Breisgau,Breitnau,Brigachtal,Buchenbach,Buggingen,Buhl,Buhlertal,Calw,Dachsberg,Dobel,Donaueschingen,Dornhan,Dornstetten,Dottingen,Dunningen,Durbach,Durrheim,Ebhausen,Ebringen,Efringen,Egenhausen,Ehrenkirchen,Ehrsberg,Eimeldingen,Eisenbach,Elzach,Elztal,Emmendingen,Endingen,Engelsbrand,Enz,Enzklosterle,Eschbronn,Ettenheim,Ettlingen,Feldberg,Fischerbach,Fischingen,Fluorn,Forbach,Freiamt,Freiburg,Freudenstadt,Friedenweiler,Friesenheim,Frohnd,Furtwangen,Gaggenau,Geisingen,Gengenbach,Gernsbach,Glatt,Glatten,Glottertal,Gorwihl,Gottenheim,Grafenhausen,Grenzach,Griesbach,Gutach,Gutenbach,Hag,Haiterbach,Hardt,Harmersbach,Hasel,Haslach,Hausach,Hausen,Hausern,Heitersheim,Herbolzheim,Herrenalb,Herrischried,Hinterzarten,Hochenschwand,Hofen,Hofstetten,Hohberg,Horb,Horben,Hornberg,Hufingen,Ibach,Ihringen,Inzlingen,Kandern,Kappel,Kappelrodeck,Karlsbad,Karlsruhe,Kehl,Keltern,Kippenheim,Kirchzarten,Konigsfeld,Krozingen,Kuppenheim,Kussaberg,Lahr,Lauchringen,Lauf,Laufenburg,Lautenbach,Lauterbach,Lenzkirch,Liebenzell,Loffenau,Loffingen,Lorrach,Lossburg,Mahlberg,Malsburg,Malsch,March,Marxzell,Marzell,Maulburg,Monchweiler,Muhlenbach,Mullheim,Munstertal,Murg,Nagold,Neubulach,Neuenburg,Neuhausen,Neuried,Neuweiler,Niedereschach,Nordrach,Oberharmersbach,Oberkirch,Oberndorf,Oberbach,Oberried,Oberwolfach,Offenburg,Ohlsbach,Oppenau,Ortenberg,otigheim,Ottenhofen,Ottersweier,Peterstal,Pfaffenweiler,Pfalzgrafenweiler,Pforzheim,Rastatt,Renchen,Rheinau,Rheinfelden,Rheinmunster,Rickenbach,Rippoldsau,Rohrdorf,Rottweil,Rummingen,Rust,Sackingen,Sasbach,Sasbachwalden,Schallbach,Schallstadt,Schapbach,Schenkenzell,Schiltach,Schliengen,Schluchsee,Schomberg,Schonach,Schonau,Schonenberg,Schonwald,Schopfheim,Schopfloch,Schramberg,Schuttertal,Schwenningen,Schworstadt,Seebach,Seelbach,Seewald,Sexau,Simmersfeld,Simonswald,Sinzheim,Solden,Staufen,Stegen,Steinach,Steinen,Steinmauern,Straubenhardt,Stuhlingen,Sulz,Sulzburg,Teinach,Tiefenbronn,Tiengen,Titisee,Todtmoos,Todtnau,Todtnauberg,Triberg,Tunau,Tuningen,uhlingen,Unterkirnach,Reichenbach,Utzenfeld,Villingen,Villingendorf,Vogtsburg,Vohrenbach,Waldachtal,Waldbronn,Waldkirch,Waldshut,Wehr,Weil,Weilheim,Weisenbach,Wembach,Wieden,Wiesental,Wildberg,Winzeln,Wittlingen,Wittnau,Wolfach,Wutach,Wutoschingen,Wyhlen,Zavelstein"}, diff --git a/modules/ocean-layers.js b/modules/ocean-layers.js index 94a18fd2..7751b376 100644 --- a/modules/ocean-layers.js +++ b/modules/ocean-layers.js @@ -28,16 +28,21 @@ if (!start) continue; used[i] = 1; const chain = connectVertices(start, t); // vertices chain to form a path - const relaxation = 1 + t * -2; // select only n-th point - const relaxed = chain.filter((v, i) => i % relaxation === 0 || vertices.c[v].some(c => c >= pointsN)); + if (chain.length < 4) continue; + const relax = 1 + t * -2; // select only n-th point + const relaxed = chain.filter((v, i) => !(i%relax) || vertices.c[v].some(c => c >= pointsN)); + if (relaxed.length < 4) continue; const points = clipPoly(relaxed.map(v => vertices.p[v]), 1); - if (relaxed.length >= 3) chains.push([t, points]); + const inside = d3.polygonContains(points, grid.points[i]); + chains.push([t, points, inside]); } + const bbox = `M0,0h${graphWidth}v${graphHeight}h${-graphWidth}Z`; for (const t of limits) { - const path = chains.filter(c => c[0] === t).map(c => round(lineGen(c[1]))).join(); + const layer = chains.filter(c => c[0] === t); + let path = layer.map(c => round(lineGen(c[1]))).join(""); + if (layer.every(c => !c[2])) path = bbox + path; // add outer ring if all segments are outside (works not for all cases) if (path) oceanLayers.append("path").attr("d", path).attr("fill", "#ecf2f9").style("opacity", opacity); - // For each layer there should outer ring. If no, layer will be upside down. Need to fix it in the future } // find eligible cell vertex to start path detection diff --git a/modules/save-and-load.js b/modules/save-and-load.js index a725b5a9..68f2450f 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -326,6 +326,7 @@ async function loadFromCloud() { function saveGeoJSON_Cells() { let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n"; const cells = pack.cells, v = pack.vertices; + const getPopulation = i => {const [r, u] = getCellPopulation(i); return rn(r+u)}; cells.i.forEach(i => { data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [["; @@ -340,13 +341,13 @@ function saveGeoJSON_Cells() { data += "["+x+","+y+"]"; data += "]] },\n \"properties\": {\n"; - let height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]])); + const height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]])); data += " \"id\": \""+i+"\",\n"; data += " \"height\": \""+height+"\",\n"; data += " \"biome\": \""+cells.biome[i]+"\",\n"; data += " \"type\": \""+pack.features[cells.f[i]].type+"\",\n"; - data += " \"population\": \""+getFriendlyPopulation(i)+"\",\n"; + data += " \"population\": \""+getPopulation(i)+"\",\n"; data += " \"state\": \""+cells.state[i]+"\",\n"; data += " \"province\": \""+cells.province[i]+"\",\n"; data += " \"culture\": \""+cells.culture[i]+"\",\n"; diff --git a/modules/ui/elevation-profile.js b/modules/ui/elevation-profile.js index de19378f..7f18a688 100644 --- a/modules/ui/elevation-profile.js +++ b/modules/ui/elevation-profile.js @@ -2,45 +2,35 @@ function showEPForRoute(node) { const points = []; - var prevB=0, i=0, j=0, b=0, ma=0, mi=100, h=0; debug.select("#controlPoints").selectAll("circle").each(function() { - i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); + const i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); points.push(i); }); const routeLen = node.getTotalLength() * distanceScaleInput.value; - showElevationProfile(points, routeLen, false); } function showEPForRiver(node) { const points = []; - var prevB=0, i=0, j=0, b=0, ma=0, mi=100, h=0; debug.select("#controlPoints").selectAll("circle").each(function() { - i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); + const i = findCell(this.getAttribute("cx"), this.getAttribute("cy")); points.push(i); }); const riverLen = (node.getTotalLength() / 2) * distanceScaleInput.value; - showElevationProfile(points, riverLen, true); } function resizeElevationProfile() { } -function closeElevationProfile() { - modules.elevation = false; -} - function showElevationProfile(data, routeLen, isRiver) { -// data is an array of cell indexes, routeLen is the distance, isRiver should be true for rivers, false otherwise + // data is an array of cell indexes, routeLen is the distance, isRiver should be true for rivers, false otherwise document.getElementById("elevationGraph").innerHTML = ""; $("#elevationProfile").dialog({ - title: "Elevation profile", resizable: false, - width: window.width, - close: closeElevationProfile, + title: "Elevation profile", resizable: false, width: window.width, position: {my: "left top", at: "left+20 bottom-240", of: window, collision: "fit"} }); diff --git a/modules/ui/general.js b/modules/ui/general.js index 449d7798..9833618b 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -223,16 +223,20 @@ function getRiverInfo(id) { return r ? `${r.name} ${r.type} (${id})` : "n/a"; } +function getCellPopulation(i) { + const rural = pack.cells.pop[i] * populationRate.value; + const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate.value * urbanization.value : 0; + return [rural, urban]; +} + // get user-friendly (real-world) population value from map data function getFriendlyPopulation(i) { - const rural = pack.cells.pop[i] * populationRate.value; - const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate.value * urbanization.value : 0; + const [rural, urban] = getCellPopulation(i); return `${si(rural+urban)} (${si(rural)} rural, urban ${si(urban)})`; } function getPopulationTip(i) { - const rural = pack.cells.pop[i] * populationRate.value; - const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate.value * urbanization.value : 0; + const [rural, urban] = getCellPopulation(i); return `Cell population: ${si(rural+urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`; } diff --git a/modules/ui/layers.js b/modules/ui/layers.js index 9be802b0..deb78f83 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -260,7 +260,7 @@ function drawTemp() { temperature.append("path").attr("d", `M0,0 h${svgWidth} v${svgHeight} h${-svgWidth} Z`).attr("fill", scheme(1 - (min - tMin) / delta)).attr("stroke", "none"); for (const t of isolines) { - const path = chains.filter(c => c[0] === t).map(c => round(lineGen(c[1]))).join(); + const path = chains.filter(c => c[0] === t).map(c => round(lineGen(c[1]))).join(""); if (!path) continue; const fill = scheme(1 - (t - tMin) / delta), stroke = d3.color(fill).darker(.2); temperature.append("path").attr("d", path).attr("fill", fill).attr("stroke", stroke); diff --git a/modules/ui/namesbase-editor.js b/modules/ui/namesbase-editor.js index 26a72749..baf0acb5 100644 --- a/modules/ui/namesbase-editor.js +++ b/modules/ui/namesbase-editor.js @@ -16,7 +16,6 @@ function editNamesbase() { document.getElementById("namesbaseMin").addEventListener("input", updateBaseMin); document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax); document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication); - document.getElementById("namesbaseMulti").addEventListener("input", updateBaseMiltiwordRate); document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd); document.getElementById("namesbaseAnalize").addEventListener("click", analizeNamesbase); document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault); @@ -46,7 +45,6 @@ function editNamesbase() { document.getElementById("namesbaseMin").value = nameBases[base].min; document.getElementById("namesbaseMax").value = nameBases[base].max; document.getElementById("namesbaseDouble").value = nameBases[base].d; - document.getElementById("namesbaseMulti").value = nameBases[base].m; updateExamples(); } @@ -67,10 +65,9 @@ function editNamesbase() { function updateNamesData() { const base = +document.getElementById("namesbaseSelect").value; - const b = document.getElementById("namesbaseTextarea").value.replace(/ /g, ""); + const b = document.getElementById("namesbaseTextarea").value; if (b.split(",").length < 3) { - tip("The names data provided is not correct", false, "error"); - document.getElementById("namesbaseTextarea").value = nameBases[base].b; + tip("The names data provided is too short of incorrect", false, "error"); return; } nameBases[base].b = b; @@ -101,12 +98,6 @@ function editNamesbase() { nameBases[base].d = this.value; } - function updateBaseMiltiwordRate() { - if (isNaN(+this.value) || +this.value < 0 || +this.value > 1) {tip("Please provide a number within [0-1] range", false, "error"); return;} - const base = +document.getElementById("namesbaseSelect").value; - nameBases[base].m = +this.value; - } - function analizeNamesbase() { const string = document.getElementById("namesbaseTextarea").value; if (!string) {tip("Names data field should not be empty", false, "error"); return;} @@ -174,7 +165,6 @@ function editNamesbase() { document.getElementById("namesbaseMin").value = 5; document.getElementById("namesbaseMax").value = 12; document.getElementById("namesbaseDouble").value = ""; - document.getElementById("namesbaseMulti").value = 0; document.getElementById("namesbaseExamples").innerHTML = "Please provide names data"; } diff --git a/modules/ui/options.js b/modules/ui/options.js index 11fdb653..6af46b0a 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -69,18 +69,11 @@ document.getElementById("options").querySelector("div.tab").addEventListener("cl if (id === "aboutTab") aboutContent.style.display = "block"; }); -document.getElementById("options").querySelectorAll("i.collapsible").forEach(el => el.addEventListener("click", collapse)); -function collapse(e) { - const trigger = e.target; - const section = trigger.parentElement.nextElementSibling; - - if (section.style.display === "none") { - section.style.display = "block"; - trigger.classList.replace("icon-down-open", "icon-up-open"); - } else { - section.style.display = "none"; - trigger.classList.replace("icon-up-open", "icon-down-open"); - } +// show popup with a list of Patreon supportes (updated manually, to be replaced with API call) +function showSupporters() { + const supporters = "Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI, Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren, Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie, Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220, Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany, Huw Williams, Joseph Hamilton, FlippantFeline, Tamashi Toh, kms, Stephen Herron, MidnightMoon, Whakomatic x, Barished, Aaron bateson, Brice Moss, Diklyquill, PatronUser, Michael Greiner, Steven Bennett, Jacob Harrington, Miguel C., Reya C., Giant Monster Games, Noirbard, Brian Drennen, Ben Craigie, Alex Smolin, Endwords"; + alertMessage.innerHTML = "
    " + supporters.split(", ").sort().map(n => `
  • ${n}
  • `).join("") + "
"; + $("#alert").dialog({resizable: false, title: "Patreon Supporters", width: "30vw", position: {my: "center", at: "center", of: "svg"}}); } // Option listeners diff --git a/modules/ui/states-editor.js b/modules/ui/states-editor.js index 78a7fb14..f344d821 100644 --- a/modules/ui/states-editor.js +++ b/modules/ui/states-editor.js @@ -184,7 +184,7 @@ function editStates() { path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)}); } - function stateHighlightOff(event) { + function stateHighlightOff() { debug.selectAll(".highlight").each(function() { d3.select(this).transition().duration(1000).attr("opacity", 0).remove(); }); @@ -201,6 +201,12 @@ function editStates() { statesBody.select("#state-gap"+state).attr("stroke", fill); const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666"; statesHalo.select("#state-border"+state).attr("stroke", halo); + + // recolor regiments + const solidColor = fill[0] === "#" ? fill : "#999"; + const darkerColor = d3.color(solidColor).darker().hex(); + armies.select("#army"+state).attr("fill", solidColor); + armies.select("#army"+state).selectAll("g > rect:nth-of-type(2)").attr("fill", darkerColor); } openPicker(currentFill, callback); diff --git a/modules/utils.js b/modules/utils.js index afcdd5ea..d979e456 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -376,13 +376,13 @@ function common(a, b) { } // clip polygon by graph bbox -function clipPoly(points, secure = false) { +function clipPoly(points, secure = 0) { return polygonclip(points, [0, 0, graphWidth, graphHeight], secure); } -// check if char is vowel +// check if char is vowel or can serve as vowel function vowel(c) { - return "aeiouy".includes(c); + return `aeiouyɑ'əøɛœæɶɒɨɪɔɐʊɤɯаоиеёэыуюяàèìòùỳẁȁȅȉȍȕáéíóúýẃőűâêîôûŷŵäëïöüÿẅãẽĩõũỹąęįǫųāēīōūȳăĕĭŏŭǎěǐǒǔȧėȯẏẇạẹịọụỵẉḛḭṵṳ`.includes(c); } // remove vowels from the end of the string