fanren/src/System/BattleService.php
2025-12-16 13:49:05 +08:00

275 lines
9.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Game\System;
use Game\Event\Event;
use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher;
use Game\Model\Enemy;
use Game\Model\Player; // 即使是 Party也依赖 Player
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\Question;
/**
* BattleService: 负责处理回合制战斗逻辑。
* 战斗循环采用阻塞模式 (CLI 常见),直接获取输入。
*/
class BattleService implements EventListenerInterface {
private EventDispatcher $dispatcher;
private StateManager $stateManager;
// 战斗内输入依赖 (暂时不解耦,简化战斗循环)
private InputInterface $input;
private OutputInterface $output;
private QuestionHelper $helper;
private bool $inBattle = false;
private ?Enemy $currentEnemy = null; // 使用 ?Type 确保在非战斗状态下为 null
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, InputInterface $input, OutputInterface $output, QuestionHelper $helper) {
$this->dispatcher = $dispatcher;
$this->stateManager = $stateManager;
$this->input = $input;
$this->output = $output;
$this->helper = $helper;
}
public function handleEvent(Event $event): void {
if ($this->inBattle) {
switch ($event->getType()) {
case 'AbilityEffectApplied': // ⭐ 监听技能施放效果
$target = $event->getPayload()['target'];
if ($target instanceof Enemy && !$target->isAlive()) {
$this->handleWin();
return; // 战斗结束
}
// 如果是玩家治疗,则无需返回
break;
}
return;
}
switch ($event->getType()) {
case 'StartBattleEvent':
// 收到 MapSystem 触发的战斗开始事件
$enemyId = $event->getPayload()['enemyId'];
$this->startBattle($enemyId);
break;
}
}
/**
* 1. 初始化战斗状态
*/
private function startBattle(int $enemyId): void {
// ... 初始化敌人逻辑 (继承自 Character) ...
$enemyData = $this->loadEnemyData($enemyId);
$this->currentEnemy = new Enemy(
(string)$enemyId,
$enemyData['name'],
$enemyData['health'],
$enemyData['attack'],
$enemyData['defense'],
$enemyData['xp']
);
$this->inBattle = true;
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "⚔️ 你遭遇了 <fg=red;options=bold>{$this->currentEnemy->getName()}</>!战斗开始!"
]));
// 立即进入战斗循环
$this->battleLoop();
}
/**
* 2. 核心战斗循环
*/
private function battleLoop(): void {
while ($this->inBattle) {
// 确保 UI 服务打印了战斗状态
$this->dispatcher->dispatch(new Event('ShowBattleStatsEvent', [
'enemy' => $this->currentEnemy,
'player' => $this->stateManager->getPlayer(),
]));
// 玩家回合 (阻塞输入)
$playerAction = $this->promptPlayerAction();
if ($playerAction === 'A') {
$this->playerAttack();
}elseif ($playerAction === 'C') { // ⭐ 施法逻辑
$this->handleAbilityInput();
} elseif ($playerAction === 'R') {
if ($this->tryRunAway()) {
$this->endBattle(false);
return;
}
}
if (!$this->inBattle) break;
// 敌人回合
$this->enemyAttack();
if (!$this->inBattle) break;
}
}
private function handleAbilityInput(): void {
$player = $this->stateManager->getPlayer();
$abilities = $player->getAbilities();
if (empty($abilities)) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '你还没有学会任何技能!']));
return;
}
$this->output->writeln("\n--- 魔法技能 ---");
$availableIds = [];
foreach ($abilities as $id => $ability) {
$canCast = $player->getMana() >= $ability->manaCost ? "<fg=green>" : "<fg=red>";
$this->output->writeln(" [{$id}] {$ability->name} | 消耗: {$canCast}{$ability->manaCost} MP</>");
$availableIds[] = $id;
}
$this->output->writeln("----------------");
$question = new Question("> 请输入技能 ID (或 X 取消)");
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
if ($choice === 'X') return;
if (in_array($choice, $availableIds)) {
// ⭐ 触发事件,将处理权交给 AbilityService
$this->dispatcher->dispatch(new Event('CastAbilityEvent', [
'abilityId' => $choice,
'target' => $this->currentEnemy // 暂定为当前敌人
]));
} else {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的技能 ID。']));
}
}
/**
* 获取玩家战斗指令 (直接使用注入的 I/O 接口)
*/
private function promptPlayerAction(): string {
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "\n--- 你的回合 --- [A] 攻击 | [C] 施法 | [R] 逃跑" // ⭐ 增加 C
]));
$question = new Question("> 请选择指令 (A/C/R)");
// 关键:使用注入的 I/O 接口
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
// 简单的输入验证
if (in_array($choice, ['A', 'C', 'R'])) { // ⭐ 增加 C
return $choice;
} else {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的战斗指令。']));
return $this->promptPlayerAction();
}
}
/**
* 玩家攻击逻辑
*/
private function playerAttack(): void {
$player = $this->stateManager->getPlayer();
$rawDamage = $player->getAttack();
$actualDamage = $this->currentEnemy->takeDamage($rawDamage);
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "⚡ 你对 {$this->currentEnemy->getName()} 造成了 <fg=yellow>{$actualDamage}</> 点伤害!"
]));
if (!$this->currentEnemy->isAlive()) {
$this->handleWin();
}
}
/**
* 敌人攻击逻辑
*/
private function enemyAttack(): void {
$player = $this->stateManager->getPlayer();
$rawDamage = $this->currentEnemy->getAttack();
// Player 的 takeDamage 会更新 StateManager 中的 Player 实例
$actualDamage = $player->takeDamage($rawDamage);
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "💥 {$this->currentEnemy->getName()} 对你造成了 <fg=red>{$actualDamage}</> 点伤害!"
]));
if (!$player->isAlive()) {
$this->handleLoss();
}
}
/**
* 尝试逃跑
*/
private function tryRunAway(): bool {
if (rand(1, 100) > 50) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "💨 你成功逃离了战斗!"]));
return true;
} else {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "{$this->currentEnemy->getName()} 拦住了!逃跑失败。"]));
return false;
}
}
/**
* 3. 战斗胜利处理
*/
private function handleWin(): void {
$xpGained = $this->currentEnemy->getXpValue();
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "🏆 恭喜你击败了 {$this->currentEnemy->getName()}"
]));
// 触发 XP 和 Loot 事件,交由其他系统处理
$this->dispatcher->dispatch(new Event('XpGainedEvent', ['xp' => $xpGained]));
$this->dispatcher->dispatch(new Event('LootDropEvent', ['enemyId' => $this->currentEnemy->getId()]));
$this->endBattle(true);
}
/**
* 4. 战斗失败处理
*/
private function handleLoss(): void {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "💀 你被击败了... 游戏结束。"]));
// TODO: 触发 GameEndEvent 或 RespawnEvent
$this->endBattle(false);
}
/**
* 结束战斗状态
*/
private function endBattle(bool $isWin): void {
$this->inBattle = false;
$this->currentEnemy = null;
// 战斗结束后,重新打印主菜单请求,以继续主循环
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
}
/**
* 模拟从配置中加载敌人数据
*/
private function loadEnemyData(int $id): array {
// 在实际项目中,这应该从数据库或 JSON 文件加载
return match ($id) {
1 => ['name' => '弱小的哥布林', 'health' => 20, 'attack' => 5, 'defense' => 1, 'xp' => 10],
2 => ['name' => '愤怒的野猪', 'health' => 35, 'attack' => 8, 'defense' => 3, 'xp' => 25],
3 => ['name' => '森林狼', 'health' => 40, 'attack' => 10, 'defense' => 5, 'xp' => 40],
default => ['name' => '未知生物', 'health' => 1, 'attack' => 1, 'defense' => 0, 'xp' => 1],
};
}
}