diff --git a/src/config/religionsData.ts b/src/config/religionsData.ts index c655136a..b7d70665 100644 --- a/src/config/religionsData.ts +++ b/src/config/religionsData.ts @@ -298,15 +298,34 @@ const forms = { Heresy: {Heresy: 1} }; -const methods = { - "Random + type": 3, - "Random + ism": 1, - "Supreme + ism": 5, - "Faith of + Supreme": 5, - "Place + ism": 1, - "Culture + ism": 2, - "Place + ian + type": 6, - "Culture + type": 4 +const namingMethods = { + Folk: { + "Culture + type": 1 + }, + + Organized: { + "Random + type": 3, + "Random + ism": 1, + "Supreme + ism": 5, + "Faith of + Supreme": 5, + "Place + ism": 1, + "Culture + ism": 2, + "Place + ian + type": 6, + "Culture + type": 4 + }, + + Cult: { + "Burg + ian + type": 2, + "Random + ian + type": 1, + "Type + of the + meaning": 2 + }, + + Heresy: { + "Burg + ian + type": 3, + "Random + ism": 3, + "Random + ian + type": 2, + "Type + of the + meaning": 1 + } }; const types = { @@ -336,4 +355,4 @@ const types = { } }; -export const religionsData = {approaches, base, forms, methods, types}; +export const religionsData = {approaches, base, forms, namingMethods, types}; diff --git a/src/scripts/generation/pack/generateRoutes.ts b/src/scripts/generation/pack/generateRoutes.ts index d80b6ad1..da280f9a 100644 --- a/src/scripts/generation/pack/generateRoutes.ts +++ b/src/scripts/generation/pack/generateRoutes.ts @@ -4,6 +4,7 @@ import FlatQueue from "flatqueue"; import {TIME} from "config/logging"; import {ELEVATION, MIN_LAND_HEIGHT, ROUTES} from "config/generation"; import {dist2} from "utils/functionUtils"; +import {isBurg} from "utils/typeUtils"; type TCellsData = Pick; @@ -25,7 +26,6 @@ export function generateRoutes(burgs: TBurgs, temp: Int8Array, cells: TCellsData const capitalsByFeature: Dict = {}; const portsByFeature: Dict = {}; - const isBurg = (burg: IBurg | TNoBurg): burg is IBurg => burg.i !== 0; const addBurg = (object: Dict, feature: number, burg: IBurg) => { if (!object[feature]) object[feature] = []; object[feature].push(burg); diff --git a/src/scripts/generation/pack/religions/expandReligions.ts b/src/scripts/generation/pack/religions/expandReligions.ts new file mode 100644 index 00000000..314e1a59 --- /dev/null +++ b/src/scripts/generation/pack/religions/expandReligions.ts @@ -0,0 +1,7 @@ +export function expandReligions( + religions: Pick[] +) { + const religionIds = new Uint16Array(); + + return religionIds; +} diff --git a/src/scripts/generation/pack/religions/generateDeityName.ts b/src/scripts/generation/pack/religions/generateDeityName.ts index f58c7ce2..d1342ec9 100644 --- a/src/scripts/generation/pack/religions/generateDeityName.ts +++ b/src/scripts/generation/pack/religions/generateDeityName.ts @@ -15,7 +15,7 @@ export function getDeityName(cultures: TCultures, cultureId: number) { return cultureName + ", The " + meaning; } -function generateMeaning() { +export function generateMeaning() { const approach = ra(approaches); if (approach === "Number") return ra(base.number); if (approach === "Being") return ra(base.being); diff --git a/src/scripts/generation/pack/religions/generateFolkReligions.ts b/src/scripts/generation/pack/religions/generateFolkReligions.ts index 59d99910..37f05c72 100644 --- a/src/scripts/generation/pack/religions/generateFolkReligions.ts +++ b/src/scripts/generation/pack/religions/generateFolkReligions.ts @@ -1,22 +1,14 @@ import {religionsData} from "config/religionsData"; -import {getMixedColor} from "utils/colorUtils"; import {rw} from "utils/probabilityUtils"; -import {getDeityName} from "./generateDeityName"; +import {isCulture} from "utils/typeUtils"; -const {forms, types} = religionsData; +const {forms} = religionsData; -export function generateFolkReligions(cultures: TCultures) { - const isValidCulture = (culture: TWilderness | ICulture): culture is ICulture => - culture.i !== 0 && !(culture as ICulture).removed; +export function generateFolkReligions(cultures: TCultures): Pick[] { + return cultures.filter(isCulture).map(culture => { + const {i: cultureId, center} = culture; + const form = rw(forms.Folk); - 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]}; + return {type: "Folk", form, culture: cultureId, center}; }); } diff --git a/src/scripts/generation/pack/religions/generateOrganizedReligions.ts b/src/scripts/generation/pack/religions/generateOrganizedReligions.ts new file mode 100644 index 00000000..6df0e5be --- /dev/null +++ b/src/scripts/generation/pack/religions/generateOrganizedReligions.ts @@ -0,0 +1,70 @@ +import * as d3 from "d3"; + +import {WARN} from "config/logging"; +import {religionsData} from "config/religionsData"; +import {getInputNumber} from "utils/nodeUtils"; +import {rand, rw} from "utils/probabilityUtils"; +import {isBurg} from "utils/typeUtils"; + +const {forms} = religionsData; + +export function generateOrganizedReligions( + burgs: TBurgs, + cultureIds: Uint16Array, + cells: Pick +): Pick[] { + const religionsNumber = getInputNumber("religionsInput"); + if (religionsNumber === 0) return []; + + const canditateCells = getCandidateCells(); + const religionCells = placeReligions(); + + const cultsNumber = Math.floor((rand(1, 4) / 10) * religionCells.length); // 10-40% + const heresiesNumber = Math.floor((rand(0, 2) / 10) * religionCells.length); // 0-20% + const organizedNumber = religionCells.length - cultsNumber - heresiesNumber; + + const getType = (index: number) => { + if (index < organizedNumber) return "Organized"; + if (index < organizedNumber + cultsNumber) return "Cult"; + return "Heresy"; + }; + + return religionCells.map((cellId, index) => { + const type = getType(index); + const form = rw(forms[type]); + const cultureId = cultureIds[cellId]; + + return {type, form, culture: cultureId, center: cellId}; + }); + + function placeReligions() { + const religionCells = []; + const religionsTree = d3.quadtree(); + + // min distance between religions + const spacing = (graphWidth + graphHeight) / 2 / 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]); + } +} diff --git a/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts b/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts deleted file mode 100644 index 44e74be9..00000000 --- a/src/scripts/generation/pack/religions/generateOrganizedReligionsAndCults.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as d3 from "d3"; - -import {WARN} from "config/logging"; -import {religionsData} from "config/religionsData"; -import {getRandomColor, getMixedColor} from "utils/colorUtils"; -import {getInputNumber} from "utils/nodeUtils"; -import {rand, rw} from "utils/probabilityUtils"; -import {isBurg} from "utils/typeUtils"; -import {getDeityName} from "./generateDeityName"; -import {generateFolkReligions} from "./generateFolkReligions"; -import {generateReligionName} from "./generateReligionName"; - -const {forms, types} = religionsData; - -export function generateOrganizedReligionsAndCults( - states: TStates, - cultures: TCultures, - burgs: TBurgs, - cultureIds: Uint16Array, - stateIds: Uint16Array, - burgIds: Uint16Array, - folkReligions: ReturnType, - cells: Pick -) { - 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]); - } -} diff --git a/src/scripts/generation/pack/religions/generateReligionName.ts b/src/scripts/generation/pack/religions/generateReligionName.ts index 5abe3e0d..330277ec 100644 --- a/src/scripts/generation/pack/religions/generateReligionName.ts +++ b/src/scripts/generation/pack/religions/generateReligionName.ts @@ -1,10 +1,10 @@ import {religionsData} from "config/religionsData"; import {trimVowels, getAdjective} from "utils/languageUtils"; import {rw, ra} from "utils/probabilityUtils"; -import {isCulture, isState} from "utils/typeUtils"; +import {generateMeaning} from "./generateDeityName"; const {Names} = window; -const {methods, types} = religionsData; +const {namingMethods, types} = religionsData; interface IContext { cultureId: number; @@ -13,9 +13,8 @@ interface IContext { cultures: TCultures; states: TStates; burgs: TBurgs; - center: number; - form: keyof typeof types; - deity: string; + form: string; + supreme: string; } const context = { @@ -43,8 +42,8 @@ const context = { return this.data.form; }, - get deity() { - return this.data.deity; + get supreme() { + return this.data.supreme; }, // generation methods @@ -53,11 +52,11 @@ const context = { }, get type() { - return rw(types[this.form] as {[key: string]: number}); + return rw(types[this.form as keyof typeof types]); }, - get supreme() { - return this.deity.split(/[ ,]+/)[0]; + get supremeName() { + return this.supreme.split(/[ ,]+/)[0]; }, get cultureName() { @@ -67,15 +66,19 @@ const context = { get place() { const base = this.burg.name || this.state.name; return trimVowels(base.split(/[ ,]+/)[0]); + }, + + get meaning() { + return generateMeaning(); } }; 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"}, + "Supreme + ism": {getName: () => `${trimVowels(context.supremeName)}ism`, expansion: "global"}, "Faith of + Supreme": { - getName: () => `${ra(["Faith", "Way", "Path", "Word", "Witnesses"])} of ${context.supreme}`, + getName: () => `${ra(["Faith", "Way", "Path", "Word", "Witnesses"])} of ${context.supremeName}`, expansion: "global" }, "Place + ism": {getName: () => `${context.place}ism`, expansion: "state"}, @@ -84,29 +87,36 @@ const nameMethodsMap = { getName: () => `${getAdjective(context.place)} ${context.type}`, expansion: "state" }, - "Culture + type": {getName: () => `${context.cultureName} ${context.type}`, expansion: "culture"} + "Culture + type": {getName: () => `${context.cultureName} ${context.type}`, expansion: "culture"}, + "Burg + ian + type": { + getName: () => context.burg.name && `${getAdjective(context.burg.name)} ${context.type}`, + expansion: "global" + }, + "Random + ian + type": {getName: () => `${getAdjective(context.random)} ${context.type}`, expansion: "global"}, + "Type + of the + meaning": {getName: () => `${context.type} of the ${context.meaning}`, expansion: "global"} }; -export function generateReligionName(data: IContext) { +const fallbackMethod = nameMethodsMap["Random + type"]; + +function getMethod(type: IReligion["type"]) { + const methods: {[key in string]: number} = namingMethods[type]; + const method = rw(methods); + return nameMethodsMap[method as keyof typeof nameMethodsMap]; +} + +export function generateReligionName( + type: IReligion["type"], + data: IContext +): {name: string; expansion: IReligion["expansion"]} { context.current = data; - const {stateId, cultureId, states, cultures, center} = data; + const {stateId, cultureId} = data; - const method = nameMethodsMap[rw(methods)]; - const name = method.getName(); + const method = getMethod(type); + const name = method.getName() || fallbackMethod.getName(); - let expansion = method.expansion; + let expansion = method.expansion as IReligion["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}; + return {name, expansion}; } diff --git a/src/scripts/generation/pack/religions/generateReligions.ts b/src/scripts/generation/pack/religions/generateReligions.ts index 59eb65a1..91a6eb75 100644 --- a/src/scripts/generation/pack/religions/generateReligions.ts +++ b/src/scripts/generation/pack/religions/generateReligions.ts @@ -1,8 +1,8 @@ import {TIME} from "config/logging"; -import {unique} from "utils/arrayUtils"; -import {findAll} from "utils/graphUtils"; +import {pick} from "utils/functionUtils"; import {generateFolkReligions} from "./generateFolkReligions"; -import {generateOrganizedReligionsAndCults} from "./generateOrganizedReligionsAndCults"; +import {generateOrganizedReligions} from "./generateOrganizedReligions"; +import {specifyReligions} from "./specifyReligions"; type TCellsData = Pick; @@ -25,36 +25,20 @@ export function generateReligions({ }) { TIME && console.time("generateReligions"); - const religionIds = new Uint16Array(cells.c.length); - const folkReligions = generateFolkReligions(cultures); - const basicReligions = generateOrganizedReligionsAndCults( - states, - cultures, - burgs, - cultureIds, + const basicReligions = generateOrganizedReligions(burgs, cultureIds, pick(cells, "i", "p", "pop")); + const {religions, religionIds} = specifyReligions( + [...folkReligions, ...basicReligions], stateIds, burgIds, - folkReligions, - { - i: cells.i, - p: cells.p, - pop: cells.pop - } + cultures, + states, + burgs, + cells.p ); - console.log(folkReligions, basicReligions); + console.log(religions); TIME && console.timeEnd("generateReligions"); - return {religionIds}; -} - -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]; + return {religionIds, religions}; } diff --git a/src/scripts/generation/pack/religions/specifyReligions.ts b/src/scripts/generation/pack/religions/specifyReligions.ts new file mode 100644 index 00000000..786c7081 --- /dev/null +++ b/src/scripts/generation/pack/religions/specifyReligions.ts @@ -0,0 +1,111 @@ +import {unique} from "utils/arrayUtils"; +import {getMixedColor, getRandomColor} from "utils/colorUtils"; +import {findAll} from "utils/graphUtils"; +import {each, gauss, rand} from "utils/probabilityUtils"; +import {isCulture} from "utils/typeUtils"; +import {expandReligions} from "./expandReligions"; +import {getDeityName} from "./generateDeityName"; +import {generateReligionName} from "./generateReligionName"; + +const expansionismMap = { + Folk: () => 0, + Organized: () => rand(3, 8), + Cult: () => gauss(1.1, 0.5, 0, 5), + Heresy: () => gauss(1.2, 0.5, 0, 5) +}; + +export function specifyReligions( + religionsData: Pick[], + stateIds: Uint16Array, + burgIds: Uint16Array, + cultures: TCultures, + states: TStates, + burgs: TBurgs, + points: TPoints +) { + const religions = religionsData.map(({type, form, culture: cultureId, center}, index) => { + const supreme = getDeityName(cultures, cultureId); + const deity = form === "Non-theism" || form === "Animism" ? null : supreme; + + const stateId = stateIds[center]; + const burgId = burgIds[center]; + + const {name, expansion} = generateReligionName(type, { + cultureId, + stateId, + burgId, + cultures, + states, + burgs, + form, + supreme + }); + + const expansionism = expansionismMap[type](); + + const culture = cultures[cultureId]; + const color = isCulture(culture) ? getMixedColor(culture.color, 0.1, 0) : getRandomColor(); + + const origins: number[] = []; // define after religions expansion + return {i: index + 1, name, type, form, culture: cultureId, center, deity, expansion, expansionism, color, origins}; + }); + + const religionIds = expandReligions(religions); + + const names = renameOldReligions(religions); + const origins = defineOrigins(religionIds, religions, points); + + return { + religions: religions.map((religion, index) => ({name: names[index], religion, origins: origins[index]})), + religionIds + }; +} + +// add 'Old' to names of folk religions which have organized competitors +function renameOldReligions(religions: Pick[]) { + return religions.map(({name, type, culture: cultureId}) => { + if (type !== "Folk") return name; + + const haveOrganized = religions.some( + ({type, culture, expansion}) => culture === cultureId && type === "Organized" && expansion === "culture" + ); + if (haveOrganized && name.slice(0, 3) !== "Old") return `Old ${name}`; + return name; + }); +} + +function defineOrigins( + religionIds: Uint16Array, + religions: Pick[], + points: TPoints +) { + return religions.map(religion => { + if (religion.type === "Folk") return [0]; + + const {type, culture: cultureId, expansion, center} = religion; + const [x, y] = points[center]; + + const folkReligion = religions.find(({culture, type}) => type === "Folk" || culture === cultureId); + const isFolkBased = folkReligion && cultureId && expansion === "culture" && each(2)(center); + + if (isFolkBased) return [folkReligion.i]; + + if (type === "Organized") { + const origins = getReligionsInRadius(religionIds, {x, y, r: 150 / religions.length, max: 2}); + return origins; + } + + const origins = getReligionsInRadius(religionIds, {x, y, r: 300 / religions.length, max: rand(0, 4)}); + return origins; + }); +} + +function getReligionsInRadius( + religionIds: Uint16Array, + {x, y, r, max}: {x: number; y: number; r: number; max: number} +) { + if (max === 0) return [0]; + const cellsInRadius: number[] = []; // findAll(x, y, r); + const religions = unique(cellsInRadius.map(i => religionIds[i]).filter(r => r)); + return religions.length ? religions.slice(0, max) : [0]; +} diff --git a/src/types/pack/religions.d.ts b/src/types/pack/religions.d.ts index 215395a1..eb4c5bb1 100644 --- a/src/types/pack/religions.d.ts +++ b/src/types/pack/religions.d.ts @@ -1,13 +1,15 @@ interface IReligion { i: number; name: string; - type: "Folk" | "Orgamized" | "Cult" | "Heresy"; + type: "Folk" | "Organized" | "Cult" | "Heresy"; color: string; culture: number; form: any; deity: string | null; center: number; origins: number[]; + expansion?: "global" | "culture" | "state"; + expansionism: number; removed?: boolean; }