137 lines
18 KiB
Markdown
137 lines
18 KiB
Markdown
# Update: Special Projectile Trail
|
|
|
|
- `SPECIAL_EFFECT.PROJECTILE.TRAIL` controls optional afterimages for the moving special projectile. Each trail copy uses the projectile's current texture frame, scale, rotation, and flip state.
|
|
- Trail objects are visual-only combat objects: they fade out and self-dispose, but they do not participate in hit detection.
|
|
- `TRAIL.INTERVAL_MS` and `TRAIL.LIFETIME_MS` bound how many afterimages can exist at once.
|
|
|
|
# Update: Split Special Projectile Visual Configs
|
|
|
|
- Special projectile visual asset settings are separated by caster type. Melee visual sheets are configured under `SPECIAL_EFFECT.MELEE`; ranged visual sheets are configured under `SPECIAL_EFFECT.RANGE`.
|
|
- `SPECIAL_EFFECT.PROJECTILE` now carries shared projectile behavior only: hold time, acceleration/ease, fallback speed, target density area, travel clamp, hit radius, max lifetime, and optional trail visuals.
|
|
|
|
# Update: One-Shot Accelerating Special Projectile
|
|
|
|
- `SPECIAL_EFFECT.MELEE.REPEAT = 0` makes melee special sprites play once. `special-melee-effect-1` currently has `frameSequence` commented out, so it uses the sprite sheet's natural frame order.
|
|
- Special projectile movement now uses a tween instead of constant `physics.moveTo`. `SPECIAL_EFFECT.PROJECTILE.startHoldMs` keeps the effect stationary for the pre-launch tell, `travelDurationMs` controls the launch duration when set, and `movementEase` controls the acceleration curve. If `travelDurationMs` is unset, movement falls back to `speed`.
|
|
- Projectile hit checks still run from the scene UPDATE event while the tween moves the sprite, so instant-kill path detection remains active during acceleration.
|
|
|
|
# Update: Special Effect Frame Sequence Refresh
|
|
|
|
- `createSpecialAnimation()` now rebuilds a special animation when the existing Phaser global animation no longer matches the configured frames, repeat count, or frame rate. This prevents stale animation keys from hiding `frameSequence` edits.
|
|
- `frameSequence` values stay 1-based in config and are converted with `generateFrameNumbers(..., { frames })`, so repeated frames are preserved when a special asset enables a custom sequence.
|
|
|
|
# Update: Special Effect Frame Rate And Render Budget
|
|
|
|
- `SPECIAL_EFFECT.FRAME_RATE_MULTIPLIER` multiplies only special-effect animation frame rates. Caster sparkle, melee special sprites, and ranged special projectile frames can play faster or slower without changing caster hold time, launch delay, projectile movement speed, travel distance, or cleanup timers.
|
|
- `SPECIAL_EFFECT.FOCUS_LAYER.BLUR_MAX_FIGHTERS` caps when `specialEffects.js` creates the full-arena blurred render-texture snapshot. Above that living-fighter count, the special focus keeps the dim layer and raised caster but skips the expensive blur pass.
|
|
- Special projectile hit checks prefer `scene.combatTargetIndex` and scan only spatial cells around the projectile segment, falling back to `scene.fighters` when no index exists.
|
|
|
|
# Update: Special Effect Projectile
|
|
|
|
- `specialEffects.js` owns the one-shot special effect flow: asset preload/animation creation, random live-match scheduling, underdog caster selection, caster pose lock, launch visuals, projectile path checks, and cleanup.
|
|
- `SPECIAL_EFFECT` in `src/constants.js` tunes trigger timing, caster Hurt-frame hold, camera zoom, melee projectile assets, ranged projectile asset scale/speed/travel distance/hit radius, target density area, arena edge padding, and lifetime. `WORLD_EFFECT.SPECIAL` points to the same config object.
|
|
- Casters must be living, non-elite, non-magic fighters from teams that are not currently tied for first by represented living count. When `SPECIAL_EFFECT.CASTER.BALANCE_NON_MAGIC_TYPES` is enabled, caster selection first picks among available non-magic types and then picks a fighter from that type, preventing the larger melee roster from overwhelming ranged special casts. If no caster exists at the chosen time, the timer retries within the configured window instead of firing multiple times.
|
|
- Special casters receive realtime invulnerability for `SPECIAL_EFFECT.CASTER.INVULNERABLE_MS`. `combat.js` checks that window in normal attacks, world-effect damage, and special instant kills, while `worldEffects.js` also skips frost survivor effects for invulnerable fighters.
|
|
- While the caster holds the Hurt frame, `specialEffects.js` pauses fighter AI, Arcade Physics, the scene clock, existing combat-object physics velocity, combat-object animations, and combat-object tweens. Existing combat/world delayed calls and already-falling meteor/frost tweens stop advancing until the realtime preparation hold releases into the attack animation.
|
|
- The Hurt-frame preparation also spawns a caster sparkle overlay from `public/assets/effects/special/effect.png`. Its animation uses the 1-based frame sequence `[2, 3, 4]` only, positions near the caster's eyes through `SPECIAL_EFFECT.CASTER_SPARKLE`, and is disposed before the attack animation starts.
|
|
- Caster emphasis uses `SPECIAL_EFFECT.FOCUS_LAYER`: `specialEffects.js` snapshots the current battlefield into a render texture, applies Phaser Blur FX when available, adds a dim layer, and raises the caster above both layers until cleanup.
|
|
- The special camera does not follow the projectile. When projectile movement begins, `zoomOutSpecialEffectCameraFocus()` zooms out in place using `SPECIAL_EFFECT.CAMERA.PROJECTILE_VIEW_ZOOM` and `PROJECTILE_ZOOM_OUT_MS` so the projectile remains readable without dragging the camera off the arena.
|
|
- Melee special projectile effects can define 1-based `frameSequence` arrays. `specialEffects.js` converts them to Phaser frames so specific frames can be repeated for readability. The projectile's `startHoldMs` keeps it visible at the caster before travel begins.
|
|
- At target-selection time, the special projectile scans living enemies with a summed-area table and locks onto the `SPECIAL_EFFECT.PROJECTILE.targetAreaTiles` square containing the highest represented `stackCount` population. The moving projectile visual is type-based: melee casters fire one random `SPECIAL_EFFECT.MELEE.ASSETS` sprite, while ranged casters use `SPECIAL_EFFECT.RANGE`. Movement uses a tweened Arcade Physics sprite, matching normal ranged projectile path-update checks while allowing acceleration, and projectile travel is cut to the arena bounds using `arenaEdgePadding`.
|
|
- The special projectile calls `applySpecialEffectInstantKill()` from `combat.js`, so instant kills still use the normal death animation, death-stat recording, kill log attribution when there is a surviving caster, split-on-death behavior, scoreboard refresh, and match-finish checks.
|
|
|
|
# Update: Elite Magic Attack Effect Scale
|
|
|
|
- `combat.js` resolves instant-spell attack effect scale through constants instead of hard-coding `FIGHTER.SCALE`.
|
|
- Normal spell effects use `FIGHTER.SCALE * FIGHTER.ATTACK_EFFECT_SCALE_MULTIPLIER`.
|
|
- Elite magic spell effects additionally multiply by `FIGHTER.ELITE.ATTACK_EFFECT_SCALE_MULTIPLIER`, keeping caster body scale and effect scale separately tunable.
|
|
|
|
# Update: Elite Target Damage And Density
|
|
|
|
- `combat.js` uses `fighter.isElite` to split damage rules. Elite critical hits deal the greater of the ordinary hit or `COMBAT.CRITICAL_DAMAGE_PERCENT` of max HP; normal critical hits deal `NORMAL_CRITICAL_DAMAGE_MULTIPLIER` times the ordinary hit.
|
|
- Elite attack and movement speed are calculated through `FIGHTER.ELITE.ATTACK_SPEED_*` and `MOVE_SPEED_*` constants. Each multiplier is additive: `0` removes its added stack bonus, while `1` applies its configured exponent.
|
|
- Elite direct kills trigger a kill splash at the killed fighter's body position. The splash deals `COMBAT.ELITE_KILL_SPLASH_DAMAGE_PERCENT` of that killed fighter's max HP to living enemies inside `COMBAT.ELITE_KILL_SPLASH_RADIUS`; splash kills are recorded normally, recursive splash chaining is controlled by `ELITE_KILL_SPLASH_CHAIN_ENABLED`, and the optional visual uses square pixel dots rather than smooth circles.
|
|
- Kills still record the attacker/defender and drive match resolution, but `COMBAT.KILL_REWARD_ENABLED = false` prevents heal effects, scale growth, and kill-derived speed multipliers in compressed elite battles.
|
|
- `worldEffects.js` passes an effect type into `applyWorldEffectDamage()`: normal targets retain fixed fire/frost damage, while elite targets take `WORLD_EFFECT.METEOR_DAMAGE_PERCENT` or `FROST_DAMAGE_PERCENT` of max HP.
|
|
- Dense-area target scanning adds each fighter's represented `stackCount` into its tile, preventing compressed armies from disappearing from meteor/frost targeting pressure.
|
|
|
|
# Update: Dense-Area Meteor Barrage
|
|
|
|
- `worldEffects.js` aggregates living fighters on the arena tile grid and uses a summed-area scan to select the `WORLD_EFFECT.AREA_TILES` square with the highest population.
|
|
- That selected square is a warning/focus area. Each fire or frost event schedules `WORLD_EFFECT.IMPACT_COUNT_MIN` to `IMPACT_COUNT_MAX` smaller strikes inside it.
|
|
- Tune the large warning visibility with `WORLD_EFFECT.WARNING_DURATION_MS`, actual damage/frost footprints with `WORLD_EFFECT.IMPACT_AREA_TILES`, sprite size with `WORLD_EFFECT.IMPACT_VISUAL_SCALE`, strike spacing with `WORLD_EFFECT.IMPACT_STAGGER_MS`, and per-strike variation with `WORLD_EFFECT.SIZE_SCALE_VARIANCE`.
|
|
- `WORLD_EFFECT.INTERVAL` sets the delay before the first barrage; subsequent normal barrages use `WORLD_EFFECT.REPEAT_INTERVAL`, with `SUDDEN_DEATH.INTERVAL_MS` taking over once sudden death is active.
|
|
- Meteor impact shake uses the same size multiplier, scaling from `WORLD_EFFECT.METEOR_SHAKE_DURATION_MS` and `WORLD_EFFECT.METEOR_SHAKE_INTENSITY`.
|
|
|
|
# Update: Large Battle Targeting
|
|
|
|
- `combat.js` now prepares a per-frame target spatial index through `prepareCombatFrame(scene)`.
|
|
- `resolveTargetEnemy()` keeps valid cached targets until their scan interval expires, then immediately looks up a fresh nearest enemy through the spatial grid.
|
|
- Nearest enemy lookup searches grid cells outward from the fighter's current cell, with full-array scanning kept only as a fallback when no frame index exists.
|
|
- Large-battle corpse cleanup uses `PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLD` and `PERFORMANCE.LARGE_BATTLE_DEAD_DESPAWN_DELAY_MS` from `src/constants.js`.
|
|
|
|
# Update: Team Shadow Animations And Frost Tint
|
|
|
|
- Dead fighters keep their death animation/corpse state at initial opacity, then fade out until `combat.js` removes them from `scene.fighters` and destroys the sprite.
|
|
- Tune that fade/despawn lifetime with `FIGHTER.DEAD_DESPAWN_DELAY_MS` and the final alpha with `FIGHTER.DEAD_DESPAWN_ALPHA` in `src/constants.js`.
|
|
- `combat.js` resolves fighter animation keys through `ensureFighterTeamAnimation()` so every action can use the team-shadow baked texture generated from the original spritesheet.
|
|
- `playIfNeeded()` compares against the team-shadow animation key. This avoids switching back to the original non-team-colored spritesheet when fighters move, attack, take damage, or die.
|
|
- Frost stun remains a body tint effect in `worldEffects.js`. Since team identity is baked into the floor shadow pixels, there is no `teamMarker` tint state to update or restore.
|
|
- The removed `teamMarker` display object means death handling no longer needs to hide or destroy a separate marker. HUD cleanup only owns health-bar objects because battlefield name labels are no longer created.
|
|
|
|
# Update: Focused Combat Effects In Large Battles
|
|
|
|
- `combat.js` exposes supplemental combat visuals only while a large battle is inside the temporary meteor camera-focus window.
|
|
- Outside that window, large battles skip critical labels, instant-spell sprites, kill-heal sprites, and kill-growth tweens while retaining underlying damage calculations. Kill rewards are globally disabled by the elite policy.
|
|
- World-effect meteor/frost visuals remain visible, and projectile objects remain enabled because projectiles currently participate in hit detection.
|
|
|
|
# Context: Combat System
|
|
|
|
## 1. 모듈별 상세 역할 (`src/game/combat/`)
|
|
|
|
- **`combat.js`**: 전투 AI, 피해 계산, 처치 기록 및 비활성화된 보너스 경로를 담당합니다. `fighterStats.js`에서 해석한 역할별 수치로 이동, 공격, 투사체 발사 등을 처리합니다.
|
|
- **`combatSettings.js`**: 전투 속도 배율 등 런타임 전투 설정을 관리합니다.
|
|
- **`arenaFinalCombatEffects.js`**: 최종 교전 시 슬로우 모션 등 연출 효과를 담당합니다. 수학적인 이징(easing) 함수와 물리 시간 배율 계산을 포함합니다.
|
|
- **`worldEffects.js`**: 실제 전투에서 설정 주기마다 생존자 밀집 구역을 탐색하고 화염/냉기 소형 메테오 포격을 실행하며, 대각선 낙하 연출, 개별 탄착 판정, 냉기 동결과 감속 구역 수명주기를 처리합니다.
|
|
|
|
## 2. 주요 로직 구현 세부 사항
|
|
|
|
### 전투 AI 및 유닛 동작
|
|
- **`updateFighter()`**: 가장 가까운 적을 찾아 이동하거나 공격하는 유닛 AI의 핵심입니다.
|
|
- **`applyHit()`**: 일반 공격 피해량은 공격자의 `melee`/`ranged`/`magic` 프로필 피해량 범위에서 계산합니다. 치명타 적중은 `Critical!`을 표시하고, 일반 대상에는 일반 피해의 2배, elite 대상에는 최대 체력 비례 피해를 적용합니다.
|
|
- **역할별 기본값**: `src/constants.js`의 `FIGHTER_TYPE_STATS`에서 체력, 이동속도, 사거리, 공격 쿨다운, 피해량, 치명타 확률, 발동 지연을 독립적으로 조절합니다. 투사체 속도는 `ranged`, 효과 적중 지연은 `magic` 프로필에 포함됩니다.
|
|
- **`projectilePathHitsDefender()`**: 투사체가 대상을 스쳐 지나가지 않도록 궤적(Line)과 히트박스(Rectangle) 겹침 검사를 수행합니다.
|
|
|
|
### 처치 보너스 정책
|
|
- elite 압축 전투에서는 `COMBAT.KILL_REWARD_ENABLED`가 `false`이므로 처치자 체력 회복, 크기 성장, 공격속도/이동속도 보너스와 회복 이펙트가 적용되지 않습니다.
|
|
- 킬로그, 사망 통계, 분열 판정, 승패 판정은 처치 보너스와 별개로 계속 처리됩니다.
|
|
- `applyKillReward()`와 관련 상수는 향후 별도의 비압축 모드에서 명시적으로 활성화할 수 있는 경로로만 보존합니다.
|
|
|
|
### 월드 이펙트
|
|
- **발동 규칙**: 프리뷰가 아닌 실제 전투에서 시작 후 첫 포격은 `WORLD_EFFECT.INTERVAL`이 지난 뒤 발생하고, 이후 일반 포격은 `WORLD_EFFECT.REPEAT_INTERVAL` 간격으로 발생합니다. 각 포격은 `AREA_TILES` 크기의 모든 후보 구역을 타일 누적합으로 평가해, 생존 캐릭터가 가장 많이 모인 범위를 선택합니다. 같은 밀도의 후보가 여러 개일 때만 그 후보 사이에서 무작위로 고릅니다.
|
|
- **포격 판정**: 선택된 큰 범위는 경고 표시와 카메라 포커스 대상으로 사용되고, 내부에 투하되는 작은 탄착 영역만 피해, 기절, 냉기 감속을 처리합니다. 이 때문에 넓은 밀집지대를 위협하면서도 영역 전체를 즉시 동일 피해로 덮지 않습니다.
|
|
- **서든 데스 (Sudden Death)**:
|
|
- **조건**: 매치 시작 후 `WORLD_EFFECT.SUDDEN_DEATH.TRIGGER_MS` 시간이 경과하면 서든 데스 상태에 진입합니다 (활성화 시).
|
|
- **효과**: 메테오 투하 주기가 `SUDDEN_DEATH.INTERVAL_MS`로 단축되며, `FORCE_FROST` 설정 시 빙결 효과를 가진 냉기 메테오가 집중적으로 생성됩니다.
|
|
- **목적**: 장기전을 방지하고 전장에 무작위 변수를 극대화하여 물량 중심 팀에게 리스크를 부여합니다.
|
|
- **낙하 방향과 크기**: 대상이 전장 좌측 반면(2, 3사분면)이면 화살표가 좌상단에서 우하단으로, 우측 반면(1, 4사분면)이면 좌우 반전되어 우상단에서 좌하단으로 이동합니다. 스프라이트를 45도로 기울이고 전용 시각 배율을 사용해 전역 마법 규모로 표현합니다.
|
|
- **화염 메테오**: `world_Effect.png`의 소형 탄착 애니메이션 3~4개가 밀집 경고 구역 내부로 순차 낙하합니다. 각 탄착은 크기에 따른 화면 흔들림과 개별 영역 고정 피해를 적용합니다. 환경 피해로 인한 사망은 킬 보상을 지급하지 않지만 사망 통계와 승패 판정에는 반영됩니다.
|
|
- **냉기 메테오**: `world_Effect_2.png`의 소형 탄착 애니메이션들이 같은 방식으로 낙하합니다. 개별 탄착 피해를 입고 생존한 대상은 얼음색으로 바뀐 채 설정 시간 동안 기절하며, 각 탄착점에 남는 냉각지대 안에서는 공격속도와 이동속도 감속 배율을 적용합니다.
|
|
|
|
### 최종교전 슬로우모션
|
|
`COMBAT.FINAL_SLOW_MOTION_ENABLED`가 활성화된 경우:
|
|
- 최종교전 상태에서 공격 모션이 시작될 때 전역 time scale을 낮춥니다.
|
|
- 진입/유지/복귀 속도 램프(Ease)를 적용합니다.
|
|
- Arcade Physics는 timeScale 방향이 반대라 물리 이동에는 역수 배율을 적용합니다.
|
|
|
|
## 3. 유지보수 규칙
|
|
- **처치 보너스**: elite 압축 규칙을 유지하는 동안 `src/constants.js`의 `COMBAT.KILL_REWARD_ENABLED`는 `false`로 유지합니다.
|
|
- **공격력 조정**: 일반 역할 피해량은 `src/constants.js`의 `FIGHTER_TYPE_STATS.<type>.damageMin/damageMax`를 수정하고, elite 추가 공격력은 `FIGHTER.ELITE.ATTACK_DAMAGE_BONUS_MULTIPLIER`와 `ATTACK_DAMAGE_STACK_EXPONENT`를 수정합니다.
|
|
- **월드 이펙트 및 서든 데스 조정**:
|
|
- `src/constants.js`의 `WORLD_EFFECT.METEOR_DAMAGE`와 `WORLD_EFFECT.FROST_DAMAGE`는 normal 고정 피해를, `METEOR_DAMAGE_PERCENT`와 `FROST_DAMAGE_PERCENT`는 elite 최대 체력 비례 피해를 조정합니다.
|
|
- `SUDDEN_DEATH.ENABLED`로 서든 데스 활성화 여부를 결정하며, `TRIGGER_MS`(시작 시간), `INTERVAL_MS`(주기), `FORCE_FROST`(냉기 고정) 설정을 변경할 수 있습니다.
|
|
- `INTERVAL`은 첫 포격까지의 대기 시간, `REPEAT_INTERVAL`은 이후 일반 포격 주기입니다. `AREA_TILES`는 밀집도를 검색하고 경고로 표시할 큰 구역이며, `WARNING_DURATION_MS`는 그 경고 표시 시간입니다. `IMPACT_AREA_TILES`, `IMPACT_COUNT_MIN`/`IMPACT_COUNT_MAX`, `IMPACT_STAGGER_MS`, `IMPACT_VISUAL_SCALE`는 내부 소형 포격의 판정 범위, 발수, 간격, 시각 크기를 조정합니다.
|
|
- `WORLD_EFFECT.FROST_STUN_DURATION`/`FROST_STUN_TINT`로 동결 시간과 표시 색상을 조정합니다.
|
|
- 나머지 `WORLD_EFFECT.*` 값으로 발동 주기, 범위, 냉각 지속시간과 감속 정도를 수정하며, 메테오 착탄 위치 포커싱은 `CAMERA.METEOR_FOCUS_ENABLED`에서 켜고 끕니다.
|
|
- **특수 규칙**: 캐릭터별 특수 공격 방식은 `fighterManifest.js`의 `combat` 설정을 확인합니다.
|