- Introduced Prisma as a dependency in package.json and updated pnpm-lock.yaml. - Created Prisma module and service for database interactions. - Added initial Prisma schema and migration for user, survivor, mission, and related entities. - Implemented throttling in the API using @nestjs/throttler for rate limiting. - Enhanced mission management logic to utilize Prisma for database transactions. - Updated missions controller and service to handle mission state and participant management. - Added Twitch PubSub service for real-time updates on mission states.
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
import type { ModifierApplication, Perk, PerkModifierTarget } from '@fog-explorer/api-interfaces';
|
|
|
|
export interface AppliedModifier extends ModifierApplication {
|
|
readonly preApplyProbability: number;
|
|
readonly postApplyProbability: number;
|
|
}
|
|
|
|
export interface PerkMathResult {
|
|
readonly probability: number;
|
|
readonly applied: AppliedModifier[];
|
|
}
|
|
|
|
const P_MIN = 0;
|
|
const P_MAX = 1;
|
|
|
|
/**
|
|
* Applies all perk modifiers targeting `successChance` from the given perk
|
|
* slots, filtered by encounter tags. Additives are summed first, then
|
|
* multiplicatives applied in order. Result is clamped to [0, 1].
|
|
*
|
|
* Separating this from the resolver keeps the probability pipeline testable
|
|
* independently and makes modifier-overflow edge cases visible.
|
|
*/
|
|
export function applyPerkModifiers(
|
|
baseProbability: number,
|
|
perks: Perk[],
|
|
encounterTags: string[],
|
|
target: PerkModifierTarget = 'successChance'
|
|
): PerkMathResult {
|
|
let probability = baseProbability;
|
|
const applied: AppliedModifier[] = [];
|
|
|
|
// Two-pass: collect additive sum, then apply multiplicatives.
|
|
// This matches standard RPG convention: add-then-multiply prevents
|
|
// multiplicative stacking from compounding excessively.
|
|
let additiveSum = 0;
|
|
const multiplicativePerks: Array<{ perk: Perk; amount: number }> = [];
|
|
|
|
for (const perk of perks) {
|
|
for (const mod of perk.modifiers) {
|
|
if (mod.target !== target) continue;
|
|
if (!conditionMatches(mod.condition, encounterTags)) continue;
|
|
|
|
if (mod.type === 'additive') {
|
|
additiveSum += mod.amount;
|
|
} else {
|
|
multiplicativePerks.push({ perk, amount: mod.amount });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply additive sum in one step.
|
|
if (additiveSum !== 0) {
|
|
const pre = probability;
|
|
probability += additiveSum;
|
|
|
|
// Record one entry per contributing perk.
|
|
for (const perk of perks) {
|
|
for (const mod of perk.modifiers) {
|
|
if (mod.target !== target || mod.type !== 'additive') continue;
|
|
if (!conditionMatches(mod.condition, encounterTags)) continue;
|
|
applied.push({
|
|
perkKey: perk.key,
|
|
target: mod.target,
|
|
type: mod.type,
|
|
effectiveAmount: mod.amount,
|
|
preApplyProbability: pre,
|
|
postApplyProbability: probability,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply multiplicatives individually so each can be logged.
|
|
for (const { perk, amount } of multiplicativePerks) {
|
|
const pre = probability;
|
|
probability *= 1 + amount;
|
|
applied.push({
|
|
perkKey: perk.key,
|
|
target,
|
|
type: 'multiplicative',
|
|
effectiveAmount: amount,
|
|
preApplyProbability: pre,
|
|
postApplyProbability: probability,
|
|
});
|
|
}
|
|
|
|
return { probability: clamp(probability, P_MIN, P_MAX), applied };
|
|
}
|
|
|
|
function conditionMatches(
|
|
condition: { encounterTags: string[] } | undefined,
|
|
encounterTags: string[]
|
|
): boolean {
|
|
if (!condition) return true;
|
|
return condition.encounterTags.some((t) => encounterTags.includes(t));
|
|
}
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|