"use strict"; window.Markers = (function () { let config = []; let occupied = []; function getDefaultConfig() { const culturesSet = document.getElementById("culturesSet").value; const isFantasy = culturesSet.includes("Fantasy"); return [ { type: "volcanoes", icon: "🌋", multiplier: 10, fn: addVolcanoes }, { type: "hot-springs", icon: "♨️", multiplier: 20, fn: addHotSprings }, { type: "mines", icon: "⛏️", multiplier: 50, fn: addMines }, { type: "gem-mines", icon: "💎", multiplier: 3, fn: addGemMines }, { type: "bridges", icon: "🌉", multiplier: 40, fn: addBridges }, { type: "inns", icon: "🍻", multiplier: 20, fn: addInns }, { type: "lighthouses", icon: "🚨", multiplier: 1, fn: addLighthouses }, { type: "waterfalls", icon: "💦", multiplier: 1, fn: addWaterfalls }, { type: "battlefields", icon: "⚔️", multiplier: 3, fn: addBattlefields }, { type: "dungeons", icon: "🗝️", multiplier: 5, fn: addDungeons }, { type: "lake-monsters", icon: "🐉", multiplier: 8, fn: addLakeMonsters }, { type: "sea-monsters", icon: "🦑", multiplier: 150, fn: addSeaMonsters }, { type: "hill-monsters", icon: "👹", multiplier: 20, fn: addHillMonsters, }, { type: "sacred-mountains", icon: "🗻", multiplier: 5, fn: addSacredMountains, }, { type: "sacred-forests", icon: "🌳", multiplier: 10, fn: addSacredForests, }, { type: "sacred-pineries", icon: "🌲", multiplier: 10, fn: addSacredPineries, }, { type: "sacred-palm-groves", icon: "🌴", multiplier: 5, fn: addSacredPalmGroves, }, { type: "brigands", icon: "💰", multiplier: 5, fn: addBrigands }, { type: "pirates", icon: "🏴☠️", multiplier: 15, fn: addPirates }, { type: "statues", icon: "🗿", multiplier: 5, fn: addStatues }, { type: "ruines", icon: "🏺", multiplier: 10, fn: addRuines }, { type: "spiders", icon: "🕷️", multiplier: 45, fn: addSpiders }, { type: "giant goat heard", icon: "🐐", multiplier: 25, fn: addGoatHeard, }, { type: "citadel", icon: "🏯", multiplier: 10, fn: addSacredCitidel }, { type: "portals", icon: "🌀", multiplier: 3, fn: addPortals }, ]; } const getConfig = () => config; const setConfig = (newConfig) => { config = newConfig; }; const generate = function () { setConfig(getDefaultConfig()); pack.markers = []; generateTypes(); }; const regenerate = () => { pack.markers = pack.markers.filter(({ i, lock, cell }) => { if (lock) { occupied[cell] = true; return true; } const id = `marker${i}`; document.getElementById(id)?.remove(); const index = notes.findIndex((note) => note.id === id); if (index != -1) notes.splice(index, 1); return false; }); generateTypes(); }; function generateTypes() { TIME && console.time("addMarkers"); config.forEach(({ type, icon, multiplier, fn }) => { if (multiplier === 0) return; fn(type, icon, multiplier); }); occupied = []; TIME && console.timeEnd("addMarkers"); } function getQuantity(array, min, each, multiplier) { if (!array.length || array.length < min / multiplier) return 0; const requestQty = Math.ceil((array.length / each) * multiplier); return array.length < requestQty ? array.length : requestQty; } function extractAnyElement(array) { const index = Math.floor(Math.random() * array.length); return array.splice(index, 1); } function getMarkerCoordinates(cell) { const { cells, burgs } = pack; const burgId = cells.burg[cell]; if (burgId) { const { x, y } = burgs[burgId]; return [x, y]; } return cells.p[cell]; } function addMarker({ cell, type, icon, dx, dy, px }) { const i = pack.markers.length; const [x, y] = getMarkerCoordinates(cell); const marker = { i, icon, type, x, y, cell }; if (dx) marker.dx = dx; if (dy) marker.dy = dy; if (px) marker.px = px; pack.markers.push(marker); occupied[cell] = true; return "marker" + i; } function addVolcanoes(type, icon, multiplier) { const { cells } = pack; let mountains = Array.from( cells.i .filter((i) => !occupied[i] && cells.h[i] >= 70) .sort((a, b) => cells.h[b] - cells.h[a]) ); let quantity = getQuantity(mountains, 10, 500, multiplier); if (!quantity) return; while (quantity) { const [cell] = extractAnyElement(mountains); const id = addMarker({ cell, icon, type, dx: 52, px: 13 }); const proper = Names.getCulture(cells.culture[cell]); const name = P(0.3) ? "Mount " + proper : Math.random() > 0.3 ? proper + " Volcano" : proper; notes.push({ id, name, legend: `Active volcano. Height: ${getFriendlyHeight(cells.p[cell])}`, }); quantity--; } } function addHotSprings(type, icon, multiplier) { const { cells } = pack; let springs = Array.from( cells.i .filter((i) => !occupied[i] && cells.h[i] > 50) .sort((a, b) => cells.h[b] - cells.h[a]) ); let quantity = getQuantity(springs, 30, 1200, multiplier); if (!quantity) return; while (quantity) { const [cell] = extractAnyElement(springs); const id = addMarker({ cell, icon, type, dy: 52 }); const proper = Names.getCulture(cells.culture[cell]); const temp = convertTemperature(gauss(35, 15, 20, 100)); notes.push({ id, name: proper + " Hot Springs", legend: `A hot springs area. Average temperature: ${temp}`, }); quantity--; } } function addMines(type, icon, multiplier) { const { cells } = pack; let hillyBurgs = Array.from( cells.i.filter((i) => !occupied[i] && cells.h[i] > 47 && cells.burg[i]) ); let quantity = getQuantity(hillyBurgs, 1, 15, multiplier); if (!quantity) return; const resources = { salt: 5, coal: 7, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1, }; while (quantity && hillyBurgs.length) { const [cell] = extractAnyElement(hillyBurgs); const id = addMarker({ cell, icon, type, dx: 48, px: 13 }); const resource = rw(resources); const burg = pack.burgs[cells.burg[cell]]; const name = `${burg.name} — ${resource} mining town`; const population = rn(burg.population * populationRate * urbanization); const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`; notes.push({ id, name, legend }); quantity--; } } function addGemMines(type, icon, multiplier) { const { cells } = pack; let hillyBurgs = Array.from( cells.i.filter((i) => !occupied[i] && cells.h[i] > 67) ); let quantity = getQuantity(hillyBurgs, 1, 15, multiplier); if (!quantity) return; const resources = { Agni_mani: 1, Alamandine: 1, Alestone: 1, Alexandrite: 1, Algae: 1, Amaratha: 1, Amber: 1, Amethyst: 1, Andar: 1, Aquamarine: 1, Aradite: 1, Augelite: 1, Aventurine: 1, Azurite: 1, Banded_agate: 1, Beljuril: 1, Beryl: 1, Black_opal: 1, Black_pearl: 1, Black_sapphire: 1, Bloodstone: 1, Blue_quartz: 1, Blue_sapphire: 1, Blue_spinel: 1, Bluestone: 1, Boakhar: 1, Brandeen: 1, Carbuncle: 1, Carnelian: 1, Chalcedony: 1, Chrysoberyl: 1, Chrysocolla: 1, Chrysolite: 1, Chrysoprase: 1, Citrine: 1, Cleiophane: 1, Coral: 1, Corstal: 1, Corundum: 1, Crown_of_silver: 1, Cymophane: 1, Datcha: 1, Demontoid: 1, Diamond: 1, Diopside: 1, Dioptase: 1, Disthene: 1, Emerald: 1, Epidote: 1, Essonite: 1, Euclase: 1, Eye_agate: 1, Fire_agate: 1, Fire_opal: 1, Flamedance: 1, Fluorite: 1, Frost_agate: 1, Garnet: 1, Goldline: 1, Greenstone: 1, Hambergyle: 1, Hematite: 1, Hyacinth: 1, Hyaline: 1, Hyalite: 1, Hydrophane: 1, Hypersthene: 1, Idicolite: 1, Iolite: 1, Irtios: 1, Jacinth: 1, Jade: 1, Jargoon: 1, Jasmal: 1, Jasper: 1, Jet: 1, Kings_tears: 1, Kornerupine: 1, Kunzite: 1, Laerals_Tears: 1, Lapis_lazuli: 1, Luriyl: 1, Lynx_eye: 1, Malachite: 1, Malacon: 1, Mellochrysos: 1, Microcline: 1, Moonbar: 1, Moonstone: 1, Morganite: 1, Moss_agate: 1, Mykaro: 1, Mynteer: 1, Nelvine: 1, Nephrite: 1, Nune: 1, Obsidian: 1, Octel: 1, Olivine: 1, Ooline: 1, Onyx: 1, Ophealine: 1, Orbaline: 1, Orblen: 1, Orl: 1, Orprase: 1, Pearl: 1, Peridot: 1, Pyrope: 1, Quartz: 1, Raindrop: 1, Red_Tears: 1, Rhodochrosite: 1, Rhodolite: 1, Rhodonite: 1, Rosaline: 1, Rubellite: 1, Ruby: 1, Rusteen: 1, Saganite: 1, Samarskite: 1, Sanidine: 1, Sarbossa: 1, Sardonyx: 1, Satin_spar: 1, Scapra: 1, Serpentine: 1, Shandon: 1, Sharpstone: 1, Silkstone: 1, Sinhalite: 1, Skydrop: 1, Spessartite: 1, Sphene: 1, Spinel: 1, Star_rose_quarts: 1, Star_ruby: 1, Star_sapphire: 1, Sunstone: 1, Tabasheer: 1, Tanzanite: 1, Tchazar: 1, Thupartial: 1, Tiger_eye: 1, Topaz: 1, Tourmaline: 1, Tremair: 1, Turquoise: 1, Ulvaen: 1, Variscite: 1, Water_opal: 1, Waterstar: 1, White_opal: 1, Witherite: 1, Wonderstone: 1, Woodtine: 1, Yellow_sapphire: 1, Zarbrina: 1, Zendalure: 1, Ziose: 1, Zircon: 1, }; while (quantity && hillyBurgs.length) { const [cell] = extractAnyElement(hillyBurgs); const id = addMarker({ cell, icon, type, dx: 48, px: 13 }); const resource = rw(resources); const burg = pack.burgs[cells.burg[cell]]; const name = `${burg.name} — ${resource} mining town`; const population = rn(burg.population * populationRate * urbanization); const legend = `${resource} mine`; notes.push({ id, name, legend }); quantity--; } } function addBridges(type, icon, multiplier) { const { cells, burgs } = pack; const meanFlux = d3.mean(cells.fl.filter((fl) => fl)); let bridges = Array.from( cells.i.filter( (i) => !occupied[i] && cells.burg[i] && cells.t[i] !== 1 && burgs[cells.burg[i]].population > 20 && cells.r[i] && cells.fl[i] > meanFlux ) ); let quantity = getQuantity(bridges, 1, 5, multiplier); if (!quantity) return; while (quantity) { const [cell] = extractAnyElement(bridges); const id = addMarker({ cell, icon, type, px: 14 }); const burg = pack.burgs[cells.burg[cell]]; const river = pack.rivers.find((r) => r.i === pack.cells.r[cell]); const riverName = river ? `${river.name} ${river.type}` : "river"; const name = river && P(0.2) ? river.name : burg.name; const weightedAdjectives = { stone: 10, wooden: 1, lengthy: 2, formidable: 2, rickety: 1, beaten: 1, weathered: 1, }; notes.push({ id, name: `${name} Bridge`, legend: `A ${rw( weightedAdjectives )} bridge spans over the ${riverName} near ${burg.name}`, }); quantity--; } } function addInns(type, icon, multiplier) { const { cells } = pack; let taverns = Array.from( cells.i.filter( (i) => !occupied[i] && cells.h[i] >= 20 && cells.road[i] > 4 && cells.pop[i] > 10 ) ); let quantity = getQuantity(taverns, 1, 100, multiplier); if (!quantity) return; const colors = [ "Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey", ]; const animals = [ "Antelope", "Ape", "Badger", "Bear", "Beaver", "Bison", "Boar", "Buffalo", "Cat", "Crane", "Crocodile", "Crow", "Deer", "Dog", "Eagle", "Elk", "Fox", "Goat", "Goose", "Hare", "Hawk", "Heron", "Horse", "Hyena", "Ibis", "Jackal", "Jaguar", "Lark", "Leopard", "Lion", "Mantis", "Marten", "Moose", "Mule", "Narwhal", "Owl", "Panther", "Rat", "Raven", "Rook", "Scorpion", "Shark", "Sheep", "Snake", "Spider", "Swan", "Tiger", "Turtle", "Wolf", "Wolverine", "Camel", "Falcon", "Hound", "Ox", ]; const adjectives = [ "New", "Good", "High", "Old", "Great", "Big", "Major", "Happy", "Main", "Huge", "Far", "Beautiful", "Fair", "Prime", "Ancient", "Golden", "Proud", "Lucky", "Fat", "Honest", "Giant", "Distant", "Friendly", "Loud", "Hungry", "Magical", "Superior", "Peaceful", "Frozen", "Divine", "Favorable", "Brave", "Sunny", "Flying", ]; const methods = [ "Boiled", "Grilled", "Roasted", "Spit-roasted", "Stewed", "Stuffed", "Jugged", "Mashed", "Baked", "Braised", "Poached", "Marinated", "Pickled", "Smoked", "Dried", "Dry-aged", "Corned", "Fried", "Pan-fried", "Deep-fried", "Dressed", "Steamed", "Cured", "Syrupped", "Flame-Broiled", ]; const courses = [ "beef", "pork", "bacon", "chicken", "lamb", "chevon", "hare", "rabbit", "hart", "deer", "antlers", "bear", "buffalo", "badger", "beaver", "turkey", "pheasant", "duck", "goose", "teal", "quail", "pigeon", "seal", "carp", "bass", "pike", "catfish", "sturgeon", "escallop", "pie", "cake", "pottage", "pudding", "onions", "carrot", "potato", "beet", "garlic", "cabbage", "eggplant", "eggs", "broccoli", "zucchini", "pepper", "olives", "pumpkin", "spinach", "peas", "chickpea", "beans", "rice", "pasta", "bread", "apples", "peaches", "pears", "melon", "oranges", "mango", "tomatoes", "cheese", "corn", "rat tails", "pig ears", ]; const types = [ "hot", "cold", "fire", "ice", "smoky", "misty", "shiny", "sweet", "bitter", "salty", "sour", "sparkling", "smelly", ]; const drinks = [ "wine", "brandy", "jinn", "whisky", "rom", "beer", "cider", "mead", "liquor", "spirit", "vodka", "tequila", "absinthe", "nectar", "milk", "kvass", "kumis", "tea", "water", "juice", "sap", ]; while (quantity) { const [cell] = extractAnyElement(taverns); const id = addMarker({ cell, icon, type, px: 14 }); const typeName = P(0.3) ? "inn" : "tavern"; const isAnimalThemed = P(0.85); const animal = ra(animals); const name = isAnimalThemed ? P(0.6) ? ra(colors) + " " + animal : ra(adjectives) + " " + animal : ra(adjectives) + " " + capitalize(type); const meal = isAnimalThemed && P(0.3) ? animal : ra(courses); const course = `${ra(methods)} ${meal}`.toLowerCase(); const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra( drinks )}`.toLowerCase(); const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here`; notes.push({ id, name: "The " + name, legend }); quantity--; } } function addLighthouses(type, icon, multiplier) { const { cells } = pack; const lighthouses = Array.from( cells.i.filter( (i) => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some((c) => cells.h[c] < 20 && cells.road[c]) ) ); let quantity = getQuantity(lighthouses, 1, 2, multiplier); if (!quantity) return; while (quantity) { const [cell] = extractAnyElement(lighthouses); const id = addMarker({ cell, icon, type, px: 14 }); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); notes.push({ id, name: getAdjective(proper) + " Lighthouse" + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`, }); quantity--; } } function addWaterfalls(type, icon, multiplier) { const { cells } = pack; const waterfalls = Array.from( cells.i.filter( (i) => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some((c) => cells.h[c] < 40 && cells.r[c]) ) ); const quantity = getQuantity(waterfalls, 1, 5, multiplier); if (!quantity) return; const descriptions = [ "A gorgeous waterfall flows here", "The rapids of an exceptionally beautiful waterfall", "An impressive waterfall has cut through the land", "The cascades of a stunning waterfall", "A river drops down from a great height forming a wonderous waterfall", "A breathtaking waterfall cuts through the landscape", ]; for (let i = 0; i < waterfalls.length && i < quantity; i++) { const cell = waterfalls[i]; const id = addMarker({ cell, icon, type, dy: 54, px: 16 }); const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]); notes.push({ id, name: getAdjective(proper) + " Waterfall" + name, legend: `${ra(descriptions)}`, }); } } function addBattlefields(type, icon, multiplier) { const { cells, states } = pack; let battlefields = Array.from( cells.i.filter( (i) => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25 ) ); let quantity = getQuantity(battlefields, 50, 700, multiplier); if (!quantity) return; while (quantity && battlefields.length) { const [cell] = extractAnyElement(battlefields); const id = addMarker({ cell, icon, type, dy: 52 }); const state = states[cells.state[cell]]; if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state); const campaign = ra(state.campaigns); const date = generateDate(campaign.start, campaign.end); const name = Names.getCulture(cells.culture[cell]) + " Battlefield"; const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`; notes.push({ id, name, legend }); quantity--; } } function addDungeons(type, icon, multiplier) { const { cells } = pack; let dungeons = Array.from( cells.i.filter((i) => !occupied[i] && cells.pop[i] && cells.pop[i] < 3) ); let quantity = getQuantity(dungeons, 30, 200, multiplier); if (!quantity) return; while (quantity) { const [cell] = extractAnyElement(dungeons); const id = addMarker({ cell, icon, type, dy: 51, px: 13 }); const dungeonSeed = `${seed}${cell}`; const name = "Dungeon"; const legend = `