mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
164 lines
7.5 KiB
JavaScript
164 lines
7.5 KiB
JavaScript
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global.Names = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
const chains = [];
|
|
|
|
// calculate Markov chain for a namesbase
|
|
const calculateChain = function(b) {
|
|
const chain = [];
|
|
const d = nameBase[b].join(" ").toLowerCase();
|
|
|
|
for (let i = -1, prev = " ", str = ""; i < d.length - 2; prev = str, i += str.length, str = "") {
|
|
let v = 0, f = " ";
|
|
|
|
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;
|
|
}
|
|
|
|
if (i >= 0) f = d[i];
|
|
if (chain[f] === undefined) chain[f] = [];
|
|
chain[f].push(str);
|
|
}
|
|
|
|
return chain;
|
|
}
|
|
|
|
// update chain for specific base
|
|
const updateChain = (b) => chains[b] = nameBase[b] ? calculateChain(b) : null;
|
|
|
|
// update chains for all used bases
|
|
const updateChains = () => chains.forEach((c, i) => chains[i] = nameBase[i] ? calculateChain(i) : null);
|
|
|
|
// generate name using Markov's chain
|
|
const getBase = function(base, min, max, dupl, multi) {
|
|
if (base === undefined) {console.error("Please define a base"); return;}
|
|
if (!chains[base]) chains[base] = nameBase[base] ? calculateChain(base) : null;
|
|
|
|
const data = chains[base];
|
|
if (!data || data[" "] === undefined) {
|
|
tip("Namesbase " + base + " is incorrect. Please check in namesbase editor", false, "error");
|
|
console.error("nameBase " + base + " is incorrect!");
|
|
return "ERROR";
|
|
}
|
|
|
|
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;
|
|
} else {
|
|
if ((w+cur).length > max) {
|
|
if (w.length < min) w += cur;
|
|
break;
|
|
} else if (cur === " " && w.length+1 < min) {
|
|
cur = "";
|
|
v = data[" "];
|
|
} else {
|
|
v = data[cur.slice(-1)];
|
|
}
|
|
}
|
|
|
|
w += cur;
|
|
cur = v[rand(v.length - 1)];
|
|
}
|
|
|
|
// parse word to get a final name
|
|
let name = [...w].reduce(function(r, c, i, d) {
|
|
if (c === d[i+1] && !dupl.includes(c)) return r; // duplication is not allowed
|
|
if (!r.length) return c.toUpperCase();
|
|
if (r.slice(-1) === " ") return r + c.toUpperCase();
|
|
if (c === "a" && d[i+1] === "e") return r; // "ae" => "e"
|
|
if (c === " " && i+1 === d.length) return r;
|
|
if (i+2 < d.length && !vowel(c) && !vowel(d[i+1]) && !vowel(d[i+2])) return r; // remove consonant before 2 consonants
|
|
if (i+2 < d.length && c === d[i+1] && c === d[i+2]) return r; // remove tree same letters in a row
|
|
return r + c;
|
|
}, "");
|
|
|
|
// join the word if any part has only 1 letter
|
|
if (name.split(" ").some(part => part.length < 2)) name = name.split(" ").map((p,i) => i ? p.toLowerCase() : p).join("");
|
|
|
|
if (name.length < 2) name = nameBase[base][rand(nameBase[base].length-1)]; // rare case when no name generated
|
|
return name;
|
|
}
|
|
|
|
// generate name for culture
|
|
const getCulture = function(culture, min, max, dupl, multi) {
|
|
if (culture === undefined) {console.error("Please define a culture"); return;}
|
|
const base = pack.cultures[culture].base;
|
|
return getBase(base, min, max, dupl, multi);
|
|
}
|
|
|
|
// generate short name for culture
|
|
const getCultureShort = function(culture) {
|
|
if (culture === undefined) {console.error("Please define a culture"); return;}
|
|
const base = pack.cultures[culture].base;
|
|
const min = nameBases[base].min-1;
|
|
const max = Math.max(nameBases[base].max-2, min);
|
|
return getBase(base, min, max, "", 0);
|
|
}
|
|
|
|
// generate state name based on capital or random name and culture-specific suffix
|
|
const getState = function(name, culture) {
|
|
if (name === undefined) {console.error("Please define a base name"); return;}
|
|
if (culture === undefined) {console.error("Please define a culture"); return;}
|
|
const base = pack.cultures[culture].base;
|
|
|
|
// exclude endings inappropriate for states name
|
|
if (name.includes(" ")) name = capitalize(name.replace(/ /g, "").toLowerCase()); // don't allow multiword state names
|
|
if (name.length > 6 && name.slice(-4) === "berg") name = name.slice(0,-4); // remove -berg for any
|
|
if (base === 5 && ["sk", "ev", "ov"].includes(name.slice(-2))) name = name.slice(0,-2); // remove -sk/-ev/-ov for Ruthenian
|
|
else if (base === 1 && name.length > 5 && name.slice(-3) === "ton") name = name.slice(0,-3); // remove -ton for English
|
|
else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u"; // Japanese ends on any vowel or -u
|
|
else if (base === 18 && Math.random() < .4) name = vowel(name.slice(0,1).toLowerCase()) ? "Al" + name.toLowerCase() : "Al " + name; // Arabic starts with -Al
|
|
|
|
// define if suffix should be used
|
|
if (name.length > 3 && vowel(name.slice(-1))) {
|
|
if (vowel(name.slice(-2,-1)) && Math.random() < .85) name = name.slice(0,-2); // 85% for vv
|
|
else if (Math.random() < .7) name = name.slice(0,-1); // ~60% for cv
|
|
else return name;
|
|
} else if (Math.random() < .4) return name; // 60% for cc and vc
|
|
|
|
// define suffix
|
|
let suffix = "";
|
|
const rnd = Math.random(), l = name.length;
|
|
if (base === 3) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Italian
|
|
else if (base === 4) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Spanish
|
|
else if (base === 13) suffix = rnd < .03 && l < 7 ? "terra" : "ia"; // Portuguese
|
|
else if (base === 2) suffix = rnd < .03 && l < 7 ? "terre" : "ia"; // French
|
|
else if (base === 0) suffix = rnd < .5 && l < 7 ? "land" : "ia"; // German
|
|
else if (base === 1) suffix = rnd < .4 && l < 7 ? "land" : "ia"; // English
|
|
else if (base === 6) suffix = rnd < .3 && l < 7 ? "land" : "ia"; // Nordic
|
|
else if (base === 7) suffix = rnd < .1 ? "eia" : "ia"; // Greek
|
|
else if (base === 9) suffix = rnd < .35 ? "maa" : "ia"; // Finnic
|
|
else if (base === 15) suffix = rnd < .6 && l < 6 ? "orszag" : "ia"; // Hungarian
|
|
else if (base === 16) suffix = rnd < .5 ? "stan" : "ya"; // Turkish
|
|
else if (base === 10) suffix = "guk"; // Korean
|
|
else if (base === 11) suffix = " Guo"; // Chinese
|
|
else if (base === 14) suffix = rnd < .6 && l < 7 ? "tlan" : "co"; // Nahuatl
|
|
else if (base === 17) suffix = rnd < .8 ? "a" : "ia"; // Berber
|
|
else if (base === 18) suffix = rnd < .8 ? "a" : "ia"; // Arabic
|
|
else suffix = "ia" // other
|
|
|
|
if (name.slice(-1 * suffix.length) === suffix) return name; // no suffix if name already ends with it
|
|
const s1 = suffix.charAt(0);
|
|
if (name.slice(-1) === s1) name = name.slice(0, -1); // remove name last letter if it's a suffix first letter
|
|
if (vowel(s1) === vowel(name.slice(-1)) && vowel(s1) === vowel(name.slice(-2,-1))) name = name.slice(0, -1); // remove name last char if 2 last chars are the same type as suffix's 1st
|
|
if (name.slice(-1) === s1) name = name.slice(0, -1); // remove name last letter if it's a suffix first letter
|
|
return name + suffix;
|
|
}
|
|
|
|
return {getBase, getCulture, getCultureShort, getState, updateChain, updateChains};
|
|
})));
|