fanren/src/System/DialogueService.php
2025-12-22 18:07:05 +08:00

164 lines
5.4 KiB
PHP

<?php
namespace Game\System;
use Game\Event\Event;
use Game\Event\EventDispatcher;
use Game\Model\DialogueNode;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\Question;
use Game\Event\EventListenerInterface; // ⭐ Add import
/**
* DialogueService: 负责处理对话树的流程逻辑和交互。
*/
class DialogueService implements EventListenerInterface {
private EventDispatcher $dispatcher;
private StateManager $stateManager; // ⭐ Add StateManager
private InputInterface $input;
private OutputInterface $output;
private QuestionHelper $helper;
private array $currentDialogueTree = [];
private string $currentNodeId = '';
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 'StartDialogueEvent':
$dialogue = $event->getPayload()['dialogue'];
$this->startDialogue($dialogue);
break;
case 'DialogueChoice': // ⭐ Listen for choice event
$choice = $event->getPayload()['choice'];
$this->handleChoice($choice);
break;
}
}
/**
* 开始一段对话 (非阻塞,初始化状态)
*/
public function startDialogue(array $dialogueTree): void {
$this->currentDialogueTree = $dialogueTree;
$this->currentNodeId = 'root';
// 切换游戏模式
$this->stateManager->setMode(StateManager::MODE_DIALOGUE);
// 显示第一个节点
$this->displayCurrentNode();
}
private function displayCurrentNode(): void {
if (!isset($this->currentDialogueTree[$this->currentNodeId])) {
$this->endDialogue();
return;
}
$nodeData = $this->currentDialogueTree[$this->currentNodeId];
$node = DialogueNode::fromArray($this->currentNodeId, $nodeData);
$this->output->writeln("\n<fg=cyan>🗣️ 对话</>");
$this->output->writeln("<fg=white>" . str_repeat("-", 40) . "</>");
$lines = explode("\n", $node->text);
foreach ($lines as $line) {
$this->output->writeln(" " . trim($line));
}
$this->output->writeln("<fg=white>" . str_repeat("-", 40) . "</>\n");
if (empty($node->options)) {
$this->output->writeln(" [按回车键或输入任意值结束]");
} else {
foreach ($node->options as $index => $option) {
$this->output->writeln(" [<fg=yellow>{$index}</>] {$option['text']}");
}
}
}
/**
* 处理玩家选择
*/
private function handleChoice(string $input): void {
if (!isset($this->currentDialogueTree[$this->currentNodeId])) {
$this->endDialogue();
return;
}
$nodeData = $this->currentDialogueTree[$this->currentNodeId];
$node = DialogueNode::fromArray($this->currentNodeId, $nodeData);
// 如果没有选项,任意输入都结束对话 (或者跳转 next)
if (empty($node->options)) {
$this->endDialogue();
return;
}
if (!is_numeric($input)) {
$this->output->writeln("<fg=red>输入无效,请输入选项数字。</>");
// 这里不需要循环,直接返回,等待下一次输入事件
return;
}
$index = (int)$input;
if (!isset($node->options[$index])) {
$this->output->writeln("<fg=red>选项不存在。</>");
return;
}
// 处理选中项
$selectedOption = $node->options[$index];
// 1. 执行动作
if (!empty($selectedOption['action'])) {
$this->handleAction($selectedOption['action']);
}
// 2. 跳转
if (empty($selectedOption['next'])) {
$this->endDialogue();
} else {
$this->currentNodeId = $selectedOption['next'];
$this->displayCurrentNode();
}
}
private function endDialogue(): void {
$this->currentDialogueTree = [];
$this->currentNodeId = '';
$this->output->writeln("\n[对话结束]");
// 恢复地图模式 (或者之前的模式,稍微简化处理)
$this->stateManager->setMode(StateManager::MODE_MAP);
}
private function handleAction(string $actionString): void {
$parts = explode(':', $actionString, 2);
$actionType = $parts[0];
$payload = $parts[1] ?? null;
switch ($actionType) {
case 'accept_quest':
if ($payload) {
$this->dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => $payload]));
}
break;
case 'open_shop':
if ($payload) {
$this->dispatcher->dispatch(new Event('OpenShopEvent', ['npcId' => $payload]));
}
break;
}
}
}