Release Healer Man 0.1.40
This commit is contained in:
1 parent
ed0d2fea3d
commit
197a64117c
215 files changed
+5131
-181
No files matched your search
+70
-16
@@ -128,6 +128,8 @@ import {
|
||||
type TriggeredAuraProc,
|
||||
} from "./combatAuras";
|
||||
import { rollBossLoot } from "./lootCatalog";
|
||||
import { bossCardForSource } from "./bossCardCatalog";
|
||||
import { collectBossCardDrop, emptyBossCardCollection, normalizeBossCardCollection, rollBossCards, type BossCardCollection, type BossCardDrop } from "./bossCards";
|
||||
import { adaptRunewakerEnemyDamage } from "./runewakerCombatFormula";
|
||||
import { channelAmountEffects } from "./combatChannelAmounts";
|
||||
import { normalizeInventory, type InventoryItem } from "./lootTypes";
|
||||
@@ -357,6 +359,7 @@ export interface MobCombatState {
|
||||
readonly dead: boolean;
|
||||
readonly hasLoot: boolean;
|
||||
readonly pendingLoot: readonly InventoryItem[];
|
||||
readonly bossCardDrop?: BossCardDrop;
|
||||
readonly looted: boolean;
|
||||
readonly despawned: boolean;
|
||||
readonly diedAt: number | null;
|
||||
@@ -454,6 +457,7 @@ export interface BossLootReward {
|
||||
readonly bossId: string;
|
||||
readonly bossName: string;
|
||||
readonly items: readonly InventoryItem[];
|
||||
readonly cards?: BossCardDrop;
|
||||
readonly acquiredAt: number;
|
||||
}
|
||||
|
||||
@@ -525,6 +529,7 @@ export interface CharacterCombatSeed {
|
||||
readonly talentRanks?: TalentRanks;
|
||||
readonly actionBindings?: unknown;
|
||||
readonly inventory?: unknown;
|
||||
readonly bossCards?: unknown;
|
||||
readonly equipment?: unknown;
|
||||
readonly settings?: Partial<GameplaySettings>;
|
||||
}
|
||||
@@ -667,6 +672,8 @@ export interface CombatState {
|
||||
talentModifiers: readonly TalentModifier[];
|
||||
actionBindings: ActionBindings;
|
||||
inventory: readonly InventoryItem[];
|
||||
bossCards: BossCardCollection;
|
||||
collectSharedBossCards: (drop: BossCardDrop, bossName: string, now: number) => void;
|
||||
equipment: EquipmentAssignments;
|
||||
gearStats: ItemStats;
|
||||
settings: GameplaySettings;
|
||||
@@ -785,6 +792,7 @@ interface MutableCastWork {
|
||||
secondaryClassId: ClassId | null;
|
||||
/** Active synchronous spell/source/target keys used to break proc cycles. */
|
||||
triggeredSpellStack?: string[];
|
||||
triggeredProcKeys?: Set<string>;
|
||||
}
|
||||
|
||||
let feedbackSequence = 0;
|
||||
@@ -1336,6 +1344,12 @@ function resolvePlayerDamageResult(
|
||||
const attacker: CombatAttackerStats = {
|
||||
...baseAttacker,
|
||||
criticalChancePercent: (baseAttacker.criticalChancePercent ?? 0) + criticalChanceFromTalents,
|
||||
criticalDamageMultiplier: applyTalentModifierValue(
|
||||
2,
|
||||
work.talentModifiers,
|
||||
"critical-damage-percent",
|
||||
ability ? abilityTalentContext(ability, periodic) : {},
|
||||
),
|
||||
};
|
||||
const immune = mob.schoolImmunities.includes(school);
|
||||
return resolveCombatResult({
|
||||
@@ -2280,17 +2294,29 @@ function evaluateProcsInWork(
|
||||
event: ProcEvent,
|
||||
now: number,
|
||||
): readonly TriggeredAuraProc[] {
|
||||
const result = evaluateAuraProcs(
|
||||
work.auras,
|
||||
event,
|
||||
now,
|
||||
({ aura, proc }) => stableCombatUnit(
|
||||
`${aura.instanceId}:${proc.id}:${event.trigger}:${event.abilityId ?? ""}:${event.otherId ?? ""}:${now}`,
|
||||
),
|
||||
);
|
||||
work.auras = [...result.auras];
|
||||
executeTriggeredAuraProcsInWork(work, result.triggered, now);
|
||||
return result.triggered;
|
||||
const root = work.triggeredProcKeys === undefined;
|
||||
const triggeredKeys = work.triggeredProcKeys ?? new Set<string>();
|
||||
work.triggeredProcKeys = triggeredKeys;
|
||||
try {
|
||||
const result = evaluateAuraProcs(
|
||||
work.auras,
|
||||
event,
|
||||
now,
|
||||
({ aura, proc }) => triggeredKeys.has(`${aura.instanceId}:${proc.id}`)
|
||||
? 1
|
||||
: stableCombatUnit(
|
||||
`${aura.instanceId}:${proc.id}:${event.trigger}:${event.abilityId ?? ""}:${event.otherId ?? ""}:${now}`,
|
||||
),
|
||||
);
|
||||
// A proc can activate once for the originating event. Its damage must not
|
||||
// reactivate it through a different branch of the same triggered chain.
|
||||
for (const proc of result.triggered) triggeredKeys.add(`${proc.auraInstanceId}:${proc.procId}`);
|
||||
work.auras = [...result.auras];
|
||||
executeTriggeredAuraProcsInWork(work, result.triggered, now);
|
||||
return result.triggered;
|
||||
} finally {
|
||||
if (root) work.triggeredProcKeys = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function eligibleTargetIds(state: CombatState, origin: CombatPosition, maxRange: number): string[] {
|
||||
@@ -2600,7 +2626,16 @@ function damageMobInWork(
|
||||
bonusItemChance: mob.bonusLootChance,
|
||||
})
|
||||
: mob.pendingLoot;
|
||||
const hasLoot = killed ? mob.hasLoot || pendingLoot.length > 0 : mob.hasLoot;
|
||||
const card = killed && mob.boss
|
||||
? bossCardForSource(mob.lootSourceId ?? mob.id, useGameStore.getState().activeDungeonId, mob.name)
|
||||
: null;
|
||||
const bossCardDrop = card ? {
|
||||
bossKey: card.key,
|
||||
spawnId: mob.id,
|
||||
claimId: `${now}:${mob.deathRevision + 1}:${mob.id}`,
|
||||
rarities: rollBossCards(),
|
||||
} satisfies BossCardDrop : mob.bossCardDrop;
|
||||
const hasLoot = killed ? mob.hasLoot || pendingLoot.length > 0 || Boolean(bossCardDrop) : mob.hasLoot;
|
||||
const xpAwarded = canAward ? grantMobExperience(classId, work, mob.xpReward) : 0;
|
||||
engageMobGroupInWork(work, id, now, source.actorId);
|
||||
const engagedMob = work.mobs[id] ?? mob;
|
||||
@@ -2618,6 +2653,7 @@ function damageMobInWork(
|
||||
dead: killed,
|
||||
hasLoot,
|
||||
pendingLoot,
|
||||
bossCardDrop,
|
||||
looted: killed ? false : mob.looted,
|
||||
despawned: false,
|
||||
diedAt: killed ? now : mob.diedAt,
|
||||
@@ -3025,7 +3061,7 @@ function executeTriggeredSpellInWork(
|
||||
} else if (effect.kind === "execute") {
|
||||
for (const mobId of mobTargets) {
|
||||
const mob = work.mobs[mobId];
|
||||
if (mob) damageMobInWork(
|
||||
if (mob && !mob.boss) damageMobInWork(
|
||||
work.classId,
|
||||
work,
|
||||
mobId,
|
||||
@@ -3064,7 +3100,7 @@ function executeTriggeredSpellInWork(
|
||||
work.cooldowns[candidate.id] = Math.max(now, readyAt - reduction);
|
||||
}
|
||||
}
|
||||
} else if (effect.kind === "trigger-spell" && depth < 1 && effect.spellId !== spellId) {
|
||||
} else if (effect.kind === "trigger-spell" && !effect.expanded && depth < 1 && effect.spellId !== spellId) {
|
||||
executeTriggeredSpellInWork(work, effect.spellId, sourceId, targetId, now, depth + 1, allowCoaHidden);
|
||||
}
|
||||
}
|
||||
@@ -3409,6 +3445,7 @@ function initialState() {
|
||||
talentModifiers,
|
||||
actionBindings: defaultActionBindings(defaultActionBarForClass(classId)),
|
||||
inventory: [] as readonly InventoryItem[],
|
||||
bossCards: emptyBossCardCollection(),
|
||||
equipment: {} as EquipmentAssignments,
|
||||
gearStats: EMPTY_ITEM_STATS,
|
||||
settings: DEFAULT_GAMEPLAY_SETTINGS,
|
||||
@@ -3481,6 +3518,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
talentModifiers,
|
||||
actionBindings: normalizeActionBindings(profile.actionBindings, defaultActionBarForCharacter(classId, secondaryClassId, progression.level)),
|
||||
inventory,
|
||||
bossCards: normalizeBossCardCollection(profile.bossCards),
|
||||
equipment,
|
||||
gearStats,
|
||||
settings: normalizedSettings(DEFAULT_GAMEPLAY_SETTINGS, profile.settings ?? {}),
|
||||
@@ -3717,11 +3755,13 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
bossId: id,
|
||||
bossName: mob.name,
|
||||
items: mob.pendingLoot,
|
||||
...(mob.bossCardDrop ? { cards: mob.bossCardDrop } : {}),
|
||||
acquiredAt: now,
|
||||
} satisfies BossLootReward
|
||||
: null;
|
||||
return {
|
||||
inventory: [...state.inventory, ...mob.pendingLoot],
|
||||
bossCards: mob.bossCardDrop ? collectBossCardDrop(state.bossCards, mob.bossCardDrop) : state.bossCards,
|
||||
mobs: {
|
||||
...state.mobs,
|
||||
[id]: {
|
||||
@@ -3745,6 +3785,17 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
: state.bossLootRewards.filter((reward) => reward.id !== id),
|
||||
})),
|
||||
|
||||
collectSharedBossCards: (drop, bossName, now) => set((state) => {
|
||||
const bossCards = collectBossCardDrop(state.bossCards, drop);
|
||||
if (bossCards === state.bossCards) return state;
|
||||
return {
|
||||
bossCards,
|
||||
bossLootRewards: [...state.bossLootRewards, {
|
||||
id: ++bossLootRewardSequence, bossId: drop.spawnId, bossName, items: [], cards: drop, acquiredAt: now,
|
||||
}],
|
||||
};
|
||||
}),
|
||||
|
||||
equipItem: (instanceId, ownerId = PLAYER_EQUIPMENT_OWNER_ID) => {
|
||||
const state = get();
|
||||
const ownerClassId = ownerId === PLAYER_EQUIPMENT_OWNER_ID
|
||||
@@ -5166,7 +5217,9 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
} else if (effect.kind === "execute") {
|
||||
for (const id of affectedMobIds) {
|
||||
const mob = work.mobs[id];
|
||||
if (!mob || mob.dead) continue;
|
||||
// DBC instant-kill effects include pet sacrifices such as Death Pact.
|
||||
// They must never bypass a boss encounter's health and mechanics.
|
||||
if (!mob || mob.dead || mob.boss) continue;
|
||||
damageMobInWork(
|
||||
state.classId,
|
||||
work,
|
||||
@@ -5320,7 +5373,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
} else if (effect.kind === "cooldown-reset") {
|
||||
cooldownAdjustments.push(effect);
|
||||
} else if (effect.kind === "trigger-spell") {
|
||||
if (effect.spellId !== ability.dbcSpellId) {
|
||||
if (!effect.expanded && effect.spellId !== ability.dbcSpellId) {
|
||||
executeTriggeredSpellInWork(
|
||||
work,
|
||||
effect.spellId,
|
||||
@@ -8359,6 +8412,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
health: preserveDefeated ? 0 : mob.maxHealth,
|
||||
dead: preserveDefeated,
|
||||
pendingLoot: preserveDefeated ? mob.pendingLoot : [],
|
||||
bossCardDrop: preserveDefeated ? mob.bossCardDrop : undefined,
|
||||
looted: preserveDefeated ? mob.looted : false,
|
||||
despawned: preserveDefeated ? mob.despawned : false,
|
||||
diedAt: preserveDefeated ? mob.diedAt : null,
|
||||
|
||||
Reference in new issue
Block a user