This commit is contained in:
105
src/hooks/combat/skillEffects.ts
Normal file
105
src/hooks/combat/skillEffects.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
getCharacterAnimationDurationMs,
|
||||
getSequenceDurationMs,
|
||||
getSkillCasterAnimation,
|
||||
getSkillDelivery,
|
||||
resolveSequenceFrames,
|
||||
} from '../../data/characterCombat';
|
||||
import type {
|
||||
Character,
|
||||
CharacterSkillDefinition,
|
||||
CombatVisualEffect,
|
||||
} from '../../types';
|
||||
|
||||
const RANGED_MONSTER_FOOT_LOCK_OFFSET_Y = -56;
|
||||
|
||||
function createCombatEffectId() {
|
||||
return `combat-effect-${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
|
||||
export function getSkillReleaseDelayMs(character: Character, skill: CharacterSkillDefinition) {
|
||||
if (typeof skill.releaseDelayMs === 'number') return skill.releaseDelayMs;
|
||||
const animationDuration = getCharacterAnimationDurationMs(character, getSkillCasterAnimation(skill));
|
||||
return Math.min(260, Math.max(120, Math.round(animationDuration * 0.45)));
|
||||
}
|
||||
|
||||
export function buildSkillEffects(
|
||||
attacker: {
|
||||
character: Character;
|
||||
xMeters: number;
|
||||
origin: 'player' | 'monster';
|
||||
facing: 'left' | 'right';
|
||||
monsterId?: string;
|
||||
},
|
||||
target: {
|
||||
xMeters: number;
|
||||
origin: 'player' | 'monster';
|
||||
monsterId?: string;
|
||||
},
|
||||
skill: CharacterSkillDefinition,
|
||||
) {
|
||||
const phases = {
|
||||
cast: [] as CombatVisualEffect[],
|
||||
travel: [] as CombatVisualEffect[],
|
||||
impact: [] as CombatVisualEffect[],
|
||||
castDurationMs: 0,
|
||||
travelDurationMs: 0,
|
||||
impactDurationMs: 0,
|
||||
};
|
||||
|
||||
const deliveryRanged = getSkillDelivery(skill) === 'ranged';
|
||||
|
||||
for (const effect of skill.effects ?? []) {
|
||||
const frames = resolveSequenceFrames(attacker.character, effect.sequence);
|
||||
if (frames.length === 0) continue;
|
||||
|
||||
const durationMs = effect.durationMs ?? getSequenceDurationMs(effect.sequence, frames.length);
|
||||
const origin = effect.anchor === 'target' ? target : attacker;
|
||||
const isProjectile =
|
||||
effect.motion === 'projectile'
|
||||
|| (effect.phase === 'travel' && deliveryRanged);
|
||||
const fallbackProjectileStartOffsetX = attacker.facing === 'right' ? 18 : -18;
|
||||
const startOffsetX = effect.startOffsetX ?? (isProjectile ? fallbackProjectileStartOffsetX : 0);
|
||||
const endOffsetX = effect.endOffsetX ?? (isProjectile ? 0 : startOffsetX);
|
||||
const startAnchorOffsetY = !isProjectile
|
||||
&& deliveryRanged
|
||||
&& origin.origin === 'monster'
|
||||
? RANGED_MONSTER_FOOT_LOCK_OFFSET_Y
|
||||
: 0;
|
||||
const endAnchorOffsetY = isProjectile
|
||||
&& deliveryRanged
|
||||
&& target.origin === 'monster'
|
||||
? RANGED_MONSTER_FOOT_LOCK_OFFSET_Y
|
||||
: startAnchorOffsetY;
|
||||
const instance: CombatVisualEffect = {
|
||||
id: createCombatEffectId(),
|
||||
frames,
|
||||
fps: effect.sequence.fps ?? 10,
|
||||
startX: isProjectile ? attacker.xMeters : origin.xMeters,
|
||||
endX: isProjectile ? target.xMeters : origin.xMeters,
|
||||
startOrigin: isProjectile ? attacker.origin : origin.origin,
|
||||
endOrigin: isProjectile ? target.origin : origin.origin,
|
||||
startMonsterId: isProjectile ? attacker.monsterId : origin.monsterId,
|
||||
endMonsterId: isProjectile ? target.monsterId : origin.monsterId,
|
||||
startAnchorOffsetY,
|
||||
endAnchorOffsetY,
|
||||
startOffsetX,
|
||||
endOffsetX,
|
||||
startYOffset: effect.startYOffset ?? 56,
|
||||
endYOffset: effect.endYOffset ?? effect.startYOffset ?? 56,
|
||||
durationMs,
|
||||
sizePx: effect.sizePx ?? 96,
|
||||
scale: effect.scale ?? 1,
|
||||
facing: attacker.facing,
|
||||
zIndex: effect.phase === 'impact' ? 28 : 24,
|
||||
traveling: isProjectile,
|
||||
};
|
||||
|
||||
phases[effect.phase].push(instance);
|
||||
if (effect.phase === 'cast') phases.castDurationMs = Math.max(phases.castDurationMs, durationMs);
|
||||
if (effect.phase === 'travel') phases.travelDurationMs = Math.max(phases.travelDurationMs, durationMs);
|
||||
if (effect.phase === 'impact') phases.impactDurationMs = Math.max(phases.impactDurationMs, durationMs);
|
||||
}
|
||||
|
||||
return phases;
|
||||
}
|
||||
Reference in New Issue
Block a user