Release Healer Man 0.1.45
This commit is contained in:
1 parent
756ee4f64a
commit
f82daf1c13
242 files changed
+1440
-101
No files matched your search
+128
-5
@@ -179,6 +179,7 @@ import {
|
||||
} from "./combatResults";
|
||||
import {
|
||||
DEFAULT_ENEMY_ATTACK,
|
||||
enemyControlPreventsActions,
|
||||
maximumEnemyAttackRange,
|
||||
normalizeEnemyAttacks,
|
||||
type EnemyAttackAnimation,
|
||||
@@ -301,6 +302,7 @@ export interface MobAura {
|
||||
readonly sourceAbilityId: string;
|
||||
readonly endsAt: number;
|
||||
readonly magnitude?: number;
|
||||
readonly lifeStealPercent?: number;
|
||||
/** Persistent driver for a content-authored main-hand auto-attack proc. */
|
||||
readonly meleeAutoCriticalProc?: EnemyMeleeAutoCriticalProc;
|
||||
/** A charged modifier consumed by a successful main-hand auto attack. */
|
||||
@@ -4285,7 +4287,10 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
else if (ability.effects.every((effect) => effect.kind === "scripted" || effect.kind === "unsupported")) failure = "unsupported";
|
||||
else if (!isAbilityUnlocked(ability, state.level)) failure = "level-locked";
|
||||
else if (state.health <= 0) failure = "player-dead";
|
||||
else if (state.controlledUntil > now) failure = "player-controlled";
|
||||
else if (
|
||||
state.controlledUntil > now
|
||||
&& enemyControlPreventsActions(state.controlMechanic)
|
||||
) failure = "player-controlled";
|
||||
else if (
|
||||
effectiveTarget === "hostile"
|
||||
&& abilityAttackKind(state.classId, ability) === "melee"
|
||||
@@ -6904,7 +6909,12 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
|| !enemySpatial
|
||||
|| isPositionInFacingArc(enemySpatial, candidate, Math.PI);
|
||||
});
|
||||
const presentationTargetId = targets[0] ?? resolution.targetActorId;
|
||||
const presentationTargetId = attack.target === "self"
|
||||
? id
|
||||
: targets[0] ?? resolution.targetActorId;
|
||||
const presentationTargetPosition = attack.target === "self"
|
||||
? position
|
||||
: actorPosition(runtimeState, presentationTargetId);
|
||||
const instantSourceCast = !resolvedEnemyCast && attack.spellId !== undefined;
|
||||
emitEnemyAttackPresentation(
|
||||
mob,
|
||||
@@ -6913,10 +6923,14 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
now,
|
||||
position,
|
||||
presentationTargetId,
|
||||
actorPosition(runtimeState, presentationTargetId),
|
||||
presentationTargetPosition,
|
||||
instantSourceCast,
|
||||
);
|
||||
const effects = effect.kind === "composite" ? effect.effects : [effect];
|
||||
let hasDamageResolution = false;
|
||||
for (const effect of effects) {
|
||||
if (effect.kind === "damage") {
|
||||
hasDamageResolution = true;
|
||||
const baselineDamage = Math.max(0, Math.round(mob.attackDamage * effect.multiplier));
|
||||
const attackDamage = attack.runewakerFormula
|
||||
? adaptRunewakerEnemyDamage({
|
||||
@@ -6934,6 +6948,25 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
instantCast: instantSourceCast,
|
||||
});
|
||||
}
|
||||
} else if (effect.kind === "leech") {
|
||||
hasDamageResolution = true;
|
||||
const attackDamage = Math.max(0, Math.round(mob.attackDamage * effect.damageMultiplier));
|
||||
for (const actorId of targets) {
|
||||
incomingHits.push({
|
||||
actorId,
|
||||
amount: attackDamage,
|
||||
mob,
|
||||
attack,
|
||||
attackRevision,
|
||||
instantCast: instantSourceCast,
|
||||
});
|
||||
}
|
||||
if (targets.length) {
|
||||
enemyHealing.push({
|
||||
targetId: id,
|
||||
amount: Math.max(1, Math.round(mob.attackDamage * effect.healMultiplier)),
|
||||
});
|
||||
}
|
||||
} else if (effect.kind === "heal") {
|
||||
const requiredMissingHealth = attack.condition?.kind === "friendly-missing-health"
|
||||
? attack.condition.amount
|
||||
@@ -6975,6 +7008,9 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
sourceAbilityId: attack.id,
|
||||
endsAt,
|
||||
...(effect.magnitude === undefined ? {} : { magnitude: effect.magnitude }),
|
||||
...(effect.lifeStealPercent === undefined
|
||||
? {}
|
||||
: { lifeStealPercent: effect.lifeStealPercent }),
|
||||
...(effect.meleeAutoCriticalProc === undefined
|
||||
? {}
|
||||
: { meleeAutoCriticalProc: effect.meleeAutoCriticalProc }),
|
||||
@@ -6987,6 +7023,29 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
disposition: "buff",
|
||||
durationMs: effect.durationMs > 0 ? effect.durationMs : null,
|
||||
dispelCategory: effect.aura === "enrage" ? "enrage" : "magic",
|
||||
...(effect.damageTakenMultiplier === undefined
|
||||
? {}
|
||||
: {
|
||||
modifiers: [{
|
||||
kind: "damage" as const,
|
||||
direction: "taken" as const,
|
||||
operation: "percent" as const,
|
||||
value: effect.damageTakenMultiplier - 1,
|
||||
...(effect.damageSchool ? { school: effect.damageSchool } : {}),
|
||||
}],
|
||||
}),
|
||||
...(effect.absorbMultiplier === undefined
|
||||
? {}
|
||||
: {
|
||||
absorbs: [{
|
||||
id: `${attack.id}:absorb`,
|
||||
amount: Math.max(1, Math.round(mob.attackDamage * effect.absorbMultiplier)),
|
||||
...(effect.damageSchool ? { school: effect.damageSchool } : {}),
|
||||
}],
|
||||
}),
|
||||
...(effect.reflectSpellChance === undefined
|
||||
? {}
|
||||
: { reflectSpellChance: effect.reflectSpellChance }),
|
||||
},
|
||||
sourceId: id,
|
||||
targetId: id,
|
||||
@@ -7001,6 +7060,17 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
disposition: "debuff",
|
||||
durationMs: effect.durationMs > 0 ? effect.durationMs : null,
|
||||
dispelCategory: "magic",
|
||||
...(effect.damageTakenMultiplier === undefined
|
||||
? {}
|
||||
: {
|
||||
modifiers: [{
|
||||
kind: "damage" as const,
|
||||
direction: "taken" as const,
|
||||
operation: "percent" as const,
|
||||
value: effect.damageTakenMultiplier - 1,
|
||||
...(effect.damageSchool ? { school: effect.damageSchool } : {}),
|
||||
}],
|
||||
}),
|
||||
},
|
||||
sourceId: id,
|
||||
targetId: actorId,
|
||||
@@ -7009,6 +7079,7 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
}
|
||||
} else if (effect.kind === "control") {
|
||||
if ((effect.damageMultiplier ?? 0) > 0) {
|
||||
hasDamageResolution = true;
|
||||
const baselineDamage = Math.max(0, Math.round(mob.attackDamage * effect.damageMultiplier!));
|
||||
const attackDamage = attack.runewakerFormula
|
||||
? adaptRunewakerEnemyDamage({
|
||||
@@ -7036,7 +7107,11 @@ 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.`,
|
||||
description: effect.mechanic === "slow"
|
||||
? `${attack.name} reduces movement speed until the effect ends.`
|
||||
: effect.mechanic === "root"
|
||||
? `${attack.name} prevents movement until the effect ends.`
|
||||
: `${attack.name} inflicts ${effect.mechanic}, preventing normal actions until the effect ends.`,
|
||||
disposition: "debuff",
|
||||
durationMs: effect.durationMs,
|
||||
dispelCategory: "magic",
|
||||
@@ -7055,6 +7130,20 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
endsAt: 0,
|
||||
},
|
||||
];
|
||||
} else if (effect.kind === "pull") {
|
||||
const pulledPosition: CombatPosition = [position[0], position[1], position[2]];
|
||||
for (const actorId of targets) {
|
||||
if (actorId === PLAYER_AGGRO_ID) {
|
||||
set({ playerPosition: pulledPosition });
|
||||
} else {
|
||||
updatePartyRuntimePosition(
|
||||
actorId,
|
||||
pulledPosition[0],
|
||||
pulledPosition[1],
|
||||
pulledPosition[2],
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (effect.kind === "call-for-help") {
|
||||
for (const helperId of nearbyMobRuntimeIds(position, effect.radius)) {
|
||||
const helper = mobs[helperId];
|
||||
@@ -7096,6 +7185,37 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
} else if (effect.kind === "encounter-event") {
|
||||
dispatchDungeonEncounterSignal(effect.eventId, now);
|
||||
}
|
||||
}
|
||||
if (hasDamageResolution && targets.length) {
|
||||
const lifeStealPercent = activeAuras.reduce(
|
||||
(maximum, aura) => Math.max(maximum, aura.lifeStealPercent ?? 0),
|
||||
0,
|
||||
);
|
||||
if (lifeStealPercent > 0) {
|
||||
enemyHealing.push({
|
||||
targetId: id,
|
||||
amount: Math.max(1, Math.round(mob.attackDamage * lifeStealPercent)),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!hasDamageResolution) {
|
||||
const auraResolution = effects.some((entry) => (
|
||||
entry.kind === "aura"
|
||||
|| entry.kind === "target-aura"
|
||||
|| entry.kind === "control"
|
||||
|| entry.kind === "transform"
|
||||
));
|
||||
emitEnemyAttackPresentation(
|
||||
mob,
|
||||
attack,
|
||||
auraResolution ? "aura-start" : "impact",
|
||||
now,
|
||||
position,
|
||||
presentationTargetId,
|
||||
presentationTargetPosition,
|
||||
instantSourceCast,
|
||||
);
|
||||
}
|
||||
attackRevision += 1;
|
||||
abilityCastCounts[attack.id] = (abilityCastCounts[attack.id] ?? 0) + 1;
|
||||
attackAnimation = attack.animation;
|
||||
@@ -7795,7 +7915,10 @@ export const useCombatStore = create<CombatState>((set, get) => ({
|
||||
const autoAttackSource = combatSpatialActor(PLAYER_AGGRO_ID);
|
||||
const canAutoAttack = (
|
||||
!state.activeCast
|
||||
&& state.controlledUntil <= now
|
||||
&& (
|
||||
state.controlledUntil <= now
|
||||
|| !enemyControlPreventsActions(state.controlMechanic)
|
||||
)
|
||||
&& Math.sqrt(mobRuntimeDistanceSquared(autoAttackTargetId!, state.playerPosition)) <= PLAYER_AUTO_ATTACK_RANGE
|
||||
&& Boolean(autoAttackTargetPosition)
|
||||
&& combatHasLineOfSight(state.playerPosition, autoAttackTargetPosition ?? state.playerPosition)
|
||||
|
||||
Reference in new issue
Block a user