184 lines
4.7 KiB
TypeScript
184 lines
4.7 KiB
TypeScript
|
|
import { ReactiveScene } from "boardgame-phaser";
|
||
|
|
import { spawnEffect, type Spawner } from "boardgame-phaser";
|
||
|
|
import { mutableSignal } from "boardgame-core";
|
||
|
|
import { type CombatState } from "boardgame-core/samples/slay-the-spire-like";
|
||
|
|
import { createCombatState } from "@/state/combatState";
|
||
|
|
import { createButton } from "@/utils/createButton";
|
||
|
|
import { SceneKey } from "./types";
|
||
|
|
import {
|
||
|
|
CombatUnitContainer,
|
||
|
|
type CombatUnitData,
|
||
|
|
} from "@/gameobjects/CombatUnitContainer";
|
||
|
|
|
||
|
|
export class CombatTestScene extends ReactiveScene {
|
||
|
|
private combatSignal = mutableSignal<CombatState | null>(null);
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super("CombatTestScene");
|
||
|
|
}
|
||
|
|
|
||
|
|
create(): void {
|
||
|
|
super.create();
|
||
|
|
const { width, height } = this.scale;
|
||
|
|
|
||
|
|
const module = createCombatState();
|
||
|
|
this.combatSignal.value = module.createInitialState();
|
||
|
|
|
||
|
|
this.add
|
||
|
|
.text(width / 2, 30, "Combat State Test", {
|
||
|
|
fontSize: "24px",
|
||
|
|
color: "#ffffff",
|
||
|
|
fontStyle: "bold",
|
||
|
|
})
|
||
|
|
.setOrigin(0.5);
|
||
|
|
|
||
|
|
this.add
|
||
|
|
.text(width / 2, 60, "Player & Enemies with Buffs / HP", {
|
||
|
|
fontSize: "14px",
|
||
|
|
color: "#aaaaaa",
|
||
|
|
})
|
||
|
|
.setOrigin(0.5);
|
||
|
|
|
||
|
|
const unitSpawner: Spawner<CombatUnitData, CombatUnitContainer> = {
|
||
|
|
getData: () => this.generateUnitData(),
|
||
|
|
getKey: (t) => t.key,
|
||
|
|
onSpawn: (t) => this.spawnUnit(t, width, height),
|
||
|
|
onUpdate: (t, obj) => obj.updateFromData(t),
|
||
|
|
onDespawn: (obj) => obj.destroy(),
|
||
|
|
};
|
||
|
|
|
||
|
|
const disposeSpawner = spawnEffect(unitSpawner);
|
||
|
|
this.disposables.add(disposeSpawner);
|
||
|
|
|
||
|
|
this.createControls(width, height);
|
||
|
|
}
|
||
|
|
|
||
|
|
private generateUnitData(): CombatUnitData[] {
|
||
|
|
const combat = this.combatSignal.value;
|
||
|
|
if (!combat) return [];
|
||
|
|
|
||
|
|
const units: CombatUnitData[] = [
|
||
|
|
{
|
||
|
|
key: "player",
|
||
|
|
entity: combat.player,
|
||
|
|
name: "Player",
|
||
|
|
isPlayer: true,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
combat.enemies.forEach((enemy, i) => {
|
||
|
|
units.push({
|
||
|
|
key: `enemy-${i}`,
|
||
|
|
entity: enemy,
|
||
|
|
name: enemy.enemy.name,
|
||
|
|
isPlayer: false,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
return units;
|
||
|
|
}
|
||
|
|
|
||
|
|
private spawnUnit(
|
||
|
|
t: CombatUnitData,
|
||
|
|
width: number,
|
||
|
|
height: number,
|
||
|
|
): CombatUnitContainer | null {
|
||
|
|
const combat = this.combatSignal.value;
|
||
|
|
if (!combat) return null;
|
||
|
|
|
||
|
|
const totalUnits = 1 + combat.enemies.length;
|
||
|
|
const spacing = 220;
|
||
|
|
const totalWidth = (totalUnits - 1) * spacing;
|
||
|
|
const startX = width / 2 - totalWidth / 2;
|
||
|
|
|
||
|
|
let x = startX;
|
||
|
|
if (t.key.startsWith("enemy-")) {
|
||
|
|
const index = parseInt(t.key.replace("enemy-", ""), 10);
|
||
|
|
x = startX + (index + 1) * spacing;
|
||
|
|
}
|
||
|
|
|
||
|
|
const container = new CombatUnitContainer(this, x, height / 2, t);
|
||
|
|
container.playSpawnEffect();
|
||
|
|
return container;
|
||
|
|
}
|
||
|
|
|
||
|
|
private createControls(width: number, height: number): void {
|
||
|
|
createButton({
|
||
|
|
scene: this,
|
||
|
|
label: "返回菜单",
|
||
|
|
x: 100,
|
||
|
|
y: 40,
|
||
|
|
onClick: async () => {
|
||
|
|
await this.sceneController.launch(SceneKey.IndexScene);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
createButton({
|
||
|
|
scene: this,
|
||
|
|
label: "Damage Player",
|
||
|
|
x: width - 520,
|
||
|
|
y: height - 40,
|
||
|
|
onClick: () => {
|
||
|
|
this.combatSignal.produce((draft) => {
|
||
|
|
if (!draft) return;
|
||
|
|
draft.player.hp = Math.max(0, draft.player.hp - 5);
|
||
|
|
if (draft.player.hp <= 0) draft.player.isAlive = false;
|
||
|
|
});
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
createButton({
|
||
|
|
scene: this,
|
||
|
|
label: "Damage Enemy",
|
||
|
|
x: width - 380,
|
||
|
|
y: height - 40,
|
||
|
|
onClick: () => {
|
||
|
|
this.combatSignal.produce((draft) => {
|
||
|
|
if (!draft) return;
|
||
|
|
const enemy = draft.enemies[0];
|
||
|
|
if (enemy) {
|
||
|
|
enemy.hp = Math.max(0, enemy.hp - 5);
|
||
|
|
if (enemy.hp <= 0) enemy.isAlive = false;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
createButton({
|
||
|
|
scene: this,
|
||
|
|
label: "Heal Player",
|
||
|
|
x: width - 240,
|
||
|
|
y: height - 40,
|
||
|
|
onClick: () => {
|
||
|
|
this.combatSignal.produce((draft) => {
|
||
|
|
if (!draft) return;
|
||
|
|
draft.player.hp = Math.min(draft.player.maxHp, draft.player.hp + 5);
|
||
|
|
if (draft.player.hp > 0) draft.player.isAlive = true;
|
||
|
|
});
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
createButton({
|
||
|
|
scene: this,
|
||
|
|
label: "Add Buff",
|
||
|
|
x: width - 100,
|
||
|
|
y: height - 40,
|
||
|
|
onClick: () => {
|
||
|
|
this.combatSignal.produce((draft) => {
|
||
|
|
if (!draft) return;
|
||
|
|
const current = draft.player.effects["strength"];
|
||
|
|
draft.player.effects["strength"] = {
|
||
|
|
data: {
|
||
|
|
id: "strength",
|
||
|
|
name: "Strength",
|
||
|
|
description: "Deal +1 damage per stack",
|
||
|
|
lifecycle: "temporary",
|
||
|
|
},
|
||
|
|
stacks: (current?.stacks ?? 0) + 1,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|