'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: 1, fn: addVolcanoes}, {type: 'hot-springs', icon: '♨️', multiplier: 1, fn: addHotSprings}, {type: 'mines', icon: '⛏️', multiplier: 1, fn: addMines}, {type: 'bridges', icon: '🌉', multiplier: 1, fn: addBridges}, {type: 'inns', icon: '🍻', multiplier: 1, fn: addInns}, {type: 'lighthouses', icon: '🚨', multiplier: 1, fn: addLighthouses}, {type: 'waterfalls', icon: '⟱', multiplier: 1, fn: addWaterfalls}, {type: 'battlefields', icon: '⚔️', multiplier: 1, fn: addBattlefields}, {type: 'dungeons', icon: '🗝️', multiplier: 1, fn: addDungeons}, {type: 'lake-monsters', icon: '🐉', multiplier: 1, fn: addLakeMonsters}, {type: 'sea-monsters', icon: '🦑', multiplier: 1, fn: addSeaMonsters}, {type: 'hill-monsters', icon: '👹', multiplier: 1, fn: addHillMonsters}, {type: 'sacred-mountains', icon: '🗻', multiplier: 1, fn: addSacredMountains}, {type: 'sacred-forests', icon: '🌳', multiplier: 1, fn: addSacredForests}, {type: 'sacred-pineries', icon: '🌲', multiplier: 1, fn: addSacredPineries}, {type: 'sacred-palm-groves', icon: '🌴', multiplier: 1, fn: addSacredPalmGroves}, {type: 'brigands', icon: '💰', multiplier: 1, fn: addBrigands}, {type: 'pirates', icon: '🏴☠️', multiplier: 1, fn: addPirates}, {type: 'statues', icon: '🗿', multiplier: 1, fn: addStatues}, {type: 'ruines', icon: '🏺', multiplier: 1, fn: addRuines}, {type: 'portals', icon: '🌀', multiplier: +isFantasy, 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, 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 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.7); 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 = `