mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
refactor: generate organized religions
This commit is contained in:
parent
70e8296c48
commit
cce374da24
8 changed files with 342 additions and 90 deletions
|
|
@ -53,13 +53,13 @@ function placeTowns(townsNumber: number, scoredCellIds: UintArray, points: TPoin
|
|||
const townCells: number[] = [];
|
||||
const townsQuadtree = d3.quadtree();
|
||||
|
||||
const randomizeScaping = (spacing: number) => spacing * gauss(1, 0.3, 0.2, 2, 2);
|
||||
const randomizeSpacing = (spacing: number) => spacing * gauss(1, 0.3, 0.2, 2, 2);
|
||||
|
||||
for (const cellId of scoredCellIds) {
|
||||
const [x, y] = points[cellId];
|
||||
|
||||
// randomize min spacing a bit to make placement not that uniform
|
||||
const currentSpacing = randomizeScaping(spacing);
|
||||
const currentSpacing = randomizeSpacing(spacing);
|
||||
|
||||
if (townsQuadtree.find(x, y, currentSpacing) === undefined) {
|
||||
townCells.push(cellId);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {minmax} from "utils/numberUtils";
|
|||
import type {createCapitals} from "./createCapitals";
|
||||
import type {createStates} from "./createStates";
|
||||
import {ELEVATION, FOREST_BIOMES, MIN_LAND_HEIGHT, DISTANCE_FIELD} from "config/generation";
|
||||
import {isState} from "utils/typeUtils";
|
||||
|
||||
type TCapitals = ReturnType<typeof createCapitals>;
|
||||
type TStates = ReturnType<typeof createStates>;
|
||||
|
|
@ -104,13 +105,9 @@ export function expandStates(
|
|||
|
||||
return normalizeStates(stateIds, capitalCells, cells.c, cells.h);
|
||||
|
||||
function isNeutrals(state: Entry<TStates>): state is TNeutrals {
|
||||
return state.i === 0;
|
||||
}
|
||||
|
||||
function getState(stateId: number) {
|
||||
const state = states[stateId];
|
||||
if (isNeutrals(state)) throw new Error("Neutrals cannot expand");
|
||||
if (!isState(state)) throw new Error("Neutrals cannot expand");
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {minmax, rn} from "utils/numberUtils";
|
|||
import {biased, P, rand} from "utils/probabilityUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {defaultNameBases} from "config/namebases";
|
||||
import {isCulture} from "utils/typeUtils";
|
||||
|
||||
const {COA} = window;
|
||||
|
||||
|
|
@ -284,9 +285,7 @@ export const expandCultures = function (
|
|||
const cultureIds = new Uint16Array(cells.h.length); // cell cultures
|
||||
const queue = new FlatQueue<{cellId: number; cultureId: number}>();
|
||||
|
||||
const isWilderness = (culture: ICulture | TWilderness): culture is TWilderness => culture.i === 0;
|
||||
cultures.forEach(culture => {
|
||||
if (isWilderness(culture) || culture.removed) return;
|
||||
cultures.filter(isCulture).forEach(culture => {
|
||||
queue.push({cellId: culture.center, cultureId: culture.i}, 0);
|
||||
});
|
||||
|
||||
|
|
@ -323,7 +322,7 @@ export const expandCultures = function (
|
|||
|
||||
function getCulture(cultureId: number) {
|
||||
const culture = cultures[cultureId];
|
||||
if (isWilderness(culture)) throw new Error("Wilderness culture cannot expand");
|
||||
if (!isCulture(culture)) throw new Error("Wilderness cannot expand");
|
||||
return culture;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
import {TIME} from "config/logging";
|
||||
import {religionsData} from "config/religionsData";
|
||||
import {getMixedColor} from "utils/colorUtils";
|
||||
import {ra, rw} from "utils/probabilityUtils";
|
||||
|
||||
type TCellsData = Pick<IPack["cells"], "c" | "p" | "g" | "h" | "t" | "biome" | "burg">;
|
||||
|
||||
const {Names} = window;
|
||||
const {approaches, base, forms, methods, types} = religionsData;
|
||||
|
||||
export function generateReligions(states: TStates, cultures: TCultures, cells: TCellsData) {
|
||||
TIME && console.time("generateReligions");
|
||||
|
||||
const religionIds = new Uint16Array(cells.c.length);
|
||||
|
||||
const folkReligions = generateFolkReligions(cultures, cells);
|
||||
console.log(folkReligions);
|
||||
|
||||
TIME && console.timeEnd("generateReligions");
|
||||
return {religionIds};
|
||||
}
|
||||
|
||||
function generateFolkReligions(cultures: TCultures, cells: TCellsData) {
|
||||
const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture =>
|
||||
culture.i !== 0 && !(culture as ICulture).removed;
|
||||
|
||||
return cultures.filter(isValidCulture).map((culture, index) => {
|
||||
const {i: cultureId, name: cultureName, center} = culture;
|
||||
const id = index + 1;
|
||||
const form = rw(forms.Folk);
|
||||
const type: {[key: string]: number} = types[form];
|
||||
const name = cultureName + " " + rw(type);
|
||||
const deity = form === "Animism" ? null : getDeityName(cultures, cultureId);
|
||||
const color = getMixedColor(culture.color, 0.1, 0);
|
||||
|
||||
return {i: id, name, color, culture: cultureId, type: "Folk", form, deity, center: center, origins: [0]};
|
||||
});
|
||||
}
|
||||
|
||||
function getDeityName(cultures: TCultures, cultureId: number) {
|
||||
if (cultureId === undefined) throw "CultureId is undefined";
|
||||
|
||||
const meaning = generateMeaning();
|
||||
|
||||
const base = cultures[cultureId].base;
|
||||
const cultureName = Names.getBase(base);
|
||||
return cultureName + ", The " + meaning;
|
||||
}
|
||||
|
||||
function generateMeaning() {
|
||||
const approach = ra(approaches);
|
||||
if (approach === "Number") return ra(base.number);
|
||||
if (approach === "Being") return ra(base.being);
|
||||
if (approach === "Adjective") return ra(base.adjective);
|
||||
if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`;
|
||||
if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`;
|
||||
if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`;
|
||||
if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`;
|
||||
if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`;
|
||||
if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`;
|
||||
if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`;
|
||||
if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`;
|
||||
if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`;
|
||||
if (approach === "Adjective + Being + of + Genitive")
|
||||
return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`;
|
||||
if (approach === "Adjective + Animal + of + Genitive")
|
||||
return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`;
|
||||
|
||||
throw "Unknown generation approach";
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import {generateCultures, expandCultures} from "./cultures";
|
|||
import {generateRivers} from "./rivers";
|
||||
import {generateBurgsAndStates} from "./burgsAndStates/generateBurgsAndStates";
|
||||
import {generateRoutes} from "./generateRoutes";
|
||||
import {generateReligions} from "./generateReligions";
|
||||
import {generateReligions} from "./religions/generateReligions";
|
||||
|
||||
const {LAND_COAST, WATER_COAST, DEEPER_WATER} = DISTANCE_FIELD;
|
||||
const {Biomes} = window;
|
||||
|
|
@ -128,14 +128,24 @@ export function createPack(grid: IGrid): IPack {
|
|||
burg: burgIds
|
||||
});
|
||||
|
||||
const {religionIds} = generateReligions(states, cultures, {
|
||||
c: cells.c,
|
||||
p: cells.p,
|
||||
g: cells.g,
|
||||
h: heights,
|
||||
t: distanceField,
|
||||
biome,
|
||||
burg: burgIds
|
||||
const {religionIds} = generateReligions({
|
||||
states,
|
||||
cultures,
|
||||
burgs,
|
||||
cultureIds,
|
||||
stateIds,
|
||||
burgIds,
|
||||
cells: {
|
||||
i: cells.i,
|
||||
c: cells.c,
|
||||
p: cells.p,
|
||||
g: cells.g,
|
||||
h: heights,
|
||||
t: distanceField,
|
||||
biome,
|
||||
pop: population,
|
||||
burg: burgIds
|
||||
}
|
||||
});
|
||||
|
||||
// Religions.generate();
|
||||
|
|
|
|||
112
src/scripts/generation/pack/religions/generateReligionName.ts
Normal file
112
src/scripts/generation/pack/religions/generateReligionName.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import {religionsData} from "config/religionsData";
|
||||
import {trimVowels, getAdjective} from "utils/languageUtils";
|
||||
import {rw, ra} from "utils/probabilityUtils";
|
||||
import {isCulture, isState} from "utils/typeUtils";
|
||||
|
||||
const {Names} = window;
|
||||
const {methods, types} = religionsData;
|
||||
|
||||
interface IContext {
|
||||
cultureId: number;
|
||||
stateId: number;
|
||||
burgId: number;
|
||||
cultures: TCultures;
|
||||
states: TStates;
|
||||
burgs: TBurgs;
|
||||
center: number;
|
||||
form: keyof typeof types;
|
||||
deity: string;
|
||||
}
|
||||
|
||||
const context = {
|
||||
data: {} as IContext,
|
||||
|
||||
// data setter
|
||||
set current(data: IContext) {
|
||||
this.data = data;
|
||||
},
|
||||
|
||||
// data getters
|
||||
get culture() {
|
||||
return this.data.cultures[this.data.cultureId];
|
||||
},
|
||||
|
||||
get state() {
|
||||
return this.data.states[this.data.stateId];
|
||||
},
|
||||
|
||||
get burg() {
|
||||
return this.data.burgs[this.data.burgId];
|
||||
},
|
||||
|
||||
get form() {
|
||||
return this.data.form;
|
||||
},
|
||||
|
||||
get deity() {
|
||||
return this.data.deity;
|
||||
},
|
||||
|
||||
// generation methods
|
||||
get random() {
|
||||
return Names.getBase(this.culture.base);
|
||||
},
|
||||
|
||||
get type() {
|
||||
return rw(types[this.form] as {[key: string]: number});
|
||||
},
|
||||
|
||||
get supreme() {
|
||||
return this.deity.split(/[ ,]+/)[0];
|
||||
},
|
||||
|
||||
get cultureName() {
|
||||
return this.culture.name;
|
||||
},
|
||||
|
||||
get place() {
|
||||
const base = this.burg.name || this.state.name;
|
||||
return trimVowels(base.split(/[ ,]+/)[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const nameMethodsMap = {
|
||||
"Random + type": {getName: () => `${context.random} ${context.type}`, expansion: "global"},
|
||||
"Random + ism": {getName: () => `${trimVowels(context.random)}ism`, expansion: "global"},
|
||||
"Supreme + ism": {getName: () => `${trimVowels(context.supreme)}ism`, expansion: "global"},
|
||||
"Faith of + Supreme": {
|
||||
getName: () => `${ra(["Faith", "Way", "Path", "Word", "Witnesses"])} of ${context.supreme}`,
|
||||
expansion: "global"
|
||||
},
|
||||
"Place + ism": {getName: () => `${context.place}ism`, expansion: "state"},
|
||||
"Culture + ism": {getName: () => `${trimVowels(context.cultureName)}ism`, expansion: "culture"},
|
||||
"Place + ian + type": {
|
||||
getName: () => `${getAdjective(context.place)} ${context.type}`,
|
||||
expansion: "state"
|
||||
},
|
||||
"Culture + type": {getName: () => `${context.cultureName} ${context.type}`, expansion: "culture"}
|
||||
};
|
||||
|
||||
export function generateReligionName(data: IContext) {
|
||||
context.current = data;
|
||||
const {stateId, cultureId, states, cultures, center} = data;
|
||||
|
||||
const method = nameMethodsMap[rw(methods)];
|
||||
const name = method.getName();
|
||||
|
||||
let expansion = method.expansion;
|
||||
if (expansion === "state" && !stateId) expansion = "global";
|
||||
if (expansion === "culture" && !cultureId) expansion = "global";
|
||||
|
||||
if (expansion === "state" && Math.random() > 0.5) {
|
||||
const state = states[stateId];
|
||||
if (isState(state)) return {name, expansion, center: state.center};
|
||||
}
|
||||
|
||||
if (expansion === "culture" && Math.random() > 0.5) {
|
||||
const culture = cultures[cultureId];
|
||||
if (isCulture(culture)) return {name, expansion, center: culture.center};
|
||||
}
|
||||
|
||||
return {name, expansion, center};
|
||||
}
|
||||
198
src/scripts/generation/pack/religions/generateReligions.ts
Normal file
198
src/scripts/generation/pack/religions/generateReligions.ts
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {TIME, WARN} from "config/logging";
|
||||
import {religionsData} from "config/religionsData";
|
||||
import {unique} from "utils/arrayUtils";
|
||||
import {getMixedColor, getRandomColor} from "utils/colorUtils";
|
||||
import {findAll} from "utils/graphUtils";
|
||||
import {getInputNumber} from "utils/nodeUtils";
|
||||
import {ra, rand, rw} from "utils/probabilityUtils";
|
||||
import {isBurg} from "utils/typeUtils";
|
||||
import {generateReligionName} from "./generateReligionName";
|
||||
|
||||
const {Names} = window;
|
||||
const {approaches, base, forms, types} = religionsData;
|
||||
|
||||
type TCellsData = Pick<IPack["cells"], "i" | "c" | "p" | "g" | "h" | "t" | "biome" | "pop" | "burg">;
|
||||
|
||||
export function generateReligions({
|
||||
states,
|
||||
cultures,
|
||||
burgs,
|
||||
cultureIds,
|
||||
stateIds,
|
||||
burgIds,
|
||||
cells
|
||||
}: {
|
||||
states: TStates;
|
||||
cultures: TCultures;
|
||||
burgs: TBurgs;
|
||||
cultureIds: Uint16Array;
|
||||
stateIds: Uint16Array;
|
||||
burgIds: Uint16Array;
|
||||
cells: TCellsData;
|
||||
}) {
|
||||
TIME && console.time("generateReligions");
|
||||
|
||||
const religionIds = new Uint16Array(cells.c.length);
|
||||
|
||||
const folkReligions = generateFolkReligions(cultures);
|
||||
const basicReligions = generateOrganizedReligionsAndCults(
|
||||
states,
|
||||
cultures,
|
||||
burgs,
|
||||
cultureIds,
|
||||
stateIds,
|
||||
burgIds,
|
||||
folkReligions,
|
||||
{
|
||||
i: cells.i,
|
||||
p: cells.p,
|
||||
pop: cells.pop
|
||||
}
|
||||
);
|
||||
|
||||
console.log(folkReligions, basicReligions);
|
||||
|
||||
TIME && console.timeEnd("generateReligions");
|
||||
return {religionIds};
|
||||
}
|
||||
|
||||
function generateFolkReligions(cultures: TCultures) {
|
||||
const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture =>
|
||||
culture.i !== 0 && !(culture as ICulture).removed;
|
||||
|
||||
return cultures.filter(isValidCulture).map(culture => {
|
||||
const {i: cultureId, name: cultureName, center} = culture;
|
||||
const form = rw(forms.Folk);
|
||||
const type: {[key: string]: number} = types[form];
|
||||
const name = cultureName + " " + rw(type);
|
||||
const deity = form === "Animism" ? null : getDeityName(cultures, cultureId);
|
||||
const color = getMixedColor(culture.color, 0.1, 0);
|
||||
|
||||
return {name, type: "Folk", form, deity, color, culture: cultureId, center, origins: [0]};
|
||||
});
|
||||
}
|
||||
|
||||
function generateOrganizedReligionsAndCults(
|
||||
states: TStates,
|
||||
cultures: TCultures,
|
||||
burgs: TBurgs,
|
||||
cultureIds: Uint16Array,
|
||||
stateIds: Uint16Array,
|
||||
burgIds: Uint16Array,
|
||||
folkReligions: ReturnType<typeof generateFolkReligions>,
|
||||
cells: Pick<IPack["cells"], "i" | "p" | "pop">
|
||||
) {
|
||||
const religionsNumber = getInputNumber("religionsInput");
|
||||
if (religionsNumber === 0) return [];
|
||||
|
||||
const cultsNumber = Math.floor((rand(1, 4) / 10) * religionsNumber); // 10-40%
|
||||
const organizedNumber = religionsNumber - cultsNumber;
|
||||
|
||||
const canditateCells = getCandidateCells();
|
||||
const religionCells = placeReligions();
|
||||
|
||||
return religionCells.map((cellId, index) => {
|
||||
const cultureId = cultureIds[cellId];
|
||||
const stateId = stateIds[cellId];
|
||||
const burgId = burgIds[cellId];
|
||||
|
||||
const type = index < organizedNumber ? "Organized" : "Cult";
|
||||
|
||||
const form = rw(forms[type] as {[key in keyof typeof types]: number});
|
||||
const deityName = getDeityName(cultures, cultureId);
|
||||
const deity = form === "Non-theism" ? null : deityName;
|
||||
|
||||
const {name, expansion, center} = generateReligionName({
|
||||
cultureId,
|
||||
stateId,
|
||||
burgId,
|
||||
cultures,
|
||||
states,
|
||||
burgs,
|
||||
center: cellId,
|
||||
form,
|
||||
deity: deityName
|
||||
});
|
||||
|
||||
const folkReligion = folkReligions.find(({culture}) => culture === cultureId);
|
||||
const baseColor = folkReligion?.color || getRandomColor();
|
||||
const color = getMixedColor(baseColor, 0.3, 0);
|
||||
|
||||
return {name, type, form, deity, color, culture: cultureId, center, expansion};
|
||||
});
|
||||
|
||||
function placeReligions() {
|
||||
const religionCells = [];
|
||||
const religionsTree = d3.quadtree();
|
||||
|
||||
// initial min distance between religions
|
||||
let spacing = (graphWidth + graphHeight) / 4 / religionsNumber;
|
||||
|
||||
for (const cellId of canditateCells) {
|
||||
const [x, y] = cells.p[cellId];
|
||||
|
||||
if (religionsTree.find(x, y, spacing) === undefined) {
|
||||
religionCells.push(cellId);
|
||||
religionsTree.add([x, y]);
|
||||
|
||||
if (religionCells.length === religionsNumber) return religionCells;
|
||||
}
|
||||
}
|
||||
|
||||
WARN && console.warn(`Placed only ${religionCells.length} of ${religionsNumber} religions`);
|
||||
return religionCells;
|
||||
}
|
||||
|
||||
function getCandidateCells() {
|
||||
const validBurgs = burgs.filter(isBurg);
|
||||
|
||||
if (validBurgs.length >= religionsNumber)
|
||||
return validBurgs.sort((a, b) => b.population - a.population).map(burg => burg.cell);
|
||||
|
||||
return cells.i.filter(i => cells.pop[i] > 2).sort((a, b) => cells.pop[b] - cells.pop[a]);
|
||||
}
|
||||
}
|
||||
|
||||
function getDeityName(cultures: TCultures, cultureId: number) {
|
||||
if (cultureId === undefined) throw "CultureId is undefined";
|
||||
|
||||
const meaning = generateMeaning();
|
||||
|
||||
const base = cultures[cultureId].base;
|
||||
const cultureName = Names.getBase(base);
|
||||
return cultureName + ", The " + meaning;
|
||||
}
|
||||
|
||||
function generateMeaning() {
|
||||
const approach = ra(approaches);
|
||||
if (approach === "Number") return ra(base.number);
|
||||
if (approach === "Being") return ra(base.being);
|
||||
if (approach === "Adjective") return ra(base.adjective);
|
||||
if (approach === "Color + Animal") return `${ra(base.color)} ${ra(base.animal)}`;
|
||||
if (approach === "Adjective + Animal") return `${ra(base.adjective)} ${ra(base.animal)}`;
|
||||
if (approach === "Adjective + Being") return `${ra(base.adjective)} ${ra(base.being)}`;
|
||||
if (approach === "Adjective + Genitive") return `${ra(base.adjective)} ${ra(base.genitive)}`;
|
||||
if (approach === "Color + Being") return `${ra(base.color)} ${ra(base.being)}`;
|
||||
if (approach === "Color + Genitive") return `${ra(base.color)} ${ra(base.genitive)}`;
|
||||
if (approach === "Being + of + Genitive") return `${ra(base.being)} of ${ra(base.genitive)}`;
|
||||
if (approach === "Being + of the + Genitive") return `${ra(base.being)} of the ${ra(base.theGenitive)}`;
|
||||
if (approach === "Animal + of + Genitive") return `${ra(base.animal)} of ${ra(base.genitive)}`;
|
||||
if (approach === "Adjective + Being + of + Genitive")
|
||||
return `${ra(base.adjective)} ${ra(base.being)} of ${ra(base.genitive)}`;
|
||||
if (approach === "Adjective + Animal + of + Genitive")
|
||||
return `${ra(base.adjective)} ${ra(base.animal)} of ${ra(base.genitive)}`;
|
||||
|
||||
throw "Unknown generation approach";
|
||||
}
|
||||
|
||||
function getReligionsInRadius(
|
||||
religionIds: Uint16Array,
|
||||
{x, y, r, max}: {x: number; y: number; r: number; max: number}
|
||||
) {
|
||||
if (max === 0) return [0];
|
||||
const cellsInRadius = findAll(x, y, r);
|
||||
const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r));
|
||||
return religions.length ? religions.slice(0, max) : [0];
|
||||
}
|
||||
6
src/utils/typeUtils.ts
Normal file
6
src/utils/typeUtils.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export const isState = (state: TNeutrals | IState): state is IState => state.i !== 0 && !(state as IState).removed;
|
||||
|
||||
export const isCulture = (culture: TWilderness | ICulture): culture is ICulture =>
|
||||
culture.i !== 0 && !(culture as ICulture).removed;
|
||||
|
||||
export const isBurg = (burg: TNoBurg | IBurg): burg is IBurg => burg.i !== 0 && !(burg as IBurg).removed;
|
||||
Loading…
Add table
Add a link
Reference in a new issue