From 364d22e6ad10f98e6a84f95581afd657cd1cce8e Mon Sep 17 00:00:00 2001 From: Azgaar Date: Wed, 5 Oct 2022 01:03:24 +0300 Subject: [PATCH] feat: war alert --- src/config/military.ts | 91 +++++++++++++++++++ src/modules/dynamic/auto-update.js | 3 +- src/modules/military-generator.js | 14 +-- .../pack/burgsAndStates/defineStateForm.ts | 10 +- .../pack/burgsAndStates/defineWarAlert.ts | 45 +++++++++ .../pack/burgsAndStates/specifyStates.ts | 22 ++++- .../pack/military/generateMilitary.ts | 15 +++ .../pack/military/getUnitModifiers.ts | 21 +++++ src/scripts/generation/pack/pack.ts | 4 + src/types/globals.d.ts | 1 + src/types/pack/states.d.ts | 14 +++ 11 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 src/config/military.ts create mode 100644 src/scripts/generation/pack/burgsAndStates/defineWarAlert.ts create mode 100644 src/scripts/generation/pack/military/generateMilitary.ts create mode 100644 src/scripts/generation/pack/military/getUnitModifiers.ts diff --git a/src/config/military.ts b/src/config/military.ts new file mode 100644 index 00000000..43649bb2 --- /dev/null +++ b/src/config/military.ts @@ -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; +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} +}; diff --git a/src/modules/dynamic/auto-update.js b/src/modules/dynamic/auto-update.js index 978853f0..9b1a7a46 100644 --- a/src/modules/dynamic/auto-update.js +++ b/src/modules/dynamic/auto-update.js @@ -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 diff --git a/src/modules/military-generator.js b/src/modules/military-generator.js index dc6a70f7..69a4d38d 100644 --- a/src/modules/military-generator.js +++ b/src/modules/military-generator.js @@ -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, diff --git a/src/scripts/generation/pack/burgsAndStates/defineStateForm.ts b/src/scripts/generation/pack/burgsAndStates/defineStateForm.ts index a434434e..6e41bc97 100644 --- a/src/scripts/generation/pack/burgsAndStates/defineStateForm.ts +++ b/src/scripts/generation/pack/burgsAndStates/defineStateForm.ts @@ -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; }; diff --git a/src/scripts/generation/pack/burgsAndStates/defineWarAlert.ts b/src/scripts/generation/pack/burgsAndStates/defineWarAlert.ts new file mode 100644 index 00000000..ff4d7d3b --- /dev/null +++ b/src/scripts/generation/pack/burgsAndStates/defineWarAlert.ts @@ -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); +} diff --git a/src/scripts/generation/pack/burgsAndStates/specifyStates.ts b/src/scripts/generation/pack/burgsAndStates/specifyStates.ts index 3edbe3c2..ffaa34c8 100644 --- a/src/scripts/generation/pack/burgsAndStates/specifyStates.ts +++ b/src/scripts/generation/pack/burgsAndStates/specifyStates.ts @@ -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); +} diff --git a/src/scripts/generation/pack/military/generateMilitary.ts b/src/scripts/generation/pack/military/generateMilitary.ts new file mode 100644 index 00000000..eb45a2b6 --- /dev/null +++ b/src/scripts/generation/pack/military/generateMilitary.ts @@ -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"); +} diff --git a/src/scripts/generation/pack/military/getUnitModifiers.ts b/src/scripts/generation/pack/military/getUnitModifiers.ts new file mode 100644 index 00000000..fed50616 --- /dev/null +++ b/src/scripts/generation/pack/military/getUnitModifiers.ts @@ -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; + } + } +} diff --git a/src/scripts/generation/pack/pack.ts b/src/scripts/generation/pack/pack.ts index 34a11b23..dce76471 100644 --- a/src/scripts/generation/pack/pack.ts +++ b/src/scripts/generation/pack/pack.ts @@ -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 diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index c65e7859..8c8ea66f 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -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; diff --git a/src/types/pack/states.d.ts b/src/types/pack/states.d.ts index 79dd27cd..647da3d7 100644 --- a/src/types/pack/states.d.ts +++ b/src/types/pack/states.d.ts @@ -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";