diff --git a/save/player.json b/save/player.json
new file mode 100644
index 0000000..1c3ec1d
--- /dev/null
+++ b/save/player.json
@@ -0,0 +1,23 @@
+{
+ "name": "hant",
+ "health": 100,
+ "maxHealth": 100,
+ "attack": 15,
+ "defense": 5,
+ "level": 1,
+ "currentXp": 10,
+ "xpToNextLevel": 100,
+ "gold": 7,
+ "inventory": [
+ {
+ "id": 1,
+ "name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
+ "type": "potion",
+ "description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
+ "value": 10,
+ "effects": []
+ }
+ ],
+ "activeQuests": [],
+ "completedQuests": []
+}
\ No newline at end of file
diff --git a/src/Core/GameCommand.php b/src/Core/GameCommand.php
index 91c5156..d598f21 100644
--- a/src/Core/GameCommand.php
+++ b/src/Core/GameCommand.php
@@ -8,9 +8,11 @@ use Game\System\BattleService;
use Game\System\CharacterService;
use Game\System\InputHandler;
use Game\System\InteractionSystem;
+use Game\System\ItemService;
use Game\System\LootService;
use Game\System\MapSystem;
use Game\System\QuestService;
+use Game\System\ShopService;
use Game\System\StateManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -41,77 +43,52 @@ class GameCommand extends Command {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- // 1. 初始化数据库管理器
- $this->dbManager = new DatabaseManager();
- $this->dbManager->loadInitialData();
- // 2. 初始化 Event Dispatcher 和所有服务
- $this->initializeServices($input, $output);
+ // 1. 初始化服务容器并注册所有系统
+ $container = new ServiceContainer($input, $output, $this->getHelperSet());
+ $this->eventDispatcher = $container->registerServices();
+ $this->inputHandler = $container->getInputHandler();
+ $this->stateManager = $container->getStateManager();
+ $saveLoadService = $container->getSaveLoadService(); // ⭐ 获取 SaveLoadService
- // 3. 角色创建/加载存档
+ $player = null;
$helper = $this->getHelper('question');
- $question = new Question("请输入你的角色名称:", "旅行者");
- $playerName = $helper->ask($input, $output, $question);
- // 创建玩家实例
- $player = new Player($playerName, 100, 15, 5);
+ // 2. ⭐ 存档/加载逻辑
+ if ($saveLoadService->hasSaveFile()) {
+ $player = $saveLoadService->loadGame();
+// $output->writeln("\n检测到存档文件!");
+// $question = new Question("是否加载存档? ([Y]是 / [N]新建):", "Y");
+// $choice = strtoupper($helper->ask($input, $output, $question));
+//
+// if ($choice === 'Y') {
+// $player = $saveLoadService->loadGame();
+// }
+ }
- // **将玩家实例交给 StateManager 管理**
+ if (!$player) {
+ // 新建角色逻辑
+ $question = new Question("请输入你的角色名称:", "旅行者");
+ $playerName = $helper->ask($input, $output, $question);
+
+ // 创建玩家实例 (初始属性)
+ $player = new Player($playerName, 100, 15, 5);
+ $this->eventDispatcher->dispatch(new Event('SystemMessage', ['message' => "角色 {$player->getName()} 创建成功!"]));
+ }
+
+ // 3. 将玩家实例交给 StateManager 管理
$this->stateManager->setPlayer($player);
- // 通知 UI 服务
- $this->eventDispatcher->dispatch(new Event('SystemMessage', ['message' => "角色 {$player->getName()} 创建成功!"]));
+ // 4. ⭐ 增加存档命令到 InputHandler
+ $this->inputHandler->setSaveLoadService($saveLoadService);
- return $this->mainLoop($input, $output);
- }
-
- private function initializeServices(InputInterface $input, OutputInterface $output): void {
-
- // 实例化 QuestionHelper
- $questionHelper = $this->getHelper('question');
- // 实例化 Event Dispatcher
- $this->eventDispatcher = new EventDispatcher();
- $this->stateManager = new StateManager($this->dbManager->getConnection());
-
-
-
- // ⭐ 实例化 CharacterService
- $characterService = new CharacterService($this->eventDispatcher, $this->stateManager);
- $this->eventDispatcher->registerListener($characterService);
-
- // ⭐ 实例化 LootService
- $lootService = new LootService($this->eventDispatcher, $this->stateManager);
- $this->eventDispatcher->registerListener($lootService);
-
- // ⭐ 实例化 InteractionSystem
- $interactionSystem = new InteractionSystem($this->eventDispatcher, $this->stateManager, $input, $output, $questionHelper);
- $this->eventDispatcher->registerListener($interactionSystem);
-
- // ⭐ 实例化 QuestService
- $questService = new QuestService($this->eventDispatcher, $this->stateManager);
- $this->eventDispatcher->registerListener($questService);
-
- // ⭐ 实例化 InputHandler 并注入依赖
- $this->inputHandler = new InputHandler($this->eventDispatcher, $input, $output, $questionHelper);
-
- // 实例化和注册 UIService (监听器)
- $this->uiService = new UIService($output, $this->stateManager);
- $this->eventDispatcher->registerListener($this->uiService);
-
- // MapSystem 注册 (需要 EventDispatcher 和 DB 连接)
- $this->mapSystem = new MapSystem($this->eventDispatcher, $this->stateManager);
- $this->eventDispatcher->registerListener($this->mapSystem);
-
- $questionHelper = $this->getHelper('question');
- $battleService = new BattleService($this->eventDispatcher, $this->stateManager, $input,$output, $questionHelper);
- $this->eventDispatcher->registerListener($battleService);
-
- // 触发一个初始事件,让 UI 服务打印欢迎信息
+ // 5. 触发启动事件
$welcomeEvent = new Event('GameStartEvent', ['message' => '核心系统已就绪,请输入指令开始游戏。']);
$this->eventDispatcher->dispatch($welcomeEvent);
- }
- // src/Core/GameCommand.php (mainLoop 方法片段)
+ // 6. 启动主循环
+ return $this->mainLoop($input, $output);
+ }
private function mainLoop(InputInterface $input, OutputInterface $output): int {
$running = true;
while ($running) {
diff --git a/src/Core/ServiceContainer.php b/src/Core/ServiceContainer.php
new file mode 100644
index 0000000..ae0b452
--- /dev/null
+++ b/src/Core/ServiceContainer.php
@@ -0,0 +1,127 @@
+input = $input;
+ $this->output = $output;
+ $this->questionHelper = $helperSet->get('question');
+ }
+
+ /**
+ * 初始化核心基础设施
+ */
+ private function initializeInfrastructure(): void {
+ // 数据库
+ $this->dbManager = new DatabaseManager();
+ $this->dbManager->loadInitialData();
+
+ // 事件分发器 (核心)
+ $this->eventDispatcher = new EventDispatcher();
+
+ // 状态管理器
+ $this->stateManager = new StateManager($this->dbManager->getConnection());
+ }
+
+ /**
+ * 实例化并注册所有系统服务
+ */
+ public function registerServices(): EventDispatcher {
+ $this->initializeInfrastructure();
+ $this->saveLoadService = new SaveLoadService($this->eventDispatcher, $this->stateManager);
+ // 1. UI 服务 (只需要 Dispatcher 和 StateManager)
+ $this->register(UIService::class, new UIService($this->output, $this->stateManager));
+
+ // 2. 核心逻辑服务 (依赖 Dispatcher, StateManager)
+ $this->register(MapSystem::class, new MapSystem($this->eventDispatcher, $this->stateManager));
+ $this->register(CharacterService::class, new CharacterService($this->eventDispatcher, $this->stateManager));
+ $this->register(LootService::class, new LootService($this->eventDispatcher, $this->stateManager));
+ $this->register(ItemService::class, new ItemService($this->eventDispatcher, $this->stateManager));
+ $this->register(QuestService::class, new QuestService($this->eventDispatcher, $this->stateManager));
+
+ // 3. I/O 交互服务 (依赖 Dispatcher, StateManager, I/O 接口)
+ $this->register(InteractionSystem::class,
+ new InteractionSystem($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
+ );
+ $this->register(BattleService::class,
+ new BattleService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
+ );
+ $this->register(ShopService::class,
+ new ShopService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
+ );
+
+ // ⭐ 实例化 AbilityService
+ $abilityService = new AbilityService($this->eventDispatcher, $this->stateManager);
+ $this->register(AbilityService::class, $abilityService);
+
+ // 4. 输入处理服务 (驱动主循环,必须最后注册,因为它依赖于所有 I/O 组件)
+ $this->register(InputHandler::class,
+ new InputHandler($this->eventDispatcher, $this->input, $this->output, $this->questionHelper)
+ );
+
+ // 返回分发器和状态管理器,供 GameCommand 使用
+ return $this->eventDispatcher;
+ }
+
+ public function getSaveLoadService(): SaveLoadService {
+ return $this->saveLoadService;
+ }
+
+ /**
+ * 注册服务并将其作为监听器添加到 Event Dispatcher
+ */
+ private function register(string $key, object $service): void {
+ $this->services[$key] = $service;
+ // 仅注册实现了 EventListenerInterface 的服务
+ if ($service instanceof EventListenerInterface) {
+ $this->eventDispatcher->registerListener($service);
+ }
+ }
+
+ // 可选:添加 Getter 方法,方便在 GameCommand 中获取 Player 或 InputHandler
+ public function getInputHandler(): InputHandler {
+ return $this->services[InputHandler::class];
+ }
+
+ public function getStateManager(): StateManager {
+ return $this->stateManager;
+ }
+}
\ No newline at end of file
diff --git a/src/Model/Ability.php b/src/Model/Ability.php
new file mode 100644
index 0000000..53c8454
--- /dev/null
+++ b/src/Model/Ability.php
@@ -0,0 +1,23 @@
+id = $id;
+ $this->name = $name;
+ $this->type = $type;
+ $this->manaCost = $manaCost;
+ $this->power = $power;
+ $this->scaling = $scaling;
+ }
+}
\ No newline at end of file
diff --git a/src/Model/Character.php b/src/Model/Character.php
index c02c4ef..ae1f572 100644
--- a/src/Model/Character.php
+++ b/src/Model/Character.php
@@ -11,66 +11,38 @@ class Character {
protected int $attack;
protected int $defense;
- protected array $activeQuests = []; // 存储进行中的任务进度,格式: ['questId' => ['currentCount' => 0, 'isCompleted' => false]]
- protected array $completedQuests = []; // 存储已完成的任务 ID
-// ⭐ 新增方法:添加/接受任务
- public function addActiveQuest(string $questId, int $targetCount): void {
- $this->activeQuests[$questId] = ['currentCount' => 0, 'isCompleted' => false, 'targetCount' => $targetCount];
- }
+// ⭐ 新增:魔法值 (MP/Mana)
+ protected int $mana;
+ protected int $maxMana;
- // ⭐ 新增方法:获取进行中的任务
- public function getActiveQuests(): array {
- return $this->activeQuests;
- }
+ // ⭐ 新增 Getter/Setter for Mana
+ public function getMana(): int { return $this->mana; }
+ public function getMaxMana(): int { return $this->maxMana; }
- // ⭐ 新增方法:更新任务进度
- public function updateQuestProgress(string $questId, int $count = 1): void {
- if (isset($this->activeQuests[$questId])) {
- $progress = &$this->activeQuests[$questId]; // 使用引用
- if (!$progress['isCompleted']) {
- $progress['currentCount'] += $count;
- if ($progress['currentCount'] >= $progress['targetCount']) {
- $progress['currentCount'] = $progress['targetCount'];
- $progress['isCompleted'] = true;
- // 触发 QuestCompletedEventRequest
- // 注意:实际的奖励和标记完成应在 QuestService 确认后进行
- }
- }
+ public function spendMana(int $cost): bool {
+ if ($this->mana >= $cost) {
+ $this->mana -= $cost;
+ return true;
}
+ return false;
}
- // ⭐ 新增方法:标记任务完成
- public function markQuestCompleted(string $questId): void {
- unset($this->activeQuests[$questId]);
- $this->completedQuests[] = $questId;
+ public function restoreMana(int $amount): void {
+ $this->mana = min($this->maxMana, $this->mana + $amount);
}
-
- // ⭐ 新增方法:检查任务是否已完成
- public function isQuestCompleted(string $questId): bool {
- return in_array($questId, $this->completedQuests);
- }
-
- // ⭐ 新增:玩家背包 (存储 Item 实例)
- protected array $inventory = [];
-
- // ... 现有构造函数和 Getter ...
-
- // ⭐ 新增方法:添加物品到背包
- public function addItem(Item $item): void {
- $this->inventory[] = $item;
- }
-
- // ⭐ 新增方法:获取背包
- public function getInventory(): array {
- return $this->inventory;
- }
-
- public function __construct(string $name, int $maxHealth, int $attack, int $defense) {
+ public function __construct(string $name, int $maxHealth, int $attack, int $defense, int $maxMana = 0) {
$this->name = $name;
- $this->maxHealth = $maxHealth;
- $this->health = $maxHealth;
+
$this->attack = $attack;
$this->defense = $defense;
+ // ... 现有初始化 ...
+ $this->maxHealth = $maxHealth;
+ $this->health = $maxHealth;
+ // ...
+
+ // ⭐ 初始化 Mana
+ $this->maxMana = $maxMana;
+ $this->mana = $maxMana;
}
public function getName(): string { return $this->name; }
@@ -96,10 +68,12 @@ class Character {
/**
* 治疗角色
*/
- public function heal(int $amount): void {
+ public function heal(int $amount): int {
+ $old = $this->health;
$this->health += $amount;
if ($this->health > $this->maxHealth) {
$this->health = $this->maxHealth;
}
+ return $this->health - $old;
}
}
\ No newline at end of file
diff --git a/src/Model/Item.php b/src/Model/Item.php
index 4299e3b..3be8179 100644
--- a/src/Model/Item.php
+++ b/src/Model/Item.php
@@ -10,12 +10,25 @@ class Item {
public string $type; // e.g., 'potion', 'weapon', 'material'
public string $description;
public int $value; // 卖出价格
-
- public function __construct(int $id, string $name, string $type, string $description, int $value) {
+ public array $effects; // ⭐ 新增:存储效果参数 e.g., ['heal' => 20]
+ public function __construct(int $id, string $name, string $type, string $description, int $value, array $effects = []) {
$this->id = $id;
$this->name = $name;
$this->type = $type;
$this->description = $description;
$this->value = $value;
+ $this->effects = $effects; // 赋值
+ }
+
+ public function toArray()
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'type' => $this->type,
+ 'description' => $this->description,
+ 'value' => $this->value,
+ 'effects' => $this->effects,
+ ];
}
}
\ No newline at end of file
diff --git a/src/Model/Player.php b/src/Model/Player.php
index 8b1979d..c10f0a3 100644
--- a/src/Model/Player.php
+++ b/src/Model/Player.php
@@ -7,9 +7,89 @@ class Player extends Character {
protected int $currentXp = 0;
protected int $xpToNextLevel = 100;
- public function __construct(string $name, int $maxHealth, int $attack, int $defense) {
+ // ⭐ 新增:已学习的技能列表
+ protected array $abilities = []; // 存储 Ability 实例
+
+ public function __construct(string $name, int $maxHealth, int $attack, int $defense,int $maxMana = 50) {
// 调用父类 (Character) 的构造函数来初始化核心属性
- parent::__construct($name, $maxHealth, $attack, $defense);
+ parent::__construct($name, $maxHealth, $attack, $defense,$maxMana);
+ }
+
+ // ⭐ 新增方法:学习技能
+ public function learnAbility(Ability $ability): void {
+ $this->abilities[$ability->id] = $ability;
+ }
+
+ // ⭐ 新增方法:获取所有技能
+ public function getAbilities(): array {
+ return $this->abilities;
+ }
+
+ // ⭐ 新增:货币属性
+ protected int $gold = 0;
+
+ // ... 现有构造函数和 Getter ...
+
+ // ⭐ 新增 Getter
+ public function getGold(): int { return $this->gold; }
+
+ // ⭐ 新增 Setter/Modifier
+ public function gainGold(int $amount): void {
+ $this->gold += $amount;
+ }
+
+ protected array $activeQuests = []; // 存储进行中的任务进度,格式: ['questId' => ['currentCount' => 0, 'isCompleted' => false]]
+ protected array $completedQuests = []; // 存储已完成的任务 ID
+// ⭐ 新增方法:添加/接受任务
+ public function addActiveQuest(string $questId, int $targetCount): void {
+ $this->activeQuests[$questId] = ['currentCount' => 0, 'isCompleted' => false, 'targetCount' => $targetCount];
+ }
+
+ // ⭐ 新增方法:获取进行中的任务
+ public function getActiveQuests(): array {
+ return $this->activeQuests;
+ }
+
+ // ⭐ 新增方法:更新任务进度
+ public function updateQuestProgress(string $questId, int $count = 1): void {
+ if (isset($this->activeQuests[$questId])) {
+ $progress = &$this->activeQuests[$questId]; // 使用引用
+ if (!$progress['isCompleted']) {
+ $progress['currentCount'] += $count;
+ if ($progress['currentCount'] >= $progress['targetCount']) {
+ $progress['currentCount'] = $progress['targetCount'];
+ $progress['isCompleted'] = true;
+ // 触发 QuestCompletedEventRequest
+ // 注意:实际的奖励和标记完成应在 QuestService 确认后进行
+ }
+ }
+ }
+ }
+
+ // ⭐ 新增方法:标记任务完成
+ public function markQuestCompleted(string $questId): void {
+ unset($this->activeQuests[$questId]);
+ $this->completedQuests[] = $questId;
+ }
+
+ // ⭐ 新增方法:检查任务是否已完成
+ public function isQuestCompleted(string $questId): bool {
+ return in_array($questId, $this->completedQuests);
+ }
+
+ // ⭐ 新增:玩家背包 (存储 Item 实例)
+ protected array $inventory = [];
+
+ // ... 现有构造函数和 Getter ...
+
+ // ⭐ 新增方法:添加物品到背包
+ public function addItem(Item $item): void {
+ $this->inventory[] = $item;
+ }
+
+ // ⭐ 新增方法:获取背包
+ public function getInventory(): array {
+ return $this->inventory;
}
// Player 特有的 Getter 方法
@@ -22,4 +102,33 @@ class Player extends Character {
$this->currentXp += $amount;
// TODO: 未来在这里实现升级逻辑 (LevelUpEvent)
}
+
+ public function removeItemByIndex(int $index): bool {
+ if (isset($this->inventory[$index])) {
+ unset($this->inventory[$index]);
+ // 重新索引数组,确保背包索引连续
+ $this->inventory = array_values($this->inventory);
+ return true;
+ }
+ return false;
+ }
+
+ // ⭐ 新增 Player 特有的 Setter:
+ public function setLevel(int $level): void { $this->level = $level; }
+ public function setCurrentXp(int $xp): void { $this->currentXp = $xp; }
+ public function setXpToNextLevel(int $xp): void { $this->xpToNextLevel = $xp; }
+ public function setGold(int $gold): void { $this->gold = $gold; }
+ public function setInventory(array $inventory): void {
+ // WARNING: 这里的 inventory 数组可能只包含序列化数据,需要确保 Item 实例化
+ // 在我们当前简化的JSON方案中,暂且直接赋值原始数据。
+ $this->inventory = $inventory;
+ }
+ public function getCompletedQuests(): array { return $this->completedQuests; } // 确保这个 Getter 存在
+ public function setCompletedQuests(array $quests): void { $this->completedQuests = $quests; }
+ public function setActiveQuests(array $quests): void { $this->activeQuests = $quests; }
+
+ public function setHealth(mixed $health)
+ {
+ $this->health = $health;
+ }
}
\ No newline at end of file
diff --git a/src/System/AbilityService.php b/src/System/AbilityService.php
new file mode 100644
index 0000000..76b9737
--- /dev/null
+++ b/src/System/AbilityService.php
@@ -0,0 +1,114 @@
+dispatcher = $dispatcher;
+ $this->stateManager = $stateManager;
+ $this->loadAbilityData();
+ }
+
+ private function loadAbilityData(): void {
+ // 模拟加载所有技能数据
+ $this->abilityData = [
+ 'FIREBALL' => ['name' => '火球术', 'type' => 'damage', 'cost' => 10, 'power' => 25, 'scaling' => 'mana'],
+ 'HEAL' => ['name' => '初级治疗', 'type' => 'heal', 'cost' => 8, 'power' => 15, 'scaling' => 'mana'],
+ ];
+ }
+
+ /**
+ * 在游戏开始或升级时,让玩家学习初始技能
+ */
+ public function learnInitialAbilities(Player $player): void {
+ // 确保 FIREBALL 存在
+ if (isset($this->abilityData['FIREBALL'])) {
+ $data = $this->abilityData['FIREBALL'];
+ $player->learnAbility(new Ability('FIREBALL', $data['name'], $data['type'], $data['cost'], $data['power'], $data['scaling']));
+ }
+ if (isset($this->abilityData['HEAL'])) {
+ $data = $this->abilityData['HEAL'];
+ $player->learnAbility(new Ability('HEAL', $data['name'], $data['type'], $data['cost'], $data['power'], $data['scaling']));
+ }
+ }
+
+ public function handleEvent(Event $event): void {
+ switch ($event->getType()) {
+ case 'GameStartEvent':
+ // 确保在游戏开始时玩家获得技能
+ $this->learnInitialAbilities($this->stateManager->getPlayer());
+ break;
+ case 'CastAbilityEvent': // 响应 BattleService 的请求
+ $this->handleCastAbility($event->getPayload()['abilityId'], $event->getPayload()['target']);
+ break;
+ // TODO: 未来监听 LevelUpEvent 来学习新技能
+ }
+ }
+
+ /**
+ * 处理技能施放的核心逻辑
+ */
+ private function handleCastAbility(string $abilityId, Enemy $target): void {
+ $player = $this->stateManager->getPlayer();
+ $ability = $player->getAbilities()[$abilityId] ?? null;
+
+ if (!$ability) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 未知的技能。']));
+ return;
+ }
+
+ // 1. 检查资源消耗
+ if (!$player->spendMana($ability->manaCost)) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ MP不足,无法施放 ' . $ability->name . '。']));
+ return;
+ }
+
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "✨ {$player->getName()} 施放了 {$ability->name}>!(-{$ability->manaCost} MP)"]));
+
+ // 2. 计算效果
+ $damage = 0;
+ $heal = 0;
+
+ $scalingValue = match ($ability->scaling) {
+ 'attack' => $player->getAttack(),
+ 'mana' => $player->getMaxMana(), // 魔法值越高,技能越强
+ default => 0,
+ };
+
+ // 基础伤害计算:威力 + (加成属性 * 0.5)
+ $rawEffect = $ability->power + (int)($scalingValue * 0.5);
+
+ // 3. 应用效果
+ switch ($ability->type) {
+ case 'damage':
+ $damage = $target->takeDamage($rawEffect);
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "💥 {$ability->name} 对 {$target->getName()} 造成了 {$damage}> 点伤害!"
+ ]));
+ break;
+ case 'heal':
+ $heal = $player->heal($rawEffect);
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "💖 恢复了 {$heal}> 点生命值!"
+ ]));
+ break;
+ }
+
+ // 4. 触发 BattleService 的事件,让其检查战斗是否结束
+ $this->dispatcher->dispatch(new Event('AbilityEffectApplied', ['target' => $target]));
+ }
+}
\ No newline at end of file
diff --git a/src/System/BattleService.php b/src/System/BattleService.php
index b95bd6f..1c436c9 100644
--- a/src/System/BattleService.php
+++ b/src/System/BattleService.php
@@ -38,8 +38,16 @@ class BattleService implements EventListenerInterface {
public function handleEvent(Event $event): void {
if ($this->inBattle) {
- // 如果在战斗中,可以监听 'BattleCommandEvent' 等事件来处理输入
- // 当前版本,我们通过 battleLoop() 内部阻塞输入
+ switch ($event->getType()) {
+ case 'AbilityEffectApplied': // ⭐ 监听技能施放效果
+ $target = $event->getPayload()['target'];
+ if ($target instanceof Enemy && !$target->isAlive()) {
+ $this->handleWin();
+ return; // 战斗结束
+ }
+ // 如果是玩家治疗,则无需返回
+ break;
+ }
return;
}
@@ -93,6 +101,8 @@ class BattleService implements EventListenerInterface {
if ($playerAction === 'A') {
$this->playerAttack();
+ }elseif ($playerAction === 'C') { // ⭐ 施法逻辑
+ $this->handleAbilityInput();
} elseif ($playerAction === 'R') {
if ($this->tryRunAway()) {
$this->endBattle(false);
@@ -109,21 +119,54 @@ class BattleService implements EventListenerInterface {
}
}
+ 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 ? "" : "";
+ $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] 攻击 | [R] 逃跑"
+ 'message' => "\n--- 你的回合 --- [A] 攻击 | [C] 施法 | [R] 逃跑" // ⭐ 增加 C
]));
- $question = new Question("> 请选择指令 (A/R):");
-
+ $question = new Question("> 请选择指令 (A/C/R):");
// 关键:使用注入的 I/O 接口
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
// 简单的输入验证
- if (in_array($choice, ['A', 'R'])) {
+ if (in_array($choice, ['A', 'C', 'R'])) { // ⭐ 增加 C
return $choice;
} else {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的战斗指令。']));
diff --git a/src/System/InputHandler.php b/src/System/InputHandler.php
index bedbbd1..f21c3d3 100644
--- a/src/System/InputHandler.php
+++ b/src/System/InputHandler.php
@@ -13,7 +13,7 @@ use Symfony\Component\Console\Helper\QuestionHelper;
* 遵循单一职责原则:只处理输入和事件转发。
*/
class InputHandler {
-
+ private ?SaveLoadService $saveLoadService = null; // ⭐ 接受 SaveLoadService
private EventDispatcher $dispatcher;
private InputInterface $input;
private OutputInterface $output;
@@ -33,11 +33,19 @@ class InputHandler {
// 1. 请求 UI 服务打印主菜单 (确保 UI 已输出提示)
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
- $question = new Question("> 请选择操作 (M/E/S/I/Q):");
+ $question = new Question("> 请选择操作 (L/M/E/S/I/Q/B):");
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
// 2. 解析并分派事件
switch ($choice) {
+ case 'L': // ⭐ 保存指令
+ if ($this->saveLoadService) {
+ $this->saveLoadService->saveGame();
+ }
+ break;
+ case 'B': // ⭐ 新增背包指令
+ $this->handleInventoryInput();
+ break;
case 'M':
$this->handleMoveInput();
break;
@@ -76,4 +84,30 @@ class InputHandler {
}
// TODO: 可以在这里添加 handleBattleInput() 等,进一步解耦 BattleService
+ /**
+ * 处理背包输入和子菜单
+ */
+ private function handleInventoryInput(): void {
+ // 通知 UI 打印背包内容
+ $this->dispatcher->dispatch(new Event('ShowInventoryRequest'));
+
+ $question = new Question("> 请输入要使用的物品编号 (或 [X] 退出):");
+ $choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
+
+ if ($choice === 'X') {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '退出背包。']));
+ return;
+ }
+
+ if (is_numeric($choice)) {
+ $itemIndex = (int)$choice;
+ // 触发使用物品事件,ItemService 监听并处理
+ $this->dispatcher->dispatch(new Event('UseItemEvent', ['itemIndex' => $itemIndex]));
+ } else {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的编号。']));
+ }
+ }
+ public function setSaveLoadService(SaveLoadService $service): void {
+ $this->saveLoadService = $service;
+ }
}
\ No newline at end of file
diff --git a/src/System/ItemService.php b/src/System/ItemService.php
new file mode 100644
index 0000000..b8ed1cb
--- /dev/null
+++ b/src/System/ItemService.php
@@ -0,0 +1,82 @@
+dispatcher = $dispatcher;
+ $this->stateManager = $stateManager;
+ }
+
+ public function handleEvent(Event $event): void {
+ switch ($event->getType()) {
+ case 'UseItemEvent': // 响应玩家使用物品的请求
+ $payload = $event->getPayload();
+ $this->handleUseItem($payload['itemIndex']);
+ break;
+ // TODO: 未来添加 'EquipItemEvent'
+ }
+ }
+
+ /**
+ * 处理玩家使用物品的请求
+ */
+ private function handleUseItem(int $itemIndex): void {
+ $player = $this->stateManager->getPlayer();
+ $inventory = $player->getInventory();
+
+ if (!isset($inventory[$itemIndex])) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 背包中没有这个物品。']));
+ return;
+ }
+
+ $item = $inventory[$itemIndex];
+
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "使用了物品:{$item->name}>!"
+ ]));
+
+ // 执行物品效果
+ $this->applyItemEffects($player, $item);
+
+ // 移除消耗品
+ if ($item->type === 'potion') {
+ $player->removeItemByIndex($itemIndex);
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "{$item->name} 已消耗。"]));
+ // 触发 UI 更新
+ $this->dispatcher->dispatch(new Event('InventoryUpdateEvent'));
+ }
+ }
+
+ /**
+ * 应用物品的具体效果
+ */
+ private function applyItemEffects(Player $player, Item $item): void {
+ if (empty($item->effects)) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这个物品似乎没有效果..."]));
+ return;
+ }
+
+ if (isset($item->effects['heal'])) {
+ $healAmount = $item->effects['heal'];
+ $player->heal($healAmount);
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "❤️ 恢复了 {$healAmount}> 点生命值!当前HP: {$player->getHealth()}/{$player->getMaxHealth()}"
+ ]));
+ }
+
+ // TODO: 未来添加 'buff', 'damage', 'equip' 等效果
+ }
+}
\ No newline at end of file
diff --git a/src/System/LootService.php b/src/System/LootService.php
index 8922517..3af88a6 100644
--- a/src/System/LootService.php
+++ b/src/System/LootService.php
@@ -29,6 +29,11 @@ class LootService implements EventListenerInterface {
$lootId = $event->getPayload()['lootId'];
$this->handleLootFound($lootId);
break;
+ // ... 现有事件 ...
+ case 'ShopPurchaseEvent': // ⭐ 响应商店购买
+ $itemId = $event->getPayload()['itemId'];
+ $this->giveItemToPlayer($itemId);
+ break;
}
}
@@ -36,6 +41,16 @@ class LootService implements EventListenerInterface {
* 处理敌人死亡时的掉落逻辑
*/
private function handleLootDrop(string $enemyId): void {
+
+ $goldAmount = rand(5, 15); // 随机掉落 5 到 15 金币
+ // 1. 发放金币
+ $player = $this->stateManager->getPlayer();
+ $player->gainGold($goldAmount);
+
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "💰 获得了 {$goldAmount}> 金币。"
+ ]));
+
// 简化:总是掉落物品 ID 1 (小药水)
$roll = rand(1, 100);
if ($roll <= 70) { // 70% 掉落几率
@@ -89,11 +104,11 @@ class LootService implements EventListenerInterface {
* 模拟从配置中加载物品数据
*/
private function loadItemData(int $id): array {
- // 实际项目中应从数据库或 JSON 加载
return match ($id) {
- 1 => ['name' => '小型治疗药水', 'type' => 'potion', 'description' => '恢复少量生命。', 'value' => 10],
- 2 => ['name' => '破旧的短剑', 'type' => 'weapon', 'description' => '攻击力微弱。', 'value' => 50],
- default => ['name' => '垃圾', 'type' => 'misc', 'description' => '毫无价值的杂物。', 'value' => 1],
+ 1 => ['name' => '小型治疗药水', 'type' => 'potion', 'description' => '恢复少量生命。', 'value' => 10, 'effects' => ['heal' => 20]],
+ 2 => ['name' => '破旧的短剑', 'type' => 'weapon', 'description' => '攻击力微弱。', 'value' => 50, 'effects' => []],
+ 3 => ['name' => '高级治疗药水', 'type' => 'potion', 'description' => '恢复大量生命。', 'value' => 200, 'effects' => ['heal' => 100]], // 新增
+ default => ['name' => '垃圾', 'type' => 'misc', 'description' => '毫无价值的杂物。', 'value' => 1, 'effects' => []],
};
}
}
\ No newline at end of file
diff --git a/src/System/SaveLoadService.php b/src/System/SaveLoadService.php
new file mode 100644
index 0000000..11b0664
--- /dev/null
+++ b/src/System/SaveLoadService.php
@@ -0,0 +1,139 @@
+dispatcher = $dispatcher;
+ $this->stateManager = $stateManager;
+ $this->savePath = $savePath;
+
+ // 确保保存目录存在
+ if (!is_dir(dirname($this->savePath))) {
+ mkdir(dirname($this->savePath), 0777, true);
+ }
+ }
+
+ /**
+ * 检查是否存在存档文件
+ */
+ public function hasSaveFile(): bool {
+ return file_exists($this->savePath);
+ }
+
+ /**
+ * 将玩家状态保存到 JSON 文件
+ */
+ public function saveGame(): void {
+ $player = $this->stateManager->getPlayer();
+ if (!$player) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 无法保存:玩家状态为空。']));
+ return;
+ }
+
+ try {
+
+ // ⭐ 修正:处理 Inventory 序列化
+ $serializedInventory = [];
+ /** @var Item $item */
+ foreach ($player->getInventory() as $item) {
+ // 将 Item 对象转换为数组以安全序列化 (可选,但更清晰)
+ $serializedInventory[] = $item->toArray();
+ }
+ // 序列化 Player 对象(我们假设 Player 模型包含了所有属性的 public/getter)
+ $data = [
+ 'name' => $player->getName(),
+ 'health' => $player->getHealth(),
+ 'maxHealth' => $player->getMaxHealth(),
+ 'attack' => $player->getAttack(),
+ 'defense' => $player->getDefense(),
+ 'level' => $player->getLevel(),
+ 'currentXp' => $player->getCurrentXp(),
+ 'xpToNextLevel' => $player->getXpToNextLevel(),
+ 'gold' => $player->getGold(),
+ 'inventory' => $serializedInventory, // 注意:复杂对象数组需要递归序列化/反序列化
+ 'activeQuests' => $player->getActiveQuests(),
+ 'completedQuests' => $player->getCompletedQuests(),
+ // TODO: 添加地图位置等其他状态
+ ];
+
+ file_put_contents($this->savePath, json_encode($data, JSON_PRETTY_PRINT));
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '💾 游戏已保存!']));
+
+ } catch (\Exception $e) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 存档失败: ' . $e->getMessage()]));
+ }
+ }
+
+ /**
+ * 从 JSON 文件加载玩家状态,并返回新的 Player 实例
+ */
+ public function loadGame(): ?Player {
+ if (!$this->hasSaveFile()) {
+ return null;
+ }
+
+ try {
+ $json = file_get_contents($this->savePath);
+ $data = json_decode($json, true);
+
+ // 1. 实例化 Player(使用 Character 基类的构造函数)
+ $player = new Player($data['name'], $data['maxHealth'], $data['attack'], $data['defense']);
+
+ // 2. 恢复运行时状态
+ $player->setHealth($data['health']); // 假设 Player 模型中有 setHealth 方法
+
+ // 3. 恢复 Player 特有属性
+ $player->setLevel($data['level']); // 假设 Player 模型中有 setLevel 方法
+ $player->setCurrentXp($data['currentXp']);
+ $player->setXpToNextLevel($data['xpToNextLevel']);
+ $player->setGold($data['gold']);
+
+ // 4. 恢复复杂对象 (Inventory, Quests)
+ // Inventory 恢复需要重新实例化 Item 对象(这里是简化的难点)
+ // 简化处理:目前我们只将 Item 属性数据恢复,而不是完整的 Item 对象
+ // 实际中需要一个 ItemFactory 来根据数据重建对象
+
+ // ⭐ 关键修正:恢复复杂对象 Inventory
+ $restoredInventory = [];
+ // 确保 Item 类被引入: use Game\Model\Item;
+ foreach ($data['inventory'] as $itemData) {
+ // 重新实例化 Item 对象
+ $item = new \Game\Model\Item(
+ $itemData['id'],
+ $itemData['name'],
+ $itemData['type'],
+ $itemData['description'],
+ $itemData['value'],
+ $itemData['effects'] ?? [] // 确保 effects 存在
+ );
+ $restoredInventory[] = $item;
+ }
+ $player->setInventory($restoredInventory); // 现在赋值的是 Item 对象的数组
+
+ $player->setActiveQuests($data['activeQuests']);
+ $player->setCompletedQuests($data['completedQuests']);
+
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '📂 游戏存档已加载!']));
+ return $player;
+
+ } catch (\Exception $e) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 加载存档失败: ' . $e->getMessage()]));
+ // 移除损坏的存档文件
+ // unlink($this->savePath);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System/ShopService.php b/src/System/ShopService.php
new file mode 100644
index 0000000..12c1382
--- /dev/null
+++ b/src/System/ShopService.php
@@ -0,0 +1,195 @@
+dispatcher = $dispatcher;
+ $this->stateManager = $stateManager;
+ $this->input = $input;
+ $this->output = $output;
+ $this->helper = $helper;
+ $this->loadShopInventory();
+ }
+
+ private function loadShopInventory(): void {
+ // 模拟商店出售的物品配置 (ID 对应 LootService::loadItemData)
+ $this->shopInventory = [
+ // 商店物品 ID => 价格倍数(如果价格不是 Item::value)
+ 1 => ['stock' => 10, 'price' => 10], // 小药水
+ 3 => ['stock' => 5, 'price' => 100], // 新物品:高级药水
+ ];
+ }
+
+ public function handleEvent(Event $event): void {
+ switch ($event->getType()) {
+ case 'OpenShopEvent': // 响应 InteractionSystem 的请求
+ $this->startShopping();
+ break;
+ }
+ }
+
+ /**
+ * 启动商店界面和循环
+ */
+ private function startShopping(): void {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "\n欢迎光临!看看我有什么好东西。"]));
+ $running = true;
+
+ while ($running) {
+ $player = $this->stateManager->getPlayer();
+ $this->displayShopMenu($player);
+
+ $question = new Question("> 请选择操作 (B:购买 | S:出售 | X:退出):");
+ $choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
+
+ switch ($choice) {
+ case 'B':
+ $this->handlePurchase();
+ break;
+ case 'S':
+ $this->handleSelling();
+ break;
+ case 'X':
+ $running = false;
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '下次再来!']));
+ break;
+ default:
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的指令。']));
+ }
+ }
+ }
+
+ // --- 购买逻辑 ---
+
+ private function handlePurchase(): void {
+ $this->displaySaleItems();
+ $player = $this->stateManager->getPlayer();
+
+ $question = new Question("> 输入要购买的物品编号 (或 X 退出):");
+ $choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
+
+ if ($choice === 'X') return;
+
+ if (is_numeric($choice)) {
+ $itemId = (int)$choice;
+ if (isset($this->shopInventory[$itemId])) {
+ $price = $this->shopInventory[$itemId]['price'];
+
+ if ($player->spendGold($price)) {
+ // ⭐ 触发事件请求 LootService 给予物品(重用 LootService 的逻辑)
+ $this->dispatcher->dispatch(new Event('ShopPurchaseEvent', ['itemId' => $itemId]));
+
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "交易成功!花费 {$price} 💰,剩余 {$player->getGold()} 💰。"
+ ]));
+ } else {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "金币不足!你需要 {$price} 💰。"]));
+ }
+ } else {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '商店没有这个编号的物品。']));
+ }
+ }
+ }
+
+ // --- 出售逻辑 ---
+
+ private function handleSelling(): void {
+ $player = $this->stateManager->getPlayer();
+ if (empty($player->getInventory())) {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '你的背包是空的,没有什么可卖的。']));
+ return;
+ }
+
+ $this->displaySellableItems($player);
+
+ $question = new Question("> 输入要出售的物品编号 (背包索引 | 或 X 退出):");
+ $choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
+
+ if ($choice === 'X') return;
+
+ if (is_numeric($choice)) {
+ $index = (int)$choice;
+ $inventory = $player->getInventory();
+
+ if (isset($inventory[$index])) {
+ $item = $inventory[$index];
+ // 简化:出售价格为 Item::value 的一半
+ $sellPrice = floor($item->value / 2);
+
+ // 移除物品并获得金币
+ $player->removeItemByIndex($index);
+ $player->gainGold($sellPrice);
+
+ $this->dispatcher->dispatch(new Event('SystemMessage', [
+ 'message' => "💰 出售了 {$item->name}>,获得 {$sellPrice} 💰。剩余 {$player->getGold()} 💰。"
+ ]));
+
+ // 触发 UI 更新
+ $this->dispatcher->dispatch(new Event('InventoryUpdateEvent'));
+
+ } else {
+ $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的背包编号。']));
+ }
+ }
+ }
+
+ // --- UI 辅助方法 ---
+
+ private function displayShopMenu(Player $player): void {
+ $this->output->writeln("\n--- 商店主页 ---");
+ $this->output->writeln("你的金币: {$player->getGold()}> 💰");
+ $this->output->writeln("--------------------------");
+ }
+
+ private function displaySaleItems(): void {
+ $this->output->writeln("\n--- 🛒 商店出售 ---");
+ // 模拟获取 Item 数据的服务(实际应通过 ItemService/DB)
+ $itemsData = [
+ 1 => ['name' => '小型治疗药水', 'value' => 10, 'type' => 'potion'],
+ 3 => ['name' => '高级治疗药水', 'value' => 200, 'type' => 'potion'], // 假设 ID 3
+ ];
+
+ foreach ($this->shopInventory as $itemId => $data) {
+ $name = $itemsData[$itemId]['name'] ?? "未知物品";
+ $price = $data['price'];
+ $this->output->writeln("[{$itemId}>] {$name} | 价格: {$price}> 💰");
+ }
+ $this->output->writeln("--------------------------");
+ }
+
+ private function displaySellableItems(Player $player): void {
+ $this->output->writeln("\n--- 📦 出售你的物品 ---");
+ $inventory = $player->getInventory();
+
+ if (empty($inventory)) return;
+
+ foreach ($inventory as $index => $item) {
+ $sellPrice = floor($item->value / 2);
+ $this->output->writeln("[{$index}>] {$item->name}> | 售价: {$sellPrice}> 💰");
+ }
+ $this->output->writeln("--------------------------");
+ }
+}
\ No newline at end of file
diff --git a/src/System/UIService.php b/src/System/UIService.php
index 500e9cb..f8395cc 100644
--- a/src/System/UIService.php
+++ b/src/System/UIService.php
@@ -6,6 +6,7 @@ use Game\Event\EventListenerInterface;
use Game\Model\Player;
use Game\Model\MapTile; // 需要引入 MapTile 模型
use Symfony\Component\Console\Output\OutputInterface;
+use function Symfony\Component\String\b;
/**
* UIService: 负责所有终端输出的监听器。
@@ -58,6 +59,9 @@ class UIService implements EventListenerInterface {
$this->output->writeln("UI 错误:无法加载地图区域 {$tileId} {$e->getMessage()}。");
}
break;
+ case 'ShowInventoryRequest': // ⭐ 新增背包显示请求
+ $this->displayInventory();
+ break;
case 'StartBattleEvent':
$this->output->writeln("\n\n⚔️ 遭遇战触发!请选择战斗指令...>");
@@ -71,18 +75,22 @@ class UIService implements EventListenerInterface {
* 打印玩家状态信息
*/
private function displayPlayerStats(Player $player): void {
- $this->output->writeln("\n--- 角色状态:{$player->getName()} ---");
- $this->output->writeln("等级: {$player->getLevel()}");
- $this->output->writeln("HP: {$player->getHealth()}>/{$player->getMaxHealth()}");
- $this->output->writeln("攻击力: {$player->getAttack()}>");
- $this->output->writeln("防御力: {$player->getDefense()}>");
- $this->output->writeln("经验值: {$player->getCurrentXp()}/{$player->getXpToNextLevel()}");
- $this->output->writeln("--------------------------\n");
+ $this->output->writeln("\n--- 角色状态> ---");
+ $this->output->writeln("姓名: {$player->getName()}>");
+ $this->output->writeln("等级: {$player->getLevel()}> (XP: {$player->getCurrentXp()}/{$player->getXpToNextLevel()})");
+ $this->output->writeln("生命值: {$player->getHealth()}>/{$player->getMaxHealth()}");
+ $this->output->writeln("魔法值: {$player->getMana()}>/{$player->getMaxMana()}");
+ $this->output->writeln("攻击力: {$player->getAttack()}> | 防御力: {$player->getDefense()}>");
+ // ⭐ 新增金币显示
+ $this->output->writeln("金币: {$player->getGold()}> 💰");
+
+ // TODO: 未来显示任务和物品数量
+ $this->output->writeln("--------------------------");
}
private function displayMainMenu(): void {
$this->output->writeln("\n--- 主菜单> ---");
- $this->output->writeln(" [M] 移动 | [E] 探索 | [S] 状态 | [I] 交互 | [Q] 退出"); // ⭐ 增加 I 选项
+ $this->output->writeln(" [M] 移动 | [E] 探索 | [S] 状态 | [I] 交互 | [B] 背包 | [L] 保存 | [Q] 退出"); // ⭐ 增加 L 选项
}
/**
* 打印当前地图区域信息
@@ -99,4 +107,30 @@ class UIService implements EventListenerInterface {
$this->output->writeln(" -> 可移动方向: " . implode(' | ', $connections));
$this->output->writeln("===================================\n");
}
+
+ /**
+ * 打印玩家背包内容
+ */
+ private function displayInventory(): void {
+ $player = $this->stateManager->getPlayer();
+ $inventory = $player->getInventory();
+ $this->output->writeln("\n--- 背包 ({$player->getGold()} 💰)> ---");
+
+ if (empty($inventory)) {
+ $this->output->writeln("背包是空的。");
+ $this->output->writeln("--------------------------");
+ return;
+ }
+
+ foreach ($inventory as $index => $item) {
+ $effects = implode(', ', array_map(
+ fn($k, $v) => "{$k}:{$v}",
+ array_keys($item->effects??[]),
+ $item->effects??[]
+ ));
+
+ $this->output->writeln("[{$index}>] {$item->name}> ({$item->type}) | 效果: [{$effects}]");
+ }
+ $this->output->writeln("--------------------------");
+ }
}
\ No newline at end of file