Implement crew type execution model
This commit is contained in:
@@ -1,9 +1,44 @@
|
||||
import { getTechCost, isCrewTypeAvailable } from '@sammo-ts/logic/world/unitSet.js';
|
||||
import {
|
||||
findCrewTypeById,
|
||||
getCrewTypePickScore,
|
||||
getTechCost,
|
||||
isCrewTypeAvailable,
|
||||
} from '@sammo-ts/logic/world/unitSet.js';
|
||||
import { buildWarConfig } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js';
|
||||
import type { CrewTypeDefinition, General, WarArmTypes } from '@sammo-ts/logic';
|
||||
|
||||
import type { GeneralAI } from '../core.js';
|
||||
import { asRecord, readMetaNumber, roundTo } from '../../aiUtils.js';
|
||||
import { t통솔장 } from './helpers.js';
|
||||
|
||||
export const buildRecruitArmTypeWeights = (general: General, armTypes: WarArmTypes): Array<[number, number]> => {
|
||||
const meta = asRecord(general.meta);
|
||||
const fullStrength = readMetaNumber(meta, 'fullStrength', general.stats.strength);
|
||||
const fullIntelligence = readMetaNumber(meta, 'fullIntelligence', general.stats.intelligence);
|
||||
const weights: Array<[number, number]> = [];
|
||||
|
||||
if (fullStrength > fullIntelligence * 0.9) {
|
||||
for (const armType of [armTypes.footman, armTypes.archer, armTypes.cavalry]) {
|
||||
if (armType === undefined) {
|
||||
continue;
|
||||
}
|
||||
weights.push([armType, Math.sqrt(readMetaNumber(meta, `dex${armType}`, 0) + 500) * fullStrength]);
|
||||
}
|
||||
}
|
||||
if (fullIntelligence > fullStrength * 0.9 && armTypes.wizard !== undefined) {
|
||||
weights.push([
|
||||
armTypes.wizard,
|
||||
Math.sqrt(readMetaNumber(meta, `dex${armTypes.wizard}`, 0) + 500) * fullIntelligence * 3,
|
||||
]);
|
||||
}
|
||||
return weights;
|
||||
};
|
||||
|
||||
const getRequiredTech = (crewType: CrewTypeDefinition): number | null => {
|
||||
const requirement = crewType.requirements.find((entry) => entry.type === 'ReqTech');
|
||||
return requirement?.type === 'ReqTech' && typeof requirement.tech === 'number' ? requirement.tech : null;
|
||||
};
|
||||
|
||||
export const do징병 = (ai: GeneralAI) => {
|
||||
const city = ai.city;
|
||||
const nation = ai.nation;
|
||||
@@ -37,9 +72,12 @@ export const do징병 = (ai: GeneralAI) => {
|
||||
|
||||
const tech = readMetaNumber(asRecord(nation.meta), 'tech', 0);
|
||||
const crewAmountBase = ai.general.stats.leadership * 100;
|
||||
const warConfig = buildWarConfig(ai.scenarioConfig, ai.unitSet);
|
||||
const forcedArmType = readMetaNumber(asRecord(ai.general.meta), 'armType', 0);
|
||||
const armType =
|
||||
readMetaNumber(asRecord(ai.general.meta), 'armType', 0) ||
|
||||
(ai.general.stats.strength >= ai.general.stats.intelligence * 0.9 ? 1 : 4);
|
||||
forcedArmType > 0
|
||||
? forcedArmType
|
||||
: ai.rng.choiceUsingWeightPair(buildRecruitArmTypeWeights(ai.general, warConfig.armTypes));
|
||||
|
||||
const candidates = (ai.unitSet?.crewTypes ?? [])
|
||||
.filter((crew) => crew.armType === armType)
|
||||
@@ -56,7 +94,31 @@ export const do징병 = (ai: GeneralAI) => {
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const picked = ai.rng.choiceUsingWeightPair(candidates.map((crew) => [crew, Math.max(1, crew.cost)]));
|
||||
let picked = ai.rng.choiceUsingWeightPair(
|
||||
candidates.map((crew) => [crew, getCrewTypePickScore(crew, tech, warConfig.armPerPhase)])
|
||||
);
|
||||
if (ai.generalPolicy.can('고급병종')) {
|
||||
const currentCrewType = findCrewTypeById(ai.unitSet, ai.general.crewTypeId);
|
||||
if (
|
||||
currentCrewType &&
|
||||
isCrewTypeAvailable(ai.unitSet, currentCrewType.id, {
|
||||
general: ai.general,
|
||||
nation,
|
||||
map: ai.map,
|
||||
cities: ai.worldRef?.listCities() ?? [],
|
||||
currentYear: ai.world.currentYear,
|
||||
startYear: ai.startYear,
|
||||
})
|
||||
) {
|
||||
const requiredTech = getRequiredTech(currentCrewType);
|
||||
if (
|
||||
requiredTech !== null &&
|
||||
(requiredTech >= 2000 || (currentCrewType.armType !== armType && requiredTech >= 1000))
|
||||
) {
|
||||
picked = currentCrewType;
|
||||
}
|
||||
}
|
||||
}
|
||||
const crewTypeId = picked.id;
|
||||
|
||||
let crewAmount = crewAmountBase;
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
ITEM_KEYS,
|
||||
loadItemModules,
|
||||
createInheritBuffModules,
|
||||
compileCrewTypeCatalog,
|
||||
createCrewTypeWarTriggerRegistry,
|
||||
} from '@sammo-ts/logic';
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
|
||||
@@ -70,6 +72,7 @@ export const buildCommandEnv = (config: ScenarioConfig, unitSet?: UnitSetDefinit
|
||||
const constValues = asRecord(config.const);
|
||||
|
||||
return {
|
||||
...(unitSet ? { unitSet } : {}),
|
||||
develCost: resolveNumber(constValues, ['develCost', 'develcost', 'develrate'], 0),
|
||||
minAvailableRecruitPop: resolveNumber(constValues, ['minAvailableRecruitPop'], 30000),
|
||||
trainDelta: resolveNumber(constValues, ['trainDelta'], DEFAULT_TRAIN_DELTA),
|
||||
@@ -96,11 +99,7 @@ export const buildCommandEnv = (config: ScenarioConfig, unitSet?: UnitSetDefinit
|
||||
),
|
||||
defaultSpecialDomestic: resolveOptionalString(constValues, ['defaultSpecialDomestic']),
|
||||
defaultSpecialWar: resolveOptionalString(constValues, ['defaultSpecialWar']),
|
||||
initialNationGenLimit: resolveNumber(
|
||||
constValues,
|
||||
['initialNationGenLimit'],
|
||||
DEFAULT_INITIAL_NATION_GEN_LIMIT
|
||||
),
|
||||
initialNationGenLimit: resolveNumber(constValues, ['initialNationGenLimit'], DEFAULT_INITIAL_NATION_GEN_LIMIT),
|
||||
maxTechLevel: resolveNumber(constValues, ['maxTechLevel'], DEFAULT_MAX_TECH_LEVEL),
|
||||
baseGold: resolveNumber(constValues, ['baseGold', 'basegold'], DEFAULT_BASE_GOLD),
|
||||
baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], DEFAULT_BASE_RICE),
|
||||
@@ -153,10 +152,21 @@ export const buildReservedTurnDefinitions = async (options: {
|
||||
const itemRegistry = createItemModuleRegistry(itemModules);
|
||||
const itemActionModules = createItemActionModules(itemRegistry);
|
||||
const inheritBuffModules = createInheritBuffModules();
|
||||
options.env.generalActionModules = [...(options.env.generalActionModules ?? []), ...itemActionModules.general];
|
||||
options.env.warActionModules = [...(options.env.warActionModules ?? []), ...itemActionModules.war];
|
||||
options.env.generalActionModules.push(inheritBuffModules.general);
|
||||
options.env.warActionModules.push(inheritBuffModules.war);
|
||||
const crewTypeCatalog = options.env.unitSet?.crewTypes?.length
|
||||
? compileCrewTypeCatalog(options.env.unitSet, createCrewTypeWarTriggerRegistry())
|
||||
: null;
|
||||
options.env.generalActionModules = [
|
||||
...(options.env.generalActionModules ?? []),
|
||||
...(crewTypeCatalog ? [crewTypeCatalog.generalActionModule] : []),
|
||||
inheritBuffModules.general,
|
||||
...itemActionModules.general,
|
||||
];
|
||||
options.env.warActionModules = [
|
||||
...(options.env.warActionModules ?? []),
|
||||
...(crewTypeCatalog ? [crewTypeCatalog.warActionModule] : []),
|
||||
inheritBuffModules.war,
|
||||
...itemActionModules.war,
|
||||
];
|
||||
|
||||
const generalSpecs = await loadGeneralTurnCommandSpecs(options.commandProfile.general);
|
||||
const nationSpecs = await loadNationTurnCommandSpecs(options.commandProfile.nation);
|
||||
|
||||
Reference in New Issue
Block a user