first commmit

This commit is contained in:
jakciehan
2026-05-06 08:17:32 +08:00
commit 427a33c55b
269 changed files with 20906 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
import { IBossConfig } from '../data/Interfaces';
/**
* Boss controller — 双幻坊 (req 9.1-9.6 + 8.6-8.7).
*
* Key beats:
* 1. A lone butterfly orbits the boss. Until the butterfly is hit, the boss
* is invulnerable (`butterflyRevealed = false`).
* 2. First hit on the butterfly → `revealedAt` timestamp stamped, boss
* becomes vulnerable.
* 3. While revealed, ANY single clean hit kills the boss (req 9.3). We still
* honour `phase` transitions at 2/3 and 1/3 HP for visual variety.
* 4. When HP ≤ `princessCutsceneAtHpRatio`, we emit a one-shot `princess_taken`
* event (≤ 3s, battle keeps running — req 8.6 / 14.1).
* 5. On death we emit `boss_killed` + `chapter_end_cutscene` (≤ 2s). Neither
* code path produces a "rope-severing rescue" event — decision D-5 / req
* 14.5.
*/
export type BossOutcomeEvent =
| { kind: 'phase_changed'; phase: string; actionIntervalSec: number }
| { kind: 'butterfly_revealed' }
| { kind: 'princess_taken_cutscene' }
| { kind: 'boss_killed' };
export class BossController {
private hp: number;
private butterflyRevealed = false;
private phaseIndex = 0;
private princessCutscenePlayed = false;
private killed = false;
public readonly cfg: IBossConfig;
constructor(cfg: IBossConfig) {
this.cfg = cfg;
this.hp = cfg.hp;
}
public get currentHp(): number {
return this.hp;
}
public get currentPhase() {
return this.cfg.phases[this.phaseIndex];
}
public get isButterflyRevealed(): boolean {
return this.butterflyRevealed;
}
public get isDead(): boolean {
return this.killed;
}
/** Called when a player's attack hits the butterfly (req 9.2). */
public onButterflyHit(): BossOutcomeEvent[] {
if (this.butterflyRevealed) return [];
this.butterflyRevealed = true;
return [{ kind: 'butterfly_revealed' }];
}
/**
* Called when a player's attack hits the boss body. Before the butterfly
* is revealed this call is a no-op; after reveal, the boss dies in one
* hit (req 9.3).
*/
public onBodyHit(): BossOutcomeEvent[] {
if (!this.butterflyRevealed) return [];
if (this.killed) return [];
const out: BossOutcomeEvent[] = [];
this.hp = Math.max(0, this.hp - 1);
out.push(...this.checkPhaseTransition(), ...this.checkPrincessCutscene());
if (this.hp === 0) {
this.killed = true;
out.push({ kind: 'boss_killed' });
}
return out;
}
// --------------------------------------------------------------------
private checkPhaseTransition(): BossOutcomeEvent[] {
const hpRatio = this.hp / this.cfg.hp;
for (let i = this.phaseIndex + 1; i < this.cfg.phases.length; i++) {
if (hpRatio <= this.cfg.phases[i].hpThreshold) {
this.phaseIndex = i;
return [
{
kind: 'phase_changed',
phase: this.cfg.phases[i].mode,
actionIntervalSec: this.cfg.phases[i].actionIntervalSec,
},
];
}
}
return [];
}
private checkPrincessCutscene(): BossOutcomeEvent[] {
if (this.princessCutscenePlayed) return [];
const threshold = this.cfg.princessCutsceneAtHpRatio;
if (threshold === undefined) return [];
const hpRatio = this.hp / this.cfg.hp;
if (hpRatio <= threshold) {
this.princessCutscenePlayed = true;
return [{ kind: 'princess_taken_cutscene' }];
}
return [];
}
}