Stage 5: Add Prisma integration and enhance mission management
- 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.
This commit is contained in:
101
libs/mission-logic/src/lib/perk-math.ts
Normal file
101
libs/mission-logic/src/lib/perk-math.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user