forked from phenom/healer-man
Release Healer Man 0.1.9
This commit is contained in:
1 parent
c245a199b6
commit
d060296ade
74 files changed
+5901
-334
No files matched your search
+124
-47
@@ -1,4 +1,5 @@
|
||||
import { create } from "zustand";
|
||||
import { combatNow } from "./combatClock";
|
||||
import { useGameStore } from "./store";
|
||||
import {
|
||||
baseClassFor,
|
||||
@@ -212,6 +213,14 @@ import {
|
||||
normalizeHudPosition,
|
||||
type NormalizedHudPosition,
|
||||
} from "./hudLayout";
|
||||
import {
|
||||
DEFAULT_GAMEPLAY_FRAME_RATE_LIMIT,
|
||||
DEFAULT_PREVIEW_FRAME_RATE_LIMIT,
|
||||
normalizeGameplayFrameRateLimit,
|
||||
normalizePreviewFrameRateLimit,
|
||||
type GameplayFrameRateLimit,
|
||||
type PreviewFrameRateLimit,
|
||||
} from "./frameRateSettings";
|
||||
|
||||
export type MinimapRotation = "north-up" | "player-up";
|
||||
|
||||
@@ -227,6 +236,8 @@ export interface GameplaySettings {
|
||||
readonly masterVolume: number;
|
||||
readonly uiScale: number;
|
||||
readonly minimapRotation: MinimapRotation;
|
||||
readonly gameplayFrameRate: GameplayFrameRateLimit;
|
||||
readonly previewFrameRate: PreviewFrameRateLimit;
|
||||
}
|
||||
|
||||
export const DEFAULT_GAMEPLAY_SETTINGS: GameplaySettings = Object.freeze({
|
||||
@@ -238,9 +249,11 @@ export const DEFAULT_GAMEPLAY_SETTINGS: GameplaySettings = Object.freeze({
|
||||
showFloatingCombatText: true,
|
||||
autoTargetNearest: true,
|
||||
enableScreenShake: true,
|
||||
masterVolume: 0.8,
|
||||
masterVolume: 0,
|
||||
uiScale: 1,
|
||||
minimapRotation: "player-up",
|
||||
gameplayFrameRate: DEFAULT_GAMEPLAY_FRAME_RATE_LIMIT,
|
||||
previewFrameRate: DEFAULT_PREVIEW_FRAME_RATE_LIMIT,
|
||||
});
|
||||
|
||||
/** Prevents a burst of party hits from restarting the same wound clip every frame. */
|
||||
@@ -252,6 +265,8 @@ export const MINIMUM_CORPSE_VISIBLE_MS = 1_500;
|
||||
/** Default proximity pull radii for dungeon enemies, expressed in world units. */
|
||||
export const DEFAULT_MOB_AGGRO_RANGE = 16;
|
||||
export const DEFAULT_BOSS_AGGRO_RANGE = 20;
|
||||
/** Safety ceiling for synchronous proc-to-triggered-spell chains. */
|
||||
const MAX_TRIGGERED_SPELL_CHAIN_DEPTH = 16;
|
||||
|
||||
export interface MobStatus {
|
||||
readonly kind: MobStatusKind;
|
||||
@@ -727,6 +742,8 @@ interface MutableCastWork {
|
||||
gearStats: ItemStats;
|
||||
raceId: RaceId;
|
||||
secondaryClassId: ClassId | null;
|
||||
/** Active synchronous spell/source/target keys used to break proc cycles. */
|
||||
triggeredSpellStack?: string[];
|
||||
}
|
||||
|
||||
let feedbackSequence = 0;
|
||||
@@ -1600,9 +1617,34 @@ function normalizedSettings(
|
||||
minimapRotation: patch.minimapRotation === "north-up" || patch.minimapRotation === "player-up"
|
||||
? patch.minimapRotation
|
||||
: current.minimapRotation,
|
||||
gameplayFrameRate: normalizeGameplayFrameRateLimit(
|
||||
patch.gameplayFrameRate,
|
||||
current.gameplayFrameRate,
|
||||
),
|
||||
previewFrameRate: normalizePreviewFrameRateLimit(
|
||||
patch.previewFrameRate,
|
||||
current.previewFrameRate,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function gameplaySettingsEqual(left: GameplaySettings, right: GameplaySettings): boolean {
|
||||
return left.showMobHealthBars === right.showMobHealthBars
|
||||
&& left.showMobAggroRanges === right.showMobAggroRanges
|
||||
&& left.showTargetFrame === right.showTargetFrame
|
||||
&& left.showThreatMeter === right.showThreatMeter
|
||||
&& left.threatMeterPosition.x === right.threatMeterPosition.x
|
||||
&& left.threatMeterPosition.y === right.threatMeterPosition.y
|
||||
&& left.showFloatingCombatText === right.showFloatingCombatText
|
||||
&& left.autoTargetNearest === right.autoTargetNearest
|
||||
&& left.enableScreenShake === right.enableScreenShake
|
||||
&& left.masterVolume === right.masterVolume
|
||||
&& left.uiScale === right.uiScale
|
||||
&& left.minimapRotation === right.minimapRotation
|
||||
&& left.gameplayFrameRate === right.gameplayFrameRate
|
||||
&& left.previewFrameRate === right.previewFrameRate;
|
||||
}
|
||||
|
||||
function defaultMobStats(
|
||||
playerLevel: number,
|
||||
boss: boolean,
|
||||
@@ -1829,6 +1871,7 @@ function auraDefinitionForAbility(
|
||||
): AuraDefinition {
|
||||
return {
|
||||
...definition,
|
||||
description: definition.description ?? ability.description,
|
||||
icon: definition.icon ?? ability.icon,
|
||||
durationMs,
|
||||
};
|
||||
@@ -1974,15 +2017,30 @@ function executeTriggeredAuraProcsInWork(
|
||||
&& (action.id === "wow335-trigger-spell" || action.id === "rom-trigger-skill")
|
||||
&& typeof action.data?.spellId === "number"
|
||||
) {
|
||||
executeTriggeredSpellInWork(
|
||||
work,
|
||||
action.data.spellId,
|
||||
triggered.sourceId,
|
||||
triggered.event.otherId ?? triggered.targetId,
|
||||
now,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
const spellId = action.data.spellId;
|
||||
const targetId = triggered.event.otherId ?? triggered.targetId;
|
||||
const triggerKey = `${spellId}:${triggered.sourceId}:${targetId}`;
|
||||
const triggeredSpellStack = work.triggeredSpellStack ?? [];
|
||||
if (
|
||||
triggeredSpellStack.length >= MAX_TRIGGERED_SPELL_CHAIN_DEPTH
|
||||
|| triggeredSpellStack.includes(triggerKey)
|
||||
) continue;
|
||||
triggeredSpellStack.push(triggerKey);
|
||||
work.triggeredSpellStack = triggeredSpellStack;
|
||||
try {
|
||||
executeTriggeredSpellInWork(
|
||||
work,
|
||||
spellId,
|
||||
triggered.sourceId,
|
||||
targetId,
|
||||
now,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
} finally {
|
||||
triggeredSpellStack.pop();
|
||||
if (triggeredSpellStack.length === 0) work.triggeredSpellStack = undefined;
|
||||
}
|
||||
} else if (
|
||||
action.kind === "custom"
|
||||
&& action.id === "classic-status"
|
||||
@@ -2140,7 +2198,7 @@ function grantMobExperience(
|
||||
work.raceId,
|
||||
work.secondaryClassId,
|
||||
);
|
||||
work.auras = syncPassiveAuras(work.auras, work.abilities, result.level, work.talentRanks, Date.now());
|
||||
work.auras = syncPassiveAuras(work.auras, work.abilities, result.level, work.talentRanks, combatNow());
|
||||
}
|
||||
return scaledAward;
|
||||
}
|
||||
@@ -2696,6 +2754,8 @@ function executeTriggeredSpellInWork(
|
||||
definition: {
|
||||
id: `coa-triggered-shield:${spellId}`,
|
||||
name: ability.name,
|
||||
description: ability.description,
|
||||
icon: ability.icon,
|
||||
disposition: "buff",
|
||||
durationMs: effect.durationMs,
|
||||
absorbs: [{ id: `coa-triggered-shield:${spellId}:pool`, amount }],
|
||||
@@ -3152,7 +3212,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
resourceName: resource.name,
|
||||
abilities,
|
||||
deathKnightRunes: classId === "death-knight" ? createDeathKnightRunes() : [],
|
||||
auras: syncPassiveAuras([], abilities, progression.level, talentRanks, Date.now()),
|
||||
auras: syncPassiveAuras([], abilities, progression.level, talentRanks, combatNow()),
|
||||
talentRanks,
|
||||
talentModifiers,
|
||||
actionBindings: normalizeActionBindings(profile.actionBindings, defaultActionBarForCharacter(classId, secondaryClassId, progression.level)),
|
||||
@@ -3338,7 +3398,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
};
|
||||
}),
|
||||
|
||||
lootMob: (id, now = Date.now()) => {
|
||||
lootMob: (id, now = combatNow()) => {
|
||||
const mob = get().mobs[id];
|
||||
if (!mob || !mob.dead || !mob.hasLoot || mob.looted || mob.despawned) return false;
|
||||
const corpseDespawnAt = Math.max(now, (mob.diedAt ?? now) + MINIMUM_CORPSE_VISIBLE_MS);
|
||||
@@ -3562,12 +3622,18 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
const movedWhileCasting = Boolean(cast && !cast.canCastWhileMoving && (
|
||||
(next[0] - cast.origin[0]) ** 2 + (next[2] - cast.origin[2]) ** 2 > 0.04
|
||||
));
|
||||
if (movedWhileCasting) emitPlayerAbilityCancellation(cast!.abilityId, Date.now(), next);
|
||||
const positionUnchanged = (
|
||||
(next[0] - state.playerPosition[0]) ** 2
|
||||
+ (next[1] - state.playerPosition[1]) ** 2
|
||||
+ (next[2] - state.playerPosition[2]) ** 2
|
||||
) <= 1e-8;
|
||||
if (positionUnchanged && !movedWhileCasting) return state;
|
||||
if (movedWhileCasting) emitPlayerAbilityCancellation(cast!.abilityId, combatNow(), next);
|
||||
return {
|
||||
playerPosition: next,
|
||||
activeCast: movedWhileCasting ? null : cast,
|
||||
combatEvents: movedWhileCasting ? recordCombatEvent(state.combatEvents, {
|
||||
occurredAt: Date.now(),
|
||||
occurredAt: combatNow(),
|
||||
kind: "interrupt",
|
||||
sourceActorId: PLAYER_AURA_ENTITY_ID,
|
||||
targetActorId: PLAYER_AURA_ENTITY_ID,
|
||||
@@ -3648,7 +3714,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
};
|
||||
}),
|
||||
|
||||
confirmGroundTarget: (position, now = Date.now()) => {
|
||||
confirmGroundTarget: (position, now = combatNow()) => {
|
||||
const pending = get().pendingGroundTarget;
|
||||
if (!pending) return emptyCastResult("invalid-destination");
|
||||
const destination = position ?? pending.position;
|
||||
@@ -3691,7 +3757,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
}));
|
||||
},
|
||||
|
||||
castAbility: (abilityId, requestOrOrigin, legacyNow = Date.now(), legacyCompletingCast) => {
|
||||
castAbility: (abilityId, requestOrOrigin, legacyNow = combatNow(), legacyCompletingCast) => {
|
||||
const request: CastRequest = Array.isArray(requestOrOrigin)
|
||||
? { origin: requestOrOrigin as CombatPosition, now: legacyNow, completingCast: legacyCompletingCast }
|
||||
: (requestOrOrigin as CastRequest | undefined) ?? { now: legacyNow, completingCast: legacyCompletingCast };
|
||||
@@ -4425,6 +4491,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
definition: {
|
||||
id: `shield:${ability.id}`,
|
||||
name: ability.name,
|
||||
description: ability.description,
|
||||
icon: ability.icon,
|
||||
disposition: "buff",
|
||||
durationMs,
|
||||
@@ -5090,7 +5157,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return get().castAbility(abilityId, origin, now);
|
||||
},
|
||||
|
||||
damageMob: (id, amount, now = Date.now(), source, school = "physical", rollContext) => {
|
||||
damageMob: (id, amount, now = combatNow(), source, school = "physical", rollContext) => {
|
||||
const state = get();
|
||||
const threatSource = source ?? playerThreatSource(state);
|
||||
const progression = progressionFromState(state);
|
||||
@@ -5206,7 +5273,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return { mobs };
|
||||
}),
|
||||
|
||||
tauntMob: (id, source, durationMs, now = Date.now()) => {
|
||||
tauntMob: (id, source, durationMs, now = combatNow()) => {
|
||||
const target = get().mobs[id];
|
||||
if (!target || target.dead || durationMs <= 0) return false;
|
||||
const previous = target.tauntDiminishingReturns;
|
||||
@@ -5257,7 +5324,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return effectiveDurationMs > 0;
|
||||
},
|
||||
|
||||
interruptMob: (id, sourceAbilityId, lockoutMs, now = Date.now()) => {
|
||||
interruptMob: (id, sourceAbilityId, lockoutMs, now = combatNow()) => {
|
||||
let interrupted = false;
|
||||
set((state) => {
|
||||
const mob = state.mobs[id];
|
||||
@@ -5273,7 +5340,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return interrupted;
|
||||
},
|
||||
|
||||
controlMob: (id, sourceAbilityId, status, durationMs, now = Date.now()) => {
|
||||
controlMob: (id, sourceAbilityId, status, durationMs, now = combatNow()) => {
|
||||
let controlled = false;
|
||||
set((state) => {
|
||||
const mob = state.mobs[id];
|
||||
@@ -5331,7 +5398,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
},
|
||||
|
||||
healActor: (id, amount, request) => {
|
||||
const now = request.now ?? Date.now();
|
||||
const now = request.now ?? combatNow();
|
||||
const state = get();
|
||||
const sourceAuras = aurasForEntity(state.auras, request.sourceActorId);
|
||||
const targetAuras = aurasForEntity(state.auras, id);
|
||||
@@ -5414,7 +5481,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return resolution;
|
||||
},
|
||||
|
||||
reviveActor: (id, percentMaxHealth, sourceActorId, abilityId, now = Date.now()) => {
|
||||
reviveActor: (id, percentMaxHealth, sourceActorId, abilityId, now = combatNow()) => {
|
||||
const state = get();
|
||||
const fraction = clamp(percentMaxHealth, 0.01, 1);
|
||||
if (id === PLAYER_AURA_ENTITY_ID) {
|
||||
@@ -5494,7 +5561,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return restored;
|
||||
},
|
||||
|
||||
restoreActorResource: (id, resource, amount, percentage = false, sourceActorId = id, abilityId, now = Date.now()) => {
|
||||
restoreActorResource: (id, resource, amount, percentage = false, sourceActorId = id, abilityId, now = combatNow()) => {
|
||||
const state = get();
|
||||
const actor = state.actors[id];
|
||||
if (!actor) return 0;
|
||||
@@ -5533,7 +5600,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return restored;
|
||||
},
|
||||
|
||||
shieldActor: (id, amount, durationMs, sourceActorId, ability, now = Date.now()) => {
|
||||
shieldActor: (id, amount, durationMs, sourceActorId, ability, now = combatNow()) => {
|
||||
const state = get();
|
||||
const absorb = Math.max(0, Math.round(amount));
|
||||
if (absorb <= 0 || !state.actors[id]) return 0;
|
||||
@@ -5541,6 +5608,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
definition: {
|
||||
id: `shield:${sourceActorId}:${ability.id}`,
|
||||
name: ability.name,
|
||||
description: ability.description,
|
||||
icon: ability.icon,
|
||||
disposition: "buff",
|
||||
durationMs: Math.max(1, durationMs),
|
||||
@@ -5570,7 +5638,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return absorb;
|
||||
},
|
||||
|
||||
applyActorAura: (sourceActorId, targetActorId, ability, effect, now = Date.now()) => {
|
||||
applyActorAura: (sourceActorId, targetActorId, ability, effect, now = combatNow()) => {
|
||||
const state = get();
|
||||
if (!state.actors[sourceActorId] || (!state.actors[targetActorId] && !state.mobs[targetActorId])) return false;
|
||||
const durationMs = effect.aura.durationMs;
|
||||
@@ -5600,7 +5668,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return true;
|
||||
},
|
||||
|
||||
applyTimedActorEffect: (sourceActorId, targetActorId, ability, effect, now = Date.now()) => {
|
||||
applyTimedActorEffect: (sourceActorId, targetActorId, ability, effect, now = combatNow()) => {
|
||||
const state = get();
|
||||
if (!state.actors[sourceActorId]) return false;
|
||||
if (effect.kind === "dot" && (!state.mobs[targetActorId] || state.mobs[targetActorId].dead)) return false;
|
||||
@@ -5637,7 +5705,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return true;
|
||||
},
|
||||
|
||||
summonActor: (ownerActorId, ability, effect, position, targetId = null, now = Date.now()) => {
|
||||
summonActor: (ownerActorId, ability, effect, position, targetId = null, now = combatNow()) => {
|
||||
const state = get();
|
||||
const owner = state.actors[ownerActorId];
|
||||
if (!owner?.alive) return null;
|
||||
@@ -5671,7 +5739,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return id;
|
||||
},
|
||||
|
||||
createActorAreaEffect: (sourceActorId, ability, position, now = Date.now()) => {
|
||||
createActorAreaEffect: (sourceActorId, ability, position, now = combatNow()) => {
|
||||
const state = get();
|
||||
const sourceActor = state.actors[sourceActorId];
|
||||
if (!sourceActor?.alive) return null;
|
||||
@@ -5716,7 +5784,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
|
||||
damagePlayer: (amount, school = "physical", attackerLevel) => {
|
||||
const state = get();
|
||||
const now = Date.now();
|
||||
const now = combatNow();
|
||||
const mitigated = damageAfterEquipmentMitigation(
|
||||
talentAdjustedIncomingDamage(amount, state.talentModifiers),
|
||||
state.gearStats,
|
||||
@@ -5775,6 +5843,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
definition: {
|
||||
id: "rom-state-revival-charge",
|
||||
name: "Revival",
|
||||
description: "Each stack makes Revival restore 5% of maximum health. Casting Revival consumes every stack.",
|
||||
disposition: "buff",
|
||||
durationMs: null,
|
||||
maxStacks: 10,
|
||||
@@ -5831,7 +5900,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
targetId: id,
|
||||
amount: requested,
|
||||
school: "physical",
|
||||
now: Date.now(),
|
||||
now: combatNow(),
|
||||
});
|
||||
applied = Math.min(summon.health, Math.max(0, Math.round(absorbed.remainingDamage)));
|
||||
const health = summon.health - applied;
|
||||
@@ -5846,7 +5915,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
summons,
|
||||
actors,
|
||||
combatEvents: recordCombatEvent(state.combatEvents, {
|
||||
occurredAt: Date.now(),
|
||||
occurredAt: combatNow(),
|
||||
kind: "damage",
|
||||
sourceActorId: "external",
|
||||
targetActorId: id,
|
||||
@@ -5878,12 +5947,12 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
subjectId: PLAYER_AURA_ENTITY_ID,
|
||||
otherId: PLAYER_AURA_ENTITY_ID,
|
||||
amount: healed,
|
||||
}, Date.now(), ({ aura, proc }) => stableCombatUnit(`${aura.instanceId}:${proc.id}:heal:${state.health}`));
|
||||
}, combatNow(), ({ aura, proc }) => stableCombatUnit(`${aura.instanceId}:${proc.id}:heal:${state.health}`));
|
||||
set({
|
||||
auras: procResult.auras,
|
||||
health: state.health + healed,
|
||||
combatEvents: recordCombatEvent(state.combatEvents, {
|
||||
occurredAt: Date.now(),
|
||||
occurredAt: combatNow(),
|
||||
kind: "healing",
|
||||
sourceActorId: PLAYER_AURA_ENTITY_ID,
|
||||
targetActorId: PLAYER_AURA_ENTITY_ID,
|
||||
@@ -5920,7 +5989,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
set((state) => ({
|
||||
activeCast: null,
|
||||
combatEvents: recordCombatEvent(state.combatEvents, {
|
||||
occurredAt: Date.now(),
|
||||
occurredAt: combatNow(),
|
||||
kind: "interrupt",
|
||||
sourceActorId: PLAYER_AURA_ENTITY_ID,
|
||||
targetActorId: PLAYER_AURA_ENTITY_ID,
|
||||
@@ -5936,11 +6005,11 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
playerAnimationEvent: nextAnimationEvent("ability-cancel", cast.abilityId),
|
||||
feedback: nextFeedback("error", message, { abilityId: cast.abilityId, targetId: cast.targetId ?? undefined }),
|
||||
}));
|
||||
emitPlayerAbilityCancellation(cast.abilityId, Date.now(), get().playerPosition);
|
||||
emitPlayerAbilityCancellation(cast.abilityId, combatNow(), get().playerPosition);
|
||||
return true;
|
||||
},
|
||||
|
||||
engageNearbyMobs: (now = Date.now(), playerPosition) => {
|
||||
engageNearbyMobs: (now = combatNow(), playerPosition) => {
|
||||
const state = get();
|
||||
const runtimeState = playerPosition
|
||||
? { ...state, playerPosition }
|
||||
@@ -5960,7 +6029,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return engagedGroups;
|
||||
},
|
||||
|
||||
advanceMobCombat: (now = Date.now(), playerPosition) => {
|
||||
advanceMobCombat: (now = combatNow(), playerPosition) => {
|
||||
const state = get();
|
||||
const runtimeState = playerPosition
|
||||
? { ...state, playerPosition }
|
||||
@@ -6237,6 +6306,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
definition: {
|
||||
id: `enemy-control:${id}:${attack.id}:${effect.mechanic}`,
|
||||
name: `${attack.name}: ${effect.mechanic}`,
|
||||
description: `${attack.name} inflicts ${effect.mechanic}, preventing normal actions until the effect ends.`,
|
||||
disposition: "debuff",
|
||||
durationMs: effect.durationMs,
|
||||
dispelCategory: "magic",
|
||||
@@ -6828,7 +6898,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
return appliedDamage;
|
||||
},
|
||||
|
||||
tick: (deltaSeconds, now = Date.now()) => {
|
||||
tick: (deltaSeconds, now = combatNow()) => {
|
||||
const partyHotTicks: Array<{
|
||||
memberId: string;
|
||||
amount: number;
|
||||
@@ -7687,7 +7757,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
selectedTargetId: null,
|
||||
selectedFriendlyActorId: null,
|
||||
effects: [],
|
||||
auras: syncPassiveAuras([], state.abilities, state.level, state.talentRanks, Date.now()),
|
||||
auras: syncPassiveAuras([], state.abilities, state.level, state.talentRanks, combatNow()),
|
||||
summons: [],
|
||||
areaEffects: [],
|
||||
pendingGroundTarget: null,
|
||||
@@ -7746,7 +7816,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
health: clamp(state.maxHealth > 0 ? state.health / state.maxHealth * maxHealth : maxHealth, 0, maxHealth),
|
||||
maxResource,
|
||||
resource: clamp(state.maxResource > 0 ? state.resource / state.maxResource * maxResource : 0, 0, maxResource),
|
||||
auras: syncPassiveAuras(state.auras, state.abilities, state.level, result.ranks, Date.now()),
|
||||
auras: syncPassiveAuras(state.auras, state.abilities, state.level, result.ranks, combatNow()),
|
||||
feedback: nextFeedback("talent", "Conquest node rank allocated."),
|
||||
});
|
||||
return true;
|
||||
@@ -7778,7 +7848,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
health: clamp(state.maxHealth > 0 ? state.health / state.maxHealth * maxHealth : maxHealth, 0, maxHealth),
|
||||
maxResource,
|
||||
resource: clamp(state.maxResource > 0 ? state.resource / state.maxResource * maxResource : 0, 0, maxResource),
|
||||
auras: syncPassiveAuras(state.auras, state.abilities, state.level, talentRanks, Date.now()),
|
||||
auras: syncPassiveAuras(state.auras, state.abilities, state.level, talentRanks, combatNow()),
|
||||
feedback: nextFeedback("talent", "Talent points reset."),
|
||||
};
|
||||
}),
|
||||
@@ -7804,8 +7874,15 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
),
|
||||
})),
|
||||
|
||||
updateSettings: (patch) => set((state) => ({ settings: normalizedSettings(state.settings, patch) })),
|
||||
resetSettings: () => set({ settings: DEFAULT_GAMEPLAY_SETTINGS }),
|
||||
updateSettings: (patch) => set((state) => {
|
||||
const settings = normalizedSettings(state.settings, patch);
|
||||
return gameplaySettingsEqual(settings, state.settings) ? state : { settings };
|
||||
}),
|
||||
resetSettings: () => set((state) => (
|
||||
gameplaySettingsEqual(state.settings, DEFAULT_GAMEPLAY_SETTINGS)
|
||||
? state
|
||||
: { settings: DEFAULT_GAMEPLAY_SETTINGS }
|
||||
)),
|
||||
}));
|
||||
|
||||
export function selectedMob(state: CombatState): MobCombatState | null {
|
||||
@@ -7815,14 +7892,14 @@ export function selectedMob(state: CombatState): MobCombatState | null {
|
||||
export function cooldownRemainingMs(
|
||||
state: Pick<CombatState, "cooldowns">,
|
||||
abilityId: string,
|
||||
now = Date.now(),
|
||||
now = combatNow(),
|
||||
): number {
|
||||
return Math.max(0, (state.cooldowns[abilityId] ?? 0) - now);
|
||||
}
|
||||
|
||||
export function globalCooldownRemainingMs(
|
||||
state: Pick<CombatState, "globalCooldownEndsAt">,
|
||||
now = Date.now(),
|
||||
now = combatNow(),
|
||||
): number {
|
||||
return Math.max(0, state.globalCooldownEndsAt - now);
|
||||
}
|
||||
@@ -7842,7 +7919,7 @@ useCombatStore.subscribe((state) => {
|
||||
collectingBossLoot = true;
|
||||
try {
|
||||
for (const boss of lootableBosses) {
|
||||
useCombatStore.getState().lootMob(boss.id, boss.diedAt ?? Date.now());
|
||||
useCombatStore.getState().lootMob(boss.id, boss.diedAt ?? combatNow());
|
||||
}
|
||||
} finally {
|
||||
collectingBossLoot = false;
|
||||
|
||||
Reference in new issue
Block a user