feat: implement fighter separation logic and optimize special effect casting
This commit is contained in:
parent
23376e8cbb
commit
3b1a883787
|
|
@ -76,7 +76,7 @@ export const FIGHTER = {
|
|||
TYPE: ["melee", "magic"],
|
||||
STACK_SIZE: 100,
|
||||
VISUAL_SCALE_MULTIPLIER: 5,
|
||||
ATTACK_EFFECT_SCALE_MULTIPLIER: 5,
|
||||
ATTACK_EFFECT_SCALE_MULTIPLIER: 2,
|
||||
HP_BONUS_RATIO: 2,
|
||||
ATTACK_RANGE_MULTIPLIER: 1.5,
|
||||
ATTACK_DAMAGE_BONUS_MULTIPLIER: 1.1,
|
||||
|
|
@ -146,6 +146,12 @@ export const COMBAT = {
|
|||
FINAL_SLOW_MOTION_HOLD_DURATION: 14000,
|
||||
FINAL_SLOW_MOTION_EXIT_DURATION: 14000,
|
||||
FINAL_SLOW_MOTION_SCALE: 0.28,
|
||||
// 전투원 간 공간 분리 (spatial grid 기반 군중 밀착 방지)
|
||||
FIGHTER_SEPARATION_ENABLED: true,
|
||||
// 전투원 중심 간 최소 이격 거리(px). HITBOX_WIDTH=22, SCALE=3이므로 약 1.5배
|
||||
FIGHTER_SEPARATION_DISTANCE: 42 * 3,
|
||||
// 밀착 시 밀어내는 힘. moveSpeed(148~163)보다 낮아야 자연스러움
|
||||
FIGHTER_SEPARATION_FORCE: 148 / 2,
|
||||
};
|
||||
|
||||
// 5. PROJECTILE 도메인
|
||||
|
|
|
|||
|
|
@ -38,16 +38,20 @@ export function updateFighter(scene, fighter, time, onWinner) {
|
|||
|| !fighter.active
|
||||
|| fighter.isDead
|
||||
|| fighter.isFrostStunned
|
||||
|| fighter.isLocked
|
||||
) {
|
||||
fighter.body?.setVelocity(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fighter.isLocked) {
|
||||
fighter.body?.setVelocity(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const enemy = resolveTargetEnemy(scene, fighter, time);
|
||||
|
||||
if (!enemy) {
|
||||
fighter.body?.setVelocity(0, 0);
|
||||
applySeparationVelocity(scene, fighter);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -63,11 +67,12 @@ export function updateFighter(scene, fighter, time, onWinner) {
|
|||
enemy,
|
||||
combatStatsFor(fighter).moveSpeed * fighterMovementSpeedMultiplier(fighter),
|
||||
);
|
||||
applySeparationVelocity(scene, fighter, { blend: true });
|
||||
playIfNeeded(fighter, "walk");
|
||||
return;
|
||||
}
|
||||
|
||||
fighter.body.setVelocity(0, 0);
|
||||
applySeparationVelocity(scene, fighter);
|
||||
|
||||
if (time >= fighter.nextAttackAt) {
|
||||
beginAttack(scene, fighter, enemy, time, onWinner);
|
||||
|
|
@ -77,6 +82,68 @@ export function updateFighter(scene, fighter, time, onWinner) {
|
|||
playIfNeeded(fighter, "idle");
|
||||
}
|
||||
|
||||
function applySeparationVelocity(scene, fighter, { blend = false } = {}) {
|
||||
if (!COMBAT.FIGHTER_SEPARATION_ENABLED) {
|
||||
if (!blend) {
|
||||
fighter.body?.setVelocity(0, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const targetIndex = scene.combatTargetIndex;
|
||||
|
||||
if (!targetIndex) {
|
||||
fighter.body?.setVelocity(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const cellX = clampCell(fighter.x, targetIndex.cellSize, targetIndex.maxCellX);
|
||||
const cellY = clampCell(fighter.y, targetIndex.cellSize, targetIndex.maxCellY);
|
||||
const sepDist = COMBAT.FIGHTER_SEPARATION_DISTANCE;
|
||||
const sepDistSq = sepDist * sepDist;
|
||||
const maxForce = COMBAT.FIGHTER_SEPARATION_FORCE;
|
||||
let forceX = 0;
|
||||
let forceY = 0;
|
||||
|
||||
for (let dx = -1; dx <= 1; dx += 1) {
|
||||
for (let dy = -1; dy <= 1; dy += 1) {
|
||||
const cell = targetIndex.cells.get(targetCellKey(cellX + dx, cellY + dy));
|
||||
|
||||
if (!cell) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cell.forEach((candidate) => {
|
||||
if (candidate === fighter || !candidate.active || candidate.isDead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = fighter.x - candidate.x;
|
||||
const deltaY = fighter.y - candidate.y;
|
||||
const distSq = deltaX * deltaX + deltaY * deltaY;
|
||||
|
||||
if (distSq >= sepDistSq || distSq < 0.001) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dist = Math.sqrt(distSq);
|
||||
const strength = (1 - dist / sepDist) * maxForce;
|
||||
|
||||
forceX += (deltaX / dist) * strength;
|
||||
forceY += (deltaY / dist) * strength;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (blend) {
|
||||
fighter.body.velocity.x += forceX;
|
||||
fighter.body.velocity.y += forceY;
|
||||
return;
|
||||
}
|
||||
|
||||
fighter.body.setVelocity(forceX, forceY);
|
||||
}
|
||||
|
||||
export function clearCombatObjects(scene) {
|
||||
scene.combatObjects?.forEach((object) => {
|
||||
object.cleanup?.();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import Phaser from "phaser";
|
||||
import {
|
||||
ARENA,
|
||||
SPECIAL_EFFECT,
|
||||
} from "../../constants.js";
|
||||
import { ARENA, SPECIAL_EFFECT } from "../../constants.js";
|
||||
import {
|
||||
applySpecialEffectInstantKill,
|
||||
disposeCombatObject,
|
||||
|
|
@ -12,10 +9,7 @@ import {
|
|||
ensureFighterTeamAnimation,
|
||||
fighterSheetKey,
|
||||
} from "../fighter/fighterAssets.js";
|
||||
import {
|
||||
FIGHTER_TYPES,
|
||||
getFighterType,
|
||||
} from "../fighter/fighterStats.js";
|
||||
import { FIGHTER_TYPES, getFighterType } from "../fighter/fighterStats.js";
|
||||
|
||||
const SPECIAL_ANIMATION_SUFFIX = "anim";
|
||||
|
||||
|
|
@ -103,7 +97,8 @@ function maybeRetrySpecialEffect(scene, matchId) {
|
|||
return;
|
||||
}
|
||||
|
||||
const elapsed = scene.time.now - (scene.specialEffectStartedAt ?? scene.time.now);
|
||||
const elapsed =
|
||||
scene.time.now - (scene.specialEffectStartedAt ?? scene.time.now);
|
||||
|
||||
if (elapsed >= SPECIAL_EFFECT.TRIGGER_DELAY_MAX_MS) {
|
||||
return;
|
||||
|
|
@ -156,14 +151,19 @@ function beginSpecialCast(scene, caster, target, matchId) {
|
|||
caster.setFlipX(direction.x < 0);
|
||||
playCasterAttack(scene, state, caster);
|
||||
|
||||
addStateTimer(scene, state, SPECIAL_EFFECT.CASTER.ATTACK_LAUNCH_DELAY_MS, () => {
|
||||
if (!isSpecialCastValid(scene, caster, matchId)) {
|
||||
cleanupSpecialCastState(scene, state, { restoreCaster: true });
|
||||
return;
|
||||
}
|
||||
addStateTimer(
|
||||
scene,
|
||||
state,
|
||||
SPECIAL_EFFECT.CASTER.ATTACK_LAUNCH_DELAY_MS,
|
||||
() => {
|
||||
if (!isSpecialCastValid(scene, caster, matchId)) {
|
||||
cleanupSpecialCastState(scene, state, { restoreCaster: true });
|
||||
return;
|
||||
}
|
||||
|
||||
spawnSpecialProjectile(scene, state, caster, direction);
|
||||
});
|
||||
spawnSpecialProjectile(scene, state, caster, direction);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +198,10 @@ function playCasterAttack(scene, state, caster) {
|
|||
return;
|
||||
}
|
||||
|
||||
caster.off(Phaser.Animations.Events.ANIMATION_COMPLETE, clearSpecialCastLock);
|
||||
caster.off(
|
||||
Phaser.Animations.Events.ANIMATION_COMPLETE,
|
||||
clearSpecialCastLock,
|
||||
);
|
||||
state.attackCompleteHandler = null;
|
||||
|
||||
if (!caster.active || caster.isDead) {
|
||||
|
|
@ -208,7 +211,8 @@ function playCasterAttack(scene, state, caster) {
|
|||
caster.isSpecialCasting = false;
|
||||
caster.isLocked = false;
|
||||
caster.anims.timeScale = 1;
|
||||
caster.nextAttackAt = scene.time.now + SPECIAL_EFFECT.CASTER.POST_CAST_COOLDOWN_MS;
|
||||
caster.nextAttackAt =
|
||||
scene.time.now + SPECIAL_EFFECT.CASTER.POST_CAST_COOLDOWN_MS;
|
||||
};
|
||||
|
||||
state.attackCompleteHandler = clearSpecialCastLock;
|
||||
|
|
@ -226,12 +230,19 @@ function spawnSpecialProjectile(scene, state, caster, direction) {
|
|||
);
|
||||
const travelDistance = ARENA.TILE_SIZE * projectileConfig.travelTiles;
|
||||
const end = resolveProjectileEndPoint(start, direction, travelDistance);
|
||||
const actualTravelDistance = Phaser.Math.Distance.Between(start.x, start.y, end.x, end.y);
|
||||
const actualTravelDistance = Phaser.Math.Distance.Between(
|
||||
start.x,
|
||||
start.y,
|
||||
end.x,
|
||||
end.y,
|
||||
);
|
||||
const duration = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
projectileConfig.maxLifetimeMs,
|
||||
Math.round((actualTravelDistance / Math.max(1, projectileConfig.speed)) * 1000),
|
||||
Math.round(
|
||||
(actualTravelDistance / Math.max(1, projectileConfig.speed)) * 1000,
|
||||
),
|
||||
),
|
||||
);
|
||||
const projectile = scene.physics.add
|
||||
|
|
@ -344,7 +355,10 @@ function resolveSpecialProjectileVisual(caster) {
|
|||
|
||||
function grantSpecialCasterInvulnerability(caster) {
|
||||
const now = resolveRealtimeNow();
|
||||
const duration = Math.max(0, Number(SPECIAL_EFFECT.CASTER.INVULNERABLE_MS) || 0);
|
||||
const duration = Math.max(
|
||||
0,
|
||||
Number(SPECIAL_EFFECT.CASTER.INVULNERABLE_MS) || 0,
|
||||
);
|
||||
const invulnerableUntil = Math.max(
|
||||
Number(caster.specialInvulnerableUntil) || 0,
|
||||
now + duration,
|
||||
|
|
@ -360,15 +374,18 @@ function grantSpecialCasterInvulnerability(caster) {
|
|||
|
||||
caster.isSpecialInvulnerable = true;
|
||||
caster.specialInvulnerableUntil = invulnerableUntil;
|
||||
caster.specialInvulnerabilityTimer = globalThis.setTimeout(() => {
|
||||
if (caster.specialInvulnerableUntil !== invulnerableUntil) {
|
||||
return;
|
||||
}
|
||||
caster.specialInvulnerabilityTimer = globalThis.setTimeout(
|
||||
() => {
|
||||
if (caster.specialInvulnerableUntil !== invulnerableUntil) {
|
||||
return;
|
||||
}
|
||||
|
||||
caster.isSpecialInvulnerable = false;
|
||||
caster.specialInvulnerableUntil = 0;
|
||||
caster.specialInvulnerabilityTimer = null;
|
||||
}, Math.max(0, invulnerableUntil - now));
|
||||
caster.isSpecialInvulnerable = false;
|
||||
caster.specialInvulnerableUntil = 0;
|
||||
caster.specialInvulnerabilityTimer = null;
|
||||
},
|
||||
Math.max(0, invulnerableUntil - now),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveRealtimeNow() {
|
||||
|
|
@ -388,10 +405,10 @@ function resolveProjectileHits(scene, projectile, state, attacker) {
|
|||
|
||||
[...scene.fighters].forEach((fighter) => {
|
||||
if (
|
||||
!isLivingFighter(fighter)
|
||||
|| fighter === attacker
|
||||
|| state.hitFighters.has(fighter)
|
||||
|| !projectileSegmentHitsFighter(segmentStart, segmentEnd, fighter)
|
||||
!isLivingFighter(fighter) ||
|
||||
fighter === attacker ||
|
||||
state.hitFighters.has(fighter) ||
|
||||
!projectileSegmentHitsFighter(segmentStart, segmentEnd, fighter)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -420,12 +437,13 @@ function projectileReachedEnd(projectile, start, end, travelDistance) {
|
|||
const pathY = end.y - start.y;
|
||||
const traveledAlongPath = traveledX * pathX + traveledY * pathY;
|
||||
|
||||
return traveledAlongPath >= (travelDistance * travelDistance);
|
||||
return traveledAlongPath >= travelDistance * travelDistance;
|
||||
}
|
||||
|
||||
function projectileSegmentHitsFighter(segmentStart, segmentEnd, fighter) {
|
||||
const center = fighter.body?.center ?? fighter;
|
||||
const hitRadius = SPECIAL_EFFECT.PROJECTILE.hitRadius + fighterHitRadius(fighter);
|
||||
const hitRadius =
|
||||
SPECIAL_EFFECT.PROJECTILE.hitRadius + fighterHitRadius(fighter);
|
||||
const distanceSq = pointToSegmentDistanceSq(center, segmentStart, segmentEnd);
|
||||
|
||||
return distanceSq <= hitRadius * hitRadius;
|
||||
|
|
@ -443,8 +461,9 @@ function pointToSegmentDistanceSq(point, segmentStart, segmentEnd) {
|
|||
}
|
||||
|
||||
const t = Phaser.Math.Clamp(
|
||||
((point.x - segmentStart.x) * deltaX + (point.y - segmentStart.y) * deltaY)
|
||||
/ lengthSq,
|
||||
((point.x - segmentStart.x) * deltaX +
|
||||
(point.y - segmentStart.y) * deltaY) /
|
||||
lengthSq,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
|
@ -457,14 +476,13 @@ function pointToSegmentDistanceSq(point, segmentStart, segmentEnd) {
|
|||
}
|
||||
|
||||
function fighterHitRadius(fighter) {
|
||||
const bodyRadius = Math.max(
|
||||
fighter.body?.width ?? 0,
|
||||
fighter.body?.height ?? 0,
|
||||
) / 2;
|
||||
const visualRadius = Math.max(
|
||||
Math.abs(fighter.displayWidth ?? 0),
|
||||
Math.abs(fighter.displayHeight ?? 0),
|
||||
) * 0.14;
|
||||
const bodyRadius =
|
||||
Math.max(fighter.body?.width ?? 0, fighter.body?.height ?? 0) / 2;
|
||||
const visualRadius =
|
||||
Math.max(
|
||||
Math.abs(fighter.displayWidth ?? 0),
|
||||
Math.abs(fighter.displayHeight ?? 0),
|
||||
) * 0.14;
|
||||
|
||||
return Math.max(8, bodyRadius, visualRadius);
|
||||
}
|
||||
|
|
@ -516,10 +534,10 @@ function cleanupSpecialCastState(
|
|||
}
|
||||
|
||||
if (
|
||||
restoreCaster
|
||||
&& state.caster?.active
|
||||
&& !state.caster.isDead
|
||||
&& state.caster.isSpecialCasting
|
||||
restoreCaster &&
|
||||
state.caster?.active &&
|
||||
!state.caster.isDead &&
|
||||
state.caster.isSpecialCasting
|
||||
) {
|
||||
state.caster.isSpecialCasting = false;
|
||||
state.caster.isLocked = false;
|
||||
|
|
@ -626,16 +644,14 @@ function pauseSpecialPreparationCombat(scene, state) {
|
|||
object.anims.pause();
|
||||
}
|
||||
|
||||
scene.tweens
|
||||
?.getTweensOf(object)
|
||||
?.forEach((tween) => {
|
||||
if (tween.paused) {
|
||||
return;
|
||||
}
|
||||
scene.tweens?.getTweensOf(object)?.forEach((tween) => {
|
||||
if (tween.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
pausedTweens.add(tween);
|
||||
tween.pause();
|
||||
});
|
||||
pausedTweens.add(tween);
|
||||
tween.pause();
|
||||
});
|
||||
|
||||
combatObjectStates.set(object, objectState);
|
||||
});
|
||||
|
|
@ -762,9 +778,16 @@ function createBlurredBattlefieldLayer(scene, config) {
|
|||
.setOrigin(0, 0)
|
||||
.setDepth(config.BLUR_DEPTH)
|
||||
.setAlpha(0);
|
||||
const entries = scene.children.getChildren().filter((gameObject) =>
|
||||
shouldDrawInSpecialFocusSnapshot(scene, gameObject, renderTexture, config),
|
||||
);
|
||||
const entries = scene.children
|
||||
.getChildren()
|
||||
.filter((gameObject) =>
|
||||
shouldDrawInSpecialFocusSnapshot(
|
||||
scene,
|
||||
gameObject,
|
||||
renderTexture,
|
||||
config,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
renderTexture.draw(entries);
|
||||
|
|
@ -785,14 +808,19 @@ function createBlurredBattlefieldLayer(scene, config) {
|
|||
return renderTexture;
|
||||
}
|
||||
|
||||
function shouldDrawInSpecialFocusSnapshot(scene, gameObject, renderTexture, config) {
|
||||
function shouldDrawInSpecialFocusSnapshot(
|
||||
scene,
|
||||
gameObject,
|
||||
renderTexture,
|
||||
config,
|
||||
) {
|
||||
return Boolean(
|
||||
gameObject
|
||||
&& gameObject !== renderTexture
|
||||
&& gameObject.active
|
||||
&& gameObject.visible
|
||||
&& gameObject !== scene.minimapGraphics
|
||||
&& gameObject.depth < config.BLUR_DEPTH
|
||||
gameObject &&
|
||||
gameObject !== renderTexture &&
|
||||
gameObject.active &&
|
||||
gameObject.visible &&
|
||||
gameObject !== scene.minimapGraphics &&
|
||||
gameObject.depth < config.BLUR_DEPTH,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -856,10 +884,11 @@ function selectSpecialCaster(scene) {
|
|||
.filter((summary) => summary.count === highestCount)
|
||||
.map((summary) => summary.teamId),
|
||||
);
|
||||
const candidates = livingFighters.filter((fighter) =>
|
||||
!leadingTeamIds.has(fighter.team?.id)
|
||||
&& !fighter.isElite
|
||||
&& getFighterType(fighter.skin) !== FIGHTER_TYPES.MAGIC,
|
||||
const candidates = livingFighters.filter(
|
||||
(fighter) =>
|
||||
!leadingTeamIds.has(fighter.team?.id) &&
|
||||
// && !fighter.isElite
|
||||
getFighterType(fighter.skin) !== FIGHTER_TYPES.MAGIC,
|
||||
);
|
||||
|
||||
if (candidates.length === 0) {
|
||||
|
|
@ -906,9 +935,8 @@ function findDensestSpecialTarget(fighters) {
|
|||
SPECIAL_EFFECT.PROJECTILE.targetAreaTiles,
|
||||
ARENA.GRID_SIZE,
|
||||
);
|
||||
const tileCounts = Array.from(
|
||||
{ length: ARENA.GRID_SIZE },
|
||||
() => Array(ARENA.GRID_SIZE).fill(0),
|
||||
const tileCounts = Array.from({ length: ARENA.GRID_SIZE }, () =>
|
||||
Array(ARENA.GRID_SIZE).fill(0),
|
||||
);
|
||||
|
||||
fighters.forEach((fighter) => {
|
||||
|
|
@ -974,7 +1002,10 @@ function livingTeamSummaries(livingFighters) {
|
|||
|
||||
function resolveLaunchDirection(caster, target) {
|
||||
if (target) {
|
||||
const direction = new Phaser.Math.Vector2(target.x - caster.x, target.y - caster.y);
|
||||
const direction = new Phaser.Math.Vector2(
|
||||
target.x - caster.x,
|
||||
target.y - caster.y,
|
||||
);
|
||||
|
||||
if (direction.lengthSq() > 0) {
|
||||
return direction.normalize();
|
||||
|
|
@ -992,7 +1023,10 @@ function pointInDirection(origin, direction, distance) {
|
|||
}
|
||||
|
||||
function resolveProjectileEndPoint(start, direction, travelDistance) {
|
||||
const padding = Math.max(0, Number(SPECIAL_EFFECT.PROJECTILE.arenaEdgePadding) || 0);
|
||||
const padding = Math.max(
|
||||
0,
|
||||
Number(SPECIAL_EFFECT.PROJECTILE.arenaEdgePadding) || 0,
|
||||
);
|
||||
const maximumDistance = Math.max(0, Number(travelDistance) || 0);
|
||||
const edgeDistance = distanceToArenaEdge(start, direction, padding);
|
||||
const distance = Math.max(0, Math.min(maximumDistance, edgeDistance));
|
||||
|
|
@ -1041,18 +1075,17 @@ function clampPointInsideArena(point, padding) {
|
|||
}
|
||||
|
||||
function createSummedAreaTable(tileCounts) {
|
||||
const sums = Array.from(
|
||||
{ length: ARENA.GRID_SIZE + 1 },
|
||||
() => Array(ARENA.GRID_SIZE + 1).fill(0),
|
||||
const sums = Array.from({ length: ARENA.GRID_SIZE + 1 }, () =>
|
||||
Array(ARENA.GRID_SIZE + 1).fill(0),
|
||||
);
|
||||
|
||||
for (let row = 0; row < ARENA.GRID_SIZE; row += 1) {
|
||||
for (let column = 0; column < ARENA.GRID_SIZE; column += 1) {
|
||||
sums[row + 1][column + 1] =
|
||||
tileCounts[row][column]
|
||||
+ sums[row][column + 1]
|
||||
+ sums[row + 1][column]
|
||||
- sums[row][column];
|
||||
tileCounts[row][column] +
|
||||
sums[row][column + 1] +
|
||||
sums[row + 1][column] -
|
||||
sums[row][column];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1064,10 +1097,10 @@ function sumArea(sums, column, row, areaTiles) {
|
|||
const right = column + areaTiles;
|
||||
|
||||
return (
|
||||
sums[bottom][right]
|
||||
- sums[row][right]
|
||||
- sums[bottom][column]
|
||||
+ sums[row][column]
|
||||
sums[bottom][right] -
|
||||
sums[row][right] -
|
||||
sums[bottom][column] +
|
||||
sums[row][column]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1078,7 +1111,11 @@ function resolveTileCount(value, maximum) {
|
|||
function resolveFighterSheetKey(scene, fighter, action) {
|
||||
ensureFighterTeamAnimation(scene, fighter.skin, action, fighter.team?.color);
|
||||
|
||||
const teamSheetKey = fighterSheetKey(fighter.skin, action, fighter.team?.color);
|
||||
const teamSheetKey = fighterSheetKey(
|
||||
fighter.skin,
|
||||
action,
|
||||
fighter.team?.color,
|
||||
);
|
||||
|
||||
if (scene.textures.exists(teamSheetKey)) {
|
||||
return teamSheetKey;
|
||||
|
|
@ -1089,12 +1126,20 @@ function resolveFighterSheetKey(scene, fighter, action) {
|
|||
|
||||
function playFighterAnimation(scene, fighter, action) {
|
||||
fighter.play(
|
||||
ensureFighterTeamAnimation(scene, fighter.skin, action, fighter.team?.color),
|
||||
ensureFighterTeamAnimation(
|
||||
scene,
|
||||
fighter.skin,
|
||||
action,
|
||||
fighter.team?.color,
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
function createSpecialAnimation(scene, { frameRate, frameSequence, frames, key, repeat }) {
|
||||
function createSpecialAnimation(
|
||||
scene,
|
||||
{ frameRate, frameSequence, frames, key, repeat },
|
||||
) {
|
||||
const animationKey = specialAnimationKey(key);
|
||||
|
||||
if (scene.anims.exists(animationKey)) {
|
||||
|
|
@ -1103,7 +1148,11 @@ function createSpecialAnimation(scene, { frameRate, frameSequence, frames, key,
|
|||
|
||||
scene.anims.create({
|
||||
key: animationKey,
|
||||
frames: resolveSpecialAnimationFrames(scene, { frameSequence, frames, key }),
|
||||
frames: resolveSpecialAnimationFrames(scene, {
|
||||
frameSequence,
|
||||
frames,
|
||||
key,
|
||||
}),
|
||||
frameRate,
|
||||
repeat,
|
||||
});
|
||||
|
|
@ -1139,18 +1188,11 @@ function resolveInitialDelayMs() {
|
|||
}
|
||||
|
||||
function isSpecialCastValid(scene, caster, matchId) {
|
||||
return (
|
||||
isLiveMatch(scene, matchId)
|
||||
&& caster?.active
|
||||
&& !caster.isDead
|
||||
);
|
||||
return isLiveMatch(scene, matchId) && caster?.active && !caster.isDead;
|
||||
}
|
||||
|
||||
function isLivingEnemy(fighter, candidate) {
|
||||
return (
|
||||
isLivingFighter(candidate)
|
||||
&& candidate.team?.id !== fighter.team?.id
|
||||
);
|
||||
return isLivingFighter(candidate) && candidate.team?.id !== fighter.team?.id;
|
||||
}
|
||||
|
||||
function isLivingFighter(fighter) {
|
||||
|
|
@ -1166,5 +1208,7 @@ function randomEntry(entries) {
|
|||
}
|
||||
|
||||
function isLiveMatch(scene, matchId = scene.matchId) {
|
||||
return !scene.matchOver && !scene.presentationMode && scene.matchId === matchId;
|
||||
return (
|
||||
!scene.matchOver && !scene.presentationMode && scene.matchId === matchId
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue