import { MAP_H, MAP_W, indexOf, inside } from "./grid.js"; import { hash2 } from "./random.js"; export const NAME_PROBABILITIES = { contextSuffix: 0.42, secondInfix: 0.22, prefixOnly: 0.38, customName: 1.0, }; const CONTEXT_SUFFIXES = { coastal: ["\u6d5c", "\u6e4a", "\u6e2f", "\u6d66", "\u6d25", "\u5d0e", "\u6e7e"], river: ["\u5ddd", "\u702c", "\u6a4b", "\u6e21", "\u6cbc", "\u6ca2"], plain: ["\u539f", "\u91ce", "\u7530", "\u91cc", "\u6751", "\u5e73"], mountain: ["\u8c37", "\u5ce0", "\u5c3e\u6839", "\u5c71", "\u6ca2", "\u9e93"], historic: ["\u57ce", "\u5e9c", "\u9928", "\u5bae", "\u753a"], suburban: ["\u4e18", "\u53f0", "\u91ce", "\u539f", "\u65b0\u753a", "\u30f6\u4e18"], }; function isNearSea(x, y, sea, radius = 3) { if (!sea) return false; for (let dy = -radius; dy <= radius; dy++) { for (let dx = -radius; dx <= radius; dx++) { const nx = x + dx; const ny = y + dy; if (inside(nx, ny) && sea[indexOf(nx, ny)]) return true; } } return false; } function boundedIndex(entity) { return indexOf(Math.max(0, Math.min(MAP_W - 1, entity.x)), Math.max(0, Math.min(MAP_H - 1, entity.y))); } function chooseNameContext(entity, fields) { const kind = String(entity.kind || "").toLowerCase(); const i = boundedIndex(entity); const coastal = (fields?.coastalLowland?.[i] || 0) > 0.35 || isNearSea(entity.x, entity.y, fields?.sea); const river = (fields?.river?.[i] || 0) > 0.35 || (fields?.valleyField?.[i] || 0) > 0.42; const plain = (fields?.plain?.[i] || 0) > 0.48 || (fields?.agriculture?.[i] || 0) > 0.46 || (fields?.basinField?.[i] || 0) > 0.35; const mountain = (fields?.elevation?.[i] || 0) > 0.52 || (fields?.slope?.[i] || 0) > 0.35 || (fields?.ridgeField?.[i] || 0) > 0.42; if (kind.includes("port") || kind.includes("harbor") || entity.portClass) return "coastal"; if (kind.includes("crossing") || kind.includes("bridge")) return "river"; if (kind.includes("pass") || kind.includes("mountain")) return "mountain"; if (kind.includes("castle") || kind.includes("market")) return "historic"; if (kind.includes("new town") || kind.includes("satellite")) return "suburban"; if (coastal && !mountain) return "coastal"; if (river) return "river"; if (mountain && !plain) return "mountain"; if (plain) return "plain"; return "plain"; } export function chooseNameSuffixPool(entity, fields) { return CONTEXT_SUFFIXES[chooseNameContext(entity, fields)] || CONTEXT_SUFFIXES.plain; } function chooseContextFreePool(entity, fields, nameParts) { const context = chooseNameContext(entity, fields); return nameParts.contextFree?.[context] || nameParts.contextFree?.plain || nameParts.infixes || nameParts.free || [""]; } function pick(pool, seed, n, salt) { if (!pool?.length) return ""; const index = Math.floor(hash2(n + salt * 101, seed + salt * 1009, seed + n * 37 + salt) * pool.length) % pool.length; return pool[index]; } function chance(seed, n, salt, probability) { return hash2(n + salt * 173, seed + salt * 811, seed + n * 43 + salt) < probability; } function appendPart(parts, part) { if (!part) return; const previous = parts[parts.length - 1] || ""; if (previous && (previous === part || previous.endsWith(part) || part.startsWith(previous))) return; parts.push(part); } export function contextualDefaultName(seed, id, entity, nameParts, fields, attempt = 0) { const prefixKey = id.split("-")[0]; const n = Number(id.split("-")[1] || 0) + attempt * 997; const prefixes = nameParts.prefixes || [""]; const infixes = nameParts.infixes || nameParts.free || [""]; const suffixes = nameParts.suffixes || [""]; const contextFree = chooseContextFreePool(entity, fields, nameParts); const contextSuffixes = chooseNameSuffixPool(entity, fields); const parts = []; if (chance(seed, n, 11 + prefixKey.length, NAME_PROBABILITIES.prefixOnly)) { appendPart(parts, pick(prefixes, seed, n, 101)); } const useContextMain = chance(seed, n, 13 + prefixKey.length, 0.42); appendPart(parts, pick(useContextMain ? contextFree : infixes, seed, n, useContextMain ? 211 : 223)); if (chance(seed, n, 19 + prefixKey.length, NAME_PROBABILITIES.secondInfix)) { appendPart(parts, pick(infixes, seed, n, 401)); } const useContextSuffix = chance(seed, n, 23 + prefixKey.length, NAME_PROBABILITIES.contextSuffix); appendPart(parts, pick(useContextSuffix ? contextSuffixes : suffixes, seed, n, useContextSuffix ? 503 : 509)); return parts.slice(-3).join("") || pick(suffixes, seed, n, 601) || "\u539f"; }