137 lines
4.6 KiB
PHP
137 lines
4.6 KiB
PHP
<?php
|
||
namespace Game\System;
|
||
|
||
use Game\Event\Event;
|
||
use Game\Event\EventListenerInterface;
|
||
use Game\Event\EventDispatcher;
|
||
use Game\Model\NPC;
|
||
use Symfony\Component\Console\Input\InputInterface;
|
||
use Symfony\Component\Console\Output\OutputInterface;
|
||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||
use Symfony\Component\Console\Question\Question;
|
||
|
||
/**
|
||
* InteractionSystem: 负责处理玩家与 NPC 的对话和交互流程。
|
||
*/
|
||
class InteractionSystem implements EventListenerInterface {
|
||
|
||
private EventDispatcher $dispatcher;
|
||
private StateManager $stateManager;
|
||
|
||
// 输入依赖
|
||
private InputInterface $input;
|
||
private OutputInterface $output;
|
||
private QuestionHelper $helper;
|
||
|
||
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 {
|
||
switch ($event->getType()) {
|
||
case 'AttemptInteractEvent':
|
||
// 假设 MapSystem 会提供当前 Tile 上的 NPC ID
|
||
$npcId = $event->getPayload()['npcId'];
|
||
$this->startInteraction($npcId);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 1. 初始化 NPC 交互
|
||
*/
|
||
private function startInteraction(string $npcId): void {
|
||
$npc = $this->loadNPC($npcId);
|
||
if (!$npc) {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈。"]));
|
||
return;
|
||
}
|
||
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||
'message' => "👤 你走近了 <fg=cyan;options=bold>{$npc->getName()}</>。"
|
||
]));
|
||
|
||
// 启动对话流程
|
||
$this->dialogueLoop($npc);
|
||
|
||
// 交互结束后,重新打印主菜单请求
|
||
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||
}
|
||
|
||
/**
|
||
* 2. 核心对话循环
|
||
*/
|
||
private function dialogueLoop(NPC $npc): void {
|
||
$currentDialogueKey = 'greeting';
|
||
$running = true;
|
||
|
||
while ($running) {
|
||
|
||
// 打印 NPC 对话
|
||
$text = $npc->getDialogueText($currentDialogueKey);
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||
'message' => "<fg=cyan>{$npc->getName()}</>:<fg=white>{$text}</>"
|
||
]));
|
||
|
||
// 检查交互类型并获取玩家选择
|
||
$choice = $this->promptPlayerChoice();
|
||
|
||
switch ($choice) {
|
||
case 'T': // 触发任务/特殊事件
|
||
$this->dispatcher->dispatch(new Event('QuestCheckEvent', ['npcId' => $npc->id]));
|
||
$currentDialogueKey = 'quest_response';
|
||
break;
|
||
case 'S': // 触发商店
|
||
$this->dispatcher->dispatch(new Event('OpenShopEvent', ['npcId' => $npc->id]));
|
||
$currentDialogueKey = 'shop_response';
|
||
break;
|
||
case 'E': // 结束对话
|
||
$running = false;
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "🤝 你结束了与 {$npc->getName()} 的对话。"]));
|
||
break;
|
||
default:
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的交互指令。']));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取玩家交互指令
|
||
*/
|
||
private function promptPlayerChoice(): string {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||
'message' => "\n--- 交互菜单 --- [T] 任务 | [S] 商店 | [E] 结束"
|
||
]));
|
||
|
||
$question = new Question("> 请选择指令 (T/S/E):");
|
||
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
||
|
||
return $choice;
|
||
}
|
||
|
||
/**
|
||
* 模拟从配置中加载 NPC 数据
|
||
*/
|
||
private function loadNPC(string $id): ?NPC {
|
||
$data = match ($id) {
|
||
'VILLAGER_1' => [
|
||
'name' => '老村长',
|
||
'dialogue' => [
|
||
'greeting' => '你好,旅行者。你看起来很强大。',
|
||
'quest_response' => '你想要帮忙吗?我们的地窖里有老鼠。',
|
||
'shop_response' => '我现在没有东西卖给你。',
|
||
]
|
||
],
|
||
default => null,
|
||
};
|
||
|
||
if ($data) {
|
||
return new NPC($id, $data['name'], $data['dialogue']);
|
||
}
|
||
return null;
|
||
}
|
||
} |