This commit is contained in:
172
src/data/attributeResolver.ts
Normal file
172
src/data/attributeResolver.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import type {
|
||||
AttributeVector,
|
||||
Character,
|
||||
CombatActionAttributeProfile,
|
||||
CustomWorldProfile,
|
||||
RoleActionDefinition,
|
||||
RoleAttributeProfile,
|
||||
RoleRelationState,
|
||||
WorldAttributeSchema,
|
||||
WorldAttributeSlot,
|
||||
WorldType,
|
||||
} from '../types';
|
||||
import {WORLD_ATTRIBUTE_SLOT_IDS} from '../types';
|
||||
import { resolveRelationStanceFromAffinity } from './affinityLevels';
|
||||
import {normalizeAttributeVector, roundNumber} from './attributeValidation';
|
||||
import {getWorldAttributeSchema} from './worldAttributeSchemas';
|
||||
|
||||
export function resolveRelationStance(affinity: number): RoleRelationState['stance'] {
|
||||
return resolveRelationStanceFromAffinity(affinity);
|
||||
}
|
||||
|
||||
export function buildRelationState(affinity: number): RoleRelationState {
|
||||
return {
|
||||
affinity,
|
||||
stance: resolveRelationStance(affinity),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveAttributeSchema(
|
||||
worldType: WorldType | null | undefined,
|
||||
customWorldProfile?: CustomWorldProfile | null,
|
||||
) {
|
||||
return getWorldAttributeSchema(worldType, customWorldProfile);
|
||||
}
|
||||
|
||||
export function resolveCharacterAttributeProfile(
|
||||
character: Character,
|
||||
worldType: WorldType | null | undefined,
|
||||
customWorldProfile?: CustomWorldProfile | null,
|
||||
) {
|
||||
void customWorldProfile;
|
||||
|
||||
if (worldType && character.attributeProfiles?.[worldType]) {
|
||||
return character.attributeProfiles[worldType] ?? character.attributeProfile;
|
||||
}
|
||||
|
||||
if (worldType === 'CUSTOM' && character.attributeProfiles?.CUSTOM) {
|
||||
return character.attributeProfiles.CUSTOM;
|
||||
}
|
||||
|
||||
return character.attributeProfile;
|
||||
}
|
||||
|
||||
export function getAttributeSlotValue(profile: RoleAttributeProfile | null | undefined, slotId: string) {
|
||||
return profile?.values?.[slotId] ?? 0;
|
||||
}
|
||||
|
||||
export function getNormalizedAttributeWeights(
|
||||
profile: RoleAttributeProfile | null | undefined,
|
||||
schema: WorldAttributeSchema,
|
||||
) {
|
||||
return normalizeAttributeVector(profile?.values ?? {}, schema.slots.map(slot => slot.slotId));
|
||||
}
|
||||
|
||||
export function scoreAttributeFit(
|
||||
profile: RoleAttributeProfile | null | undefined,
|
||||
vector: AttributeVector | null | undefined,
|
||||
schema: WorldAttributeSchema,
|
||||
) {
|
||||
const weights = getNormalizedAttributeWeights(profile, schema);
|
||||
const normalizedVector = normalizeAttributeVector(vector ?? {}, schema.slots.map(slot => slot.slotId));
|
||||
|
||||
return roundNumber(
|
||||
schema.slots.reduce(
|
||||
(sum, slot) => sum + (weights[slot.slotId] ?? 0) * (normalizedVector[slot.slotId] ?? 0),
|
||||
0,
|
||||
),
|
||||
4,
|
||||
);
|
||||
}
|
||||
|
||||
export function scoreActionMatch(
|
||||
actorProfile: RoleAttributeProfile | null | undefined,
|
||||
action:
|
||||
| Pick<RoleActionDefinition, 'intentVector' | 'resistVector' | 'baseScore'>
|
||||
| Pick<CombatActionAttributeProfile, 'intentVector' | 'resistVector'>,
|
||||
schema: WorldAttributeSchema,
|
||||
options: {
|
||||
targetProfile?: RoleAttributeProfile | null;
|
||||
actorCoefficient?: number;
|
||||
targetCoefficient?: number;
|
||||
relationModifier?: number;
|
||||
contextModifier?: number;
|
||||
} = {},
|
||||
) {
|
||||
const actorFit = scoreAttributeFit(actorProfile, action.intentVector, schema);
|
||||
const targetResistance = action.resistVector
|
||||
? scoreAttributeFit(options.targetProfile, action.resistVector, schema)
|
||||
: 0;
|
||||
const actorCoefficient = options.actorCoefficient ?? 1;
|
||||
const targetCoefficient = options.targetCoefficient ?? 1;
|
||||
const baseScore = 'baseScore' in action ? action.baseScore : 0;
|
||||
|
||||
return roundNumber(
|
||||
baseScore
|
||||
+ actorFit * actorCoefficient
|
||||
- targetResistance * targetCoefficient
|
||||
+ (options.relationModifier ?? 0)
|
||||
+ (options.contextModifier ?? 0),
|
||||
4,
|
||||
);
|
||||
}
|
||||
|
||||
export function getSortedAttributeEntries(
|
||||
profile: RoleAttributeProfile | null | undefined,
|
||||
schema: WorldAttributeSchema,
|
||||
) {
|
||||
return [...schema.slots]
|
||||
.map(slot => ({
|
||||
slot,
|
||||
value: getAttributeSlotValue(profile, slot.slotId),
|
||||
}))
|
||||
.sort((left, right) => right.value - left.value);
|
||||
}
|
||||
|
||||
export function describeTopAttributes(
|
||||
profile: RoleAttributeProfile | null | undefined,
|
||||
schema: WorldAttributeSchema,
|
||||
limit = 3,
|
||||
) {
|
||||
return getSortedAttributeEntries(profile, schema)
|
||||
.slice(0, limit)
|
||||
.map(entry => `${entry.slot.name}${entry.value}`);
|
||||
}
|
||||
|
||||
export function formatAttributeList(
|
||||
profile: RoleAttributeProfile | null | undefined,
|
||||
schema: WorldAttributeSchema,
|
||||
limit = schema.slots.length,
|
||||
) {
|
||||
return getSortedAttributeEntries(profile, schema)
|
||||
.slice(0, limit)
|
||||
.map(entry => ({
|
||||
slot: entry.slot,
|
||||
value: entry.value,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getLeadingAttributeSlot(
|
||||
profile: RoleAttributeProfile | null | undefined,
|
||||
schema: WorldAttributeSchema,
|
||||
) {
|
||||
return getSortedAttributeEntries(profile, schema)[0]?.slot ?? schema.slots[0] ?? null;
|
||||
}
|
||||
|
||||
export function buildSchemaSummary(schema: WorldAttributeSchema, limit = 6) {
|
||||
return schema.slots.slice(0, limit).map(slot => ({
|
||||
name: slot.name,
|
||||
definition: slot.definition,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getSlotById(schema: WorldAttributeSchema, slotId: string): WorldAttributeSlot | null {
|
||||
return schema.slots.find(slot => slot.slotId === slotId) ?? null;
|
||||
}
|
||||
|
||||
export function buildDefaultAxisVector(overrides: Partial<Record<(typeof WORLD_ATTRIBUTE_SLOT_IDS)[number], number>>) {
|
||||
return WORLD_ATTRIBUTE_SLOT_IDS.reduce<AttributeVector>((result, slotId) => {
|
||||
result[slotId] = overrides[slotId] ?? 0;
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
Reference in New Issue
Block a user