mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 10:01:23 +01:00
feat: war alert
This commit is contained in:
parent
50edcb4e30
commit
364d22e6ad
11 changed files with 216 additions and 24 deletions
91
src/config/military.ts
Normal file
91
src/config/military.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
export const getDefaultMilitaryOptions: () => IMilitaryOption[] = function () {
|
||||
return [
|
||||
{icon: "⚔️", name: "infantry", rural: 0.25, urban: 0.2, crew: 1, power: 1, type: "melee", separate: 0},
|
||||
{icon: "🏹", name: "archers", rural: 0.12, urban: 0.2, crew: 1, power: 1, type: "ranged", separate: 0},
|
||||
{icon: "🐴", name: "cavalry", rural: 0.12, urban: 0.03, crew: 2, power: 2, type: "mounted", separate: 0},
|
||||
{icon: "💣", name: "artillery", rural: 0, urban: 0.03, crew: 8, power: 12, type: "machinery", separate: 0},
|
||||
{icon: "🌊", name: "fleet", rural: 0, urban: 0.015, crew: 100, power: 50, type: "naval", separate: 1}
|
||||
];
|
||||
};
|
||||
|
||||
export const relationsAlertRate: {[key in TRelation]: number} = {
|
||||
Vassal: -0.5,
|
||||
Ally: -0.2,
|
||||
Friendly: -0.1,
|
||||
Neutral: 0,
|
||||
Unknown: 0,
|
||||
x: 0,
|
||||
Suspicion: 0.1,
|
||||
Suzerain: 0.3,
|
||||
Rival: 0.5,
|
||||
Enemy: 1
|
||||
};
|
||||
|
||||
type TCulture = Exclude<TCultureType, "Generic">;
|
||||
export const stateModifier: {[key in TMilitaryType]: {[key in TCulture]: number}} = {
|
||||
melee: {Nomadic: 0.5, Highland: 1.2, Lake: 1, Naval: 0.7, Hunting: 1.2, River: 1.1},
|
||||
ranged: {Nomadic: 0.9, Highland: 1.3, Lake: 1, Naval: 0.8, Hunting: 2, River: 0.8},
|
||||
mounted: {Nomadic: 2.3, Highland: 0.6, Lake: 0.7, Naval: 0.3, Hunting: 0.7, River: 0.8},
|
||||
machinery: {Nomadic: 0.8, Highland: 1.4, Lake: 1.1, Naval: 1.4, Hunting: 0.4, River: 1.1},
|
||||
naval: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.8, Hunting: 0.7, River: 1.2},
|
||||
armored: {Nomadic: 1, Highland: 0.5, Lake: 1, Naval: 1, Hunting: 0.7, River: 1.1},
|
||||
aviation: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.2, Hunting: 0.6, River: 1.2},
|
||||
magical: {Nomadic: 1, Highland: 2, Lake: 1, Naval: 1, Hunting: 1, River: 1}
|
||||
};
|
||||
|
||||
export const cellTypeModifier: {[key: string]: {[key in TMilitaryType]: number}} = {
|
||||
nomadic: {
|
||||
melee: 0.2,
|
||||
ranged: 0.5,
|
||||
mounted: 3,
|
||||
machinery: 0.4,
|
||||
naval: 0.3,
|
||||
armored: 1.6,
|
||||
aviation: 1,
|
||||
magical: 0.5
|
||||
},
|
||||
wetland: {
|
||||
melee: 0.8,
|
||||
ranged: 2,
|
||||
mounted: 0.3,
|
||||
machinery: 1.2,
|
||||
naval: 1.0,
|
||||
armored: 0.2,
|
||||
aviation: 0.5,
|
||||
magical: 0.5
|
||||
},
|
||||
highland: {
|
||||
melee: 1.2,
|
||||
ranged: 1.6,
|
||||
mounted: 0.3,
|
||||
machinery: 3,
|
||||
naval: 1.0,
|
||||
armored: 0.8,
|
||||
aviation: 0.3,
|
||||
magical: 2
|
||||
}
|
||||
};
|
||||
|
||||
export const burgTypeModifier: {[key: string]: {[key in TMilitaryType]: number}} = {
|
||||
nomadic: {
|
||||
melee: 0.3,
|
||||
ranged: 0.8,
|
||||
mounted: 3,
|
||||
machinery: 0.4,
|
||||
naval: 1.0,
|
||||
armored: 1.6,
|
||||
aviation: 1,
|
||||
magical: 0.5
|
||||
},
|
||||
wetland: {
|
||||
melee: 1,
|
||||
ranged: 1.6,
|
||||
mounted: 0.2,
|
||||
machinery: 1.2,
|
||||
naval: 1.0,
|
||||
armored: 0.2,
|
||||
aviation: 0.5,
|
||||
magical: 0.5
|
||||
},
|
||||
highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import {findCell} from "utils/graphUtils";
|
|||
import {rn} from "utils/numberUtils";
|
||||
import {P, rand, rw} from "utils/probabilityUtils";
|
||||
import {parseTransform} from "utils/stringUtils";
|
||||
import {getDefaultMilitaryOptions} from "config/military";
|
||||
|
||||
// update old .map version to the current one
|
||||
export function resolveVersionConflicts(version) {
|
||||
|
|
@ -272,7 +273,7 @@ export function resolveVersionConflicts(version) {
|
|||
const year = rand(100, 2000);
|
||||
const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era";
|
||||
const eraShort = era[0] + "E";
|
||||
const military = Military.getDefaultOptions();
|
||||
const military = getDefaultMilitaryOptions();
|
||||
options = {winds, year, era, eraShort, military};
|
||||
|
||||
// v1.3 added campaings data for all states
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {rn, minmax} from "utils/numberUtils";
|
|||
import {rand, gauss, ra} from "utils/probabilityUtils";
|
||||
import {si} from "utils/unitUtils";
|
||||
import {nth} from "utils/languageUtils";
|
||||
import {getDefaultMilitaryOptions} from "config/military";
|
||||
|
||||
window.Military = (function () {
|
||||
const generate = function () {
|
||||
|
|
@ -12,7 +13,7 @@ window.Military = (function () {
|
|||
const {cells, states} = pack;
|
||||
const {p} = cells;
|
||||
const valid = states.filter(s => s.i && !s.removed); // valid states
|
||||
if (!options.military) options.military = getDefaultOptions();
|
||||
if (!options.military) options.military = getDefaultMilitaryOptions();
|
||||
|
||||
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
||||
const area = d3.sum(valid.map(s => s.area)); // total area
|
||||
|
|
@ -331,16 +332,6 @@ window.Military = (function () {
|
|||
validStates.forEach(s => drawRegiments(s.military, s.i));
|
||||
}
|
||||
|
||||
const getDefaultOptions = function () {
|
||||
return [
|
||||
{icon: "⚔️", name: "infantry", rural: 0.25, urban: 0.2, crew: 1, power: 1, type: "melee", separate: 0},
|
||||
{icon: "🏹", name: "archers", rural: 0.12, urban: 0.2, crew: 1, power: 1, type: "ranged", separate: 0},
|
||||
{icon: "🐴", name: "cavalry", rural: 0.12, urban: 0.03, crew: 2, power: 2, type: "mounted", separate: 0},
|
||||
{icon: "💣", name: "artillery", rural: 0, urban: 0.03, crew: 8, power: 12, type: "machinery", separate: 0},
|
||||
{icon: "🌊", name: "fleet", rural: 0, urban: 0.015, crew: 100, power: 50, type: "naval", separate: 1}
|
||||
];
|
||||
};
|
||||
|
||||
const drawRegiments = function (regiments, s) {
|
||||
const size = +armies.attr("box-size");
|
||||
const w = d => (d.n ? size * 4 : size * 6);
|
||||
|
|
@ -512,7 +503,6 @@ window.Military = (function () {
|
|||
return {
|
||||
generate,
|
||||
redraw,
|
||||
getDefaultOptions,
|
||||
getName,
|
||||
generateNote,
|
||||
drawRegiments,
|
||||
|
|
|
|||
|
|
@ -4,20 +4,16 @@ import {NAMEBASE} from "config/namebases";
|
|||
import {getInputNumber} from "utils/nodeUtils";
|
||||
import {P, rand, rw} from "utils/probabilityUtils";
|
||||
import {AreaTiers, culturalMonarchyFormsMap, culturalTheocracyFormsMap, StateForms} from "./config";
|
||||
import type {TStateStatistics} from "./collectStatistics";
|
||||
|
||||
// create 5 area tiers, 4 is the biggest, 0 the smallest
|
||||
export function createAreaTiers(statistics: TStateStatistics) {
|
||||
const stateAreas = Object.entries(statistics)
|
||||
.filter(([id]) => Number(id))
|
||||
.map(([, {area}]) => area);
|
||||
const medianArea = d3.median(stateAreas)!;
|
||||
export function createAreaTiers(stateAreas: number[]) {
|
||||
const meanArea = d3.mean(stateAreas)!;
|
||||
|
||||
const topTierIndex = Math.max(Math.ceil(stateAreas.length ** 0.4) - 2, 0);
|
||||
const minTopTierArea = stateAreas.sort((a, b) => b - a)[topTierIndex];
|
||||
|
||||
return (area: number) => {
|
||||
const tier = Math.min(Math.floor((area / medianArea) * 2.6), 4) as AreaTiers;
|
||||
const tier = Math.min(Math.floor((area / meanArea) * 2.6), 4) as AreaTiers;
|
||||
if (tier === AreaTiers.EMPIRE && area < minTopTierArea) return AreaTiers.KINGDOM;
|
||||
return tier;
|
||||
};
|
||||
|
|
|
|||
45
src/scripts/generation/pack/burgsAndStates/defineWarAlert.ts
Normal file
45
src/scripts/generation/pack/burgsAndStates/defineWarAlert.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import {relationsAlertRate} from "config/military";
|
||||
import {minmax, rn} from "utils/numberUtils";
|
||||
|
||||
export function defineWarAlert(
|
||||
neighbors: number[],
|
||||
relations: TRelation[],
|
||||
areaRate: number,
|
||||
expansionismRate: number
|
||||
) {
|
||||
const expansionismRealization = getRealization(areaRate, expansionismRate);
|
||||
const peacefulness = getPeacefulness(relations);
|
||||
const neighborliness = getNeighborliness(neighbors, relations);
|
||||
const warAlert = getWarAlert(expansionismRealization, peacefulness, neighborliness);
|
||||
|
||||
return rn(warAlert, 2);
|
||||
}
|
||||
|
||||
function getRealization(expansionismRate: number, areaRate: number) {
|
||||
const [MIN, MAX] = [0.25, 4];
|
||||
return minmax(expansionismRate / areaRate, MIN, MAX);
|
||||
}
|
||||
|
||||
function getPeacefulness(relations: TRelation[]) {
|
||||
if (relations.includes("Enemy")) return 1;
|
||||
if (relations.includes("Rival")) return 0.8;
|
||||
if (relations.includes("Suspicion")) return 0.5;
|
||||
return 0.1;
|
||||
}
|
||||
|
||||
function getNeighborliness(neighbors: number[], relations: TRelation[]) {
|
||||
const neighborRelations = neighbors.map(neibStateId => relations[neibStateId]);
|
||||
|
||||
const initialRate = 0.5;
|
||||
const rate = neighborRelations.reduce((acc, relation) => (acc += relationsAlertRate[relation]), initialRate);
|
||||
|
||||
const [MIN, MAX] = [0.3, 3];
|
||||
return minmax(rate, MIN, MAX);
|
||||
}
|
||||
|
||||
function getWarAlert(expansionismRealization: number, peacefulness: number, neighborliness: number) {
|
||||
const alert = expansionismRealization * peacefulness * neighborliness;
|
||||
|
||||
const [MIN, MAX] = [0.1, 5];
|
||||
return minmax(alert, MIN, MAX);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import {defineFullStateName, defineStateName} from "./defineStateName";
|
|||
import {defineStateColors} from "./defineStateColors";
|
||||
import {isBurg} from "utils/typeUtils";
|
||||
import {generateConflicts} from "./generateConflicts";
|
||||
import {defineWarAlert} from "./defineWarAlert";
|
||||
|
||||
import type {TStateStatistics} from "./collectStatistics";
|
||||
import type {TStateData} from "./createStateData";
|
||||
|
|
@ -21,11 +22,15 @@ export function specifyStates(
|
|||
TIME && console.time("specifyStates");
|
||||
|
||||
const colors = defineStateColors(statistics);
|
||||
const getAreaTier = createAreaTiers(statistics);
|
||||
const getNameBase = (cultureId: number) => cultures[cultureId].base;
|
||||
|
||||
const states: IState[] = statesData.map(stateData => {
|
||||
const {i, center, type, culture, capital} = stateData;
|
||||
const stateAreas = getStateAreas(statistics);
|
||||
const totalArea = stateAreas.reduce((a, b) => a + b);
|
||||
const totalExpansionism = statesData.map(state => state.expansionism).reduce((a, b) => a + b);
|
||||
const getAreaTier = createAreaTiers(stateAreas);
|
||||
|
||||
const states = statesData.map(stateData => {
|
||||
const {i, center, type, culture, capital, expansionism} = stateData;
|
||||
const {area, burgs: burgsNumber, neighbors, ...stats} = statistics[i];
|
||||
const color = colors[i];
|
||||
|
||||
|
|
@ -35,6 +40,7 @@ export function specifyStates(
|
|||
|
||||
const relations = diplomacy[i];
|
||||
const isVassal = relations.includes("Vassal");
|
||||
const alert = defineWarAlert(neighbors, relations, area / totalArea, expansionism / totalExpansionism);
|
||||
|
||||
const nameBase = getNameBase(culture);
|
||||
const areaTier = getAreaTier(area);
|
||||
|
|
@ -44,7 +50,7 @@ export function specifyStates(
|
|||
|
||||
const pole = poles[i];
|
||||
|
||||
return {
|
||||
const state: IState = {
|
||||
name,
|
||||
...stateData,
|
||||
form,
|
||||
|
|
@ -56,8 +62,10 @@ export function specifyStates(
|
|||
...stats,
|
||||
neighbors,
|
||||
relations,
|
||||
alert,
|
||||
pole
|
||||
};
|
||||
return state;
|
||||
});
|
||||
|
||||
const conflicts = generateConflicts(states, cultures); // mutates states
|
||||
|
|
@ -65,3 +73,9 @@ export function specifyStates(
|
|||
TIME && console.timeEnd("specifyStates");
|
||||
return {states: [NEUTRALS, ...states], conflicts};
|
||||
}
|
||||
|
||||
function getStateAreas(statistics: TStateStatistics) {
|
||||
return Object.entries(statistics)
|
||||
.filter(([id]) => Number(id))
|
||||
.map(([, {area}]) => area);
|
||||
}
|
||||
|
|
|
|||
15
src/scripts/generation/pack/military/generateMilitary.ts
Normal file
15
src/scripts/generation/pack/military/generateMilitary.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import {TIME} from "config/logging";
|
||||
import {getDefaultMilitaryOptions} from "config/military";
|
||||
import {getUnitModifiers} from "./getUnitModifiers";
|
||||
|
||||
export function generateMilitary(states: TStates) {
|
||||
TIME && console.time("generateMilitaryForces");
|
||||
|
||||
if (!options.military) options.military = getDefaultMilitaryOptions();
|
||||
|
||||
// const unitModifiers = getUnitModifiers(states);
|
||||
|
||||
console.log(states);
|
||||
|
||||
TIME && console.timeEnd("generateMilitaryForces");
|
||||
}
|
||||
21
src/scripts/generation/pack/military/getUnitModifiers.ts
Normal file
21
src/scripts/generation/pack/military/getUnitModifiers.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import {stateModifier} from "config/military";
|
||||
import {isState} from "utils/typeUtils";
|
||||
|
||||
// calculate overall state modifiers for unit types based on state features
|
||||
export function getUnitModifiers(states: TStates) {
|
||||
const validStates = states.filter(isState);
|
||||
|
||||
for (const state of validStates) {
|
||||
const military = {platoons: []};
|
||||
const {i: stateId, relations, expansionism, area, neighbors, alert} = state;
|
||||
|
||||
for (const unit of options.military) {
|
||||
if (!stateModifier[unit.type]) continue;
|
||||
|
||||
let modifier = stateModifier[unit.type][s.type] || 1;
|
||||
if (unit.type === "mounted" && s.formName.includes("Horde")) modifier *= 2;
|
||||
else if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2;
|
||||
military[unit.name] = modifier * alert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import {markupPackFeatures} from "scripts/generation/markup";
|
||||
import {rankCells} from "scripts/generation/pack/rankCells";
|
||||
import {pick} from "utils/functionUtils";
|
||||
|
||||
import {generateBurgsAndStates} from "./burgsAndStates/generateBurgsAndStates";
|
||||
import {expandCultures} from "./cultures/expandCultures";
|
||||
import {generateCultures} from "./cultures/generateCultures";
|
||||
import {generateLakeNames} from "./lakes/generateLakeNames";
|
||||
import {generateMilitary} from "./military/generateMilitary";
|
||||
import {generateProvinces} from "./provinces/generateProvinces";
|
||||
import {generateReligions} from "./religions/generateReligions";
|
||||
import {repackGrid} from "./repackGrid";
|
||||
|
|
@ -162,6 +164,8 @@ export function createPack(grid: IGrid): IPack {
|
|||
const rivers = specifyRivers(rawRivers, cultureIds, cultures);
|
||||
const features = generateLakeNames(mergedFeatures, cultureIds, cultures);
|
||||
|
||||
generateMilitary(states);
|
||||
|
||||
// Military.generate();
|
||||
// Markers.generate();
|
||||
// addZones(); // add to pack data
|
||||
|
|
|
|||
1
src/types/globals.d.ts
vendored
1
src/types/globals.d.ts
vendored
|
|
@ -26,6 +26,7 @@ interface IOptions {
|
|||
winds: [number, number, number, number, number, number];
|
||||
stateLabelsMode: "auto" | "short" | "full";
|
||||
year: number;
|
||||
military: IMilitaryOption[];
|
||||
}
|
||||
|
||||
declare let populationRate: number;
|
||||
|
|
|
|||
14
src/types/pack/states.d.ts
vendored
14
src/types/pack/states.d.ts
vendored
|
|
@ -19,6 +19,7 @@ interface IState {
|
|||
urban: number;
|
||||
neighbors: number[];
|
||||
relations: TRelation[];
|
||||
alert: number;
|
||||
removed?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -51,3 +52,16 @@ type TRelation =
|
|||
| "Vassal"
|
||||
| "Enemy"
|
||||
| "x";
|
||||
|
||||
interface IMilitaryOption {
|
||||
name: string;
|
||||
icon: string;
|
||||
crew: number;
|
||||
power: number;
|
||||
rural: number;
|
||||
urban: number;
|
||||
type: TMilitaryType;
|
||||
separate: Logical;
|
||||
}
|
||||
|
||||
type TMilitaryType = "melee" | "ranged" | "mounted" | "machinery" | "naval" | "armored" | "aviation" | "magical";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue