完善
This commit is contained in:
parent
8e3c3a52de
commit
025c1ba2f2
23
save/player.json
Normal file
23
save/player.json
Normal file
|
|
@ -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": []
|
||||||
|
}
|
||||||
|
|
@ -8,9 +8,11 @@ use Game\System\BattleService;
|
||||||
use Game\System\CharacterService;
|
use Game\System\CharacterService;
|
||||||
use Game\System\InputHandler;
|
use Game\System\InputHandler;
|
||||||
use Game\System\InteractionSystem;
|
use Game\System\InteractionSystem;
|
||||||
|
use Game\System\ItemService;
|
||||||
use Game\System\LootService;
|
use Game\System\LootService;
|
||||||
use Game\System\MapSystem;
|
use Game\System\MapSystem;
|
||||||
use Game\System\QuestService;
|
use Game\System\QuestService;
|
||||||
|
use Game\System\ShopService;
|
||||||
use Game\System\StateManager;
|
use Game\System\StateManager;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
|
@ -41,77 +43,52 @@ class GameCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
// 1. 初始化数据库管理器
|
|
||||||
$this->dbManager = new DatabaseManager();
|
|
||||||
$this->dbManager->loadInitialData();
|
|
||||||
|
|
||||||
// 2. 初始化 Event Dispatcher 和所有服务
|
// 1. 初始化服务容器并注册所有系统
|
||||||
$this->initializeServices($input, $output);
|
$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');
|
$helper = $this->getHelper('question');
|
||||||
$question = new Question("请输入你的角色名称:", "旅行者");
|
|
||||||
$playerName = $helper->ask($input, $output, $question);
|
|
||||||
|
|
||||||
// 创建玩家实例
|
// 2. ⭐ 存档/加载逻辑
|
||||||
$player = new Player($playerName, 100, 15, 5);
|
if ($saveLoadService->hasSaveFile()) {
|
||||||
|
$player = $saveLoadService->loadGame();
|
||||||
|
// $output->writeln("\n<info>检测到存档文件!</info>");
|
||||||
|
// $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);
|
$this->stateManager->setPlayer($player);
|
||||||
|
|
||||||
// 通知 UI 服务
|
// 4. ⭐ 增加存档命令到 InputHandler
|
||||||
$this->eventDispatcher->dispatch(new Event('SystemMessage', ['message' => "角色 {$player->getName()} 创建成功!"]));
|
$this->inputHandler->setSaveLoadService($saveLoadService);
|
||||||
|
|
||||||
return $this->mainLoop($input, $output);
|
// 5. 触发启动事件
|
||||||
}
|
|
||||||
|
|
||||||
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 服务打印欢迎信息
|
|
||||||
$welcomeEvent = new Event('GameStartEvent', ['message' => '核心系统已就绪,请输入指令开始游戏。']);
|
$welcomeEvent = new Event('GameStartEvent', ['message' => '核心系统已就绪,请输入指令开始游戏。']);
|
||||||
$this->eventDispatcher->dispatch($welcomeEvent);
|
$this->eventDispatcher->dispatch($welcomeEvent);
|
||||||
}
|
|
||||||
// src/Core/GameCommand.php (mainLoop 方法片段)
|
|
||||||
|
|
||||||
|
// 6. 启动主循环
|
||||||
|
return $this->mainLoop($input, $output);
|
||||||
|
}
|
||||||
private function mainLoop(InputInterface $input, OutputInterface $output): int {
|
private function mainLoop(InputInterface $input, OutputInterface $output): int {
|
||||||
$running = true;
|
$running = true;
|
||||||
while ($running) {
|
while ($running) {
|
||||||
|
|
|
||||||
127
src/Core/ServiceContainer.php
Normal file
127
src/Core/ServiceContainer.php
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<?php
|
||||||
|
namespace Game\Core;
|
||||||
|
|
||||||
|
// 导入所有依赖和系统服务
|
||||||
|
use Game\Database\DatabaseManager;
|
||||||
|
use Game\Event\EventDispatcher;
|
||||||
|
use Game\Event\EventListenerInterface;
|
||||||
|
use Game\System\AbilityService;
|
||||||
|
use Game\System\SaveLoadService;
|
||||||
|
use Game\System\StateManager;
|
||||||
|
use Game\System\UIService;
|
||||||
|
use Game\System\MapSystem;
|
||||||
|
use Game\System\InputHandler;
|
||||||
|
use Game\System\BattleService;
|
||||||
|
use Game\System\CharacterService;
|
||||||
|
use Game\System\LootService;
|
||||||
|
use Game\System\ItemService;
|
||||||
|
use Game\System\InteractionSystem;
|
||||||
|
use Game\System\QuestService;
|
||||||
|
use Game\System\ShopService;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Game\Event\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServiceContainer: 负责实例化所有核心服务、管理依赖,并注册事件监听器。
|
||||||
|
*/
|
||||||
|
class ServiceContainer {
|
||||||
|
|
||||||
|
private DatabaseManager $dbManager;
|
||||||
|
private EventDispatcher $eventDispatcher;
|
||||||
|
private StateManager $stateManager;
|
||||||
|
private InputInterface $input;
|
||||||
|
private OutputInterface $output;
|
||||||
|
private QuestionHelper $questionHelper;
|
||||||
|
private SaveLoadService $saveLoadService; // 新增属性
|
||||||
|
// 存储所有已实例化的服务
|
||||||
|
private array $services = [];
|
||||||
|
|
||||||
|
public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) {
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Model/Ability.php
Normal file
23
src/Model/Ability.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
namespace Game\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ability: 技能模型
|
||||||
|
*/
|
||||||
|
class Ability {
|
||||||
|
public string $id;
|
||||||
|
public string $name;
|
||||||
|
public string $type; // e.g., 'damage', 'heal', 'buff'
|
||||||
|
public int $manaCost;
|
||||||
|
public int $power; // 基础威力值
|
||||||
|
public string $scaling; // 伤害加成属性 (e.g., 'attack', 'mana')
|
||||||
|
|
||||||
|
public function __construct(string $id, string $name, string $type, int $manaCost, int $power, string $scaling) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->type = $type;
|
||||||
|
$this->manaCost = $manaCost;
|
||||||
|
$this->power = $power;
|
||||||
|
$this->scaling = $scaling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,66 +11,38 @@ class Character {
|
||||||
protected int $attack;
|
protected int $attack;
|
||||||
protected int $defense;
|
protected int $defense;
|
||||||
|
|
||||||
protected array $activeQuests = []; // 存储进行中的任务进度,格式: ['questId' => ['currentCount' => 0, 'isCompleted' => false]]
|
// ⭐ 新增:魔法值 (MP/Mana)
|
||||||
protected array $completedQuests = []; // 存储已完成的任务 ID
|
protected int $mana;
|
||||||
// ⭐ 新增方法:添加/接受任务
|
protected int $maxMana;
|
||||||
public function addActiveQuest(string $questId, int $targetCount): void {
|
|
||||||
$this->activeQuests[$questId] = ['currentCount' => 0, 'isCompleted' => false, 'targetCount' => $targetCount];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ⭐ 新增方法:获取进行中的任务
|
// ⭐ 新增 Getter/Setter for Mana
|
||||||
public function getActiveQuests(): array {
|
public function getMana(): int { return $this->mana; }
|
||||||
return $this->activeQuests;
|
public function getMaxMana(): int { return $this->maxMana; }
|
||||||
}
|
|
||||||
|
|
||||||
// ⭐ 新增方法:更新任务进度
|
public function spendMana(int $cost): bool {
|
||||||
public function updateQuestProgress(string $questId, int $count = 1): void {
|
if ($this->mana >= $cost) {
|
||||||
if (isset($this->activeQuests[$questId])) {
|
$this->mana -= $cost;
|
||||||
$progress = &$this->activeQuests[$questId]; // 使用引用
|
return true;
|
||||||
if (!$progress['isCompleted']) {
|
|
||||||
$progress['currentCount'] += $count;
|
|
||||||
if ($progress['currentCount'] >= $progress['targetCount']) {
|
|
||||||
$progress['currentCount'] = $progress['targetCount'];
|
|
||||||
$progress['isCompleted'] = true;
|
|
||||||
// 触发 QuestCompletedEventRequest
|
|
||||||
// 注意:实际的奖励和标记完成应在 QuestService 确认后进行
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ 新增方法:标记任务完成
|
public function restoreMana(int $amount): void {
|
||||||
public function markQuestCompleted(string $questId): void {
|
$this->mana = min($this->maxMana, $this->mana + $amount);
|
||||||
unset($this->activeQuests[$questId]);
|
|
||||||
$this->completedQuests[] = $questId;
|
|
||||||
}
|
}
|
||||||
|
public function __construct(string $name, int $maxHealth, int $attack, int $defense, int $maxMana = 0) {
|
||||||
// ⭐ 新增方法:检查任务是否已完成
|
|
||||||
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) {
|
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->maxHealth = $maxHealth;
|
|
||||||
$this->health = $maxHealth;
|
|
||||||
$this->attack = $attack;
|
$this->attack = $attack;
|
||||||
$this->defense = $defense;
|
$this->defense = $defense;
|
||||||
|
// ... 现有初始化 ...
|
||||||
|
$this->maxHealth = $maxHealth;
|
||||||
|
$this->health = $maxHealth;
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// ⭐ 初始化 Mana
|
||||||
|
$this->maxMana = $maxMana;
|
||||||
|
$this->mana = $maxMana;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(): string { return $this->name; }
|
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;
|
$this->health += $amount;
|
||||||
if ($this->health > $this->maxHealth) {
|
if ($this->health > $this->maxHealth) {
|
||||||
$this->health = $this->maxHealth;
|
$this->health = $this->maxHealth;
|
||||||
}
|
}
|
||||||
|
return $this->health - $old;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,12 +10,25 @@ class Item {
|
||||||
public string $type; // e.g., 'potion', 'weapon', 'material'
|
public string $type; // e.g., 'potion', 'weapon', 'material'
|
||||||
public string $description;
|
public string $description;
|
||||||
public int $value; // 卖出价格
|
public int $value; // 卖出价格
|
||||||
|
public array $effects; // ⭐ 新增:存储效果参数 e.g., ['heal' => 20]
|
||||||
public function __construct(int $id, string $name, string $type, string $description, int $value) {
|
public function __construct(int $id, string $name, string $type, string $description, int $value, array $effects = []) {
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
$this->description = $description;
|
$this->description = $description;
|
||||||
$this->value = $value;
|
$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,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,9 +7,89 @@ class Player extends Character {
|
||||||
protected int $currentXp = 0;
|
protected int $currentXp = 0;
|
||||||
protected int $xpToNextLevel = 100;
|
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) 的构造函数来初始化核心属性
|
// 调用父类 (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 方法
|
// Player 特有的 Getter 方法
|
||||||
|
|
@ -22,4 +102,33 @@ class Player extends Character {
|
||||||
$this->currentXp += $amount;
|
$this->currentXp += $amount;
|
||||||
// TODO: 未来在这里实现升级逻辑 (LevelUpEvent)
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
114
src/System/AbilityService.php
Normal file
114
src/System/AbilityService.php
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
namespace Game\System;
|
||||||
|
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Event\EventListenerInterface;
|
||||||
|
use Game\Event\EventDispatcher;
|
||||||
|
use Game\Model\Ability;
|
||||||
|
use Game\Model\Player;
|
||||||
|
use Game\Model\Enemy; // 假设战斗逻辑中需要 Enemy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbilityService: 负责技能的加载、管理和效果计算。
|
||||||
|
*/
|
||||||
|
class AbilityService implements EventListenerInterface {
|
||||||
|
|
||||||
|
private EventDispatcher $dispatcher;
|
||||||
|
private StateManager $stateManager;
|
||||||
|
private array $abilityData; // 存储所有技能配置
|
||||||
|
|
||||||
|
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager) {
|
||||||
|
$this->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()} 施放了 <fg=blue>{$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()} 造成了 <fg=red>{$damage}</> 点伤害!"
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
case 'heal':
|
||||||
|
$heal = $player->heal($rawEffect);
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "💖 恢复了 <fg=green>{$heal}</> 点生命值!"
|
||||||
|
]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 触发 BattleService 的事件,让其检查战斗是否结束
|
||||||
|
$this->dispatcher->dispatch(new Event('AbilityEffectApplied', ['target' => $target]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,8 +38,16 @@ class BattleService implements EventListenerInterface {
|
||||||
|
|
||||||
public function handleEvent(Event $event): void {
|
public function handleEvent(Event $event): void {
|
||||||
if ($this->inBattle) {
|
if ($this->inBattle) {
|
||||||
// 如果在战斗中,可以监听 'BattleCommandEvent' 等事件来处理输入
|
switch ($event->getType()) {
|
||||||
// 当前版本,我们通过 battleLoop() 内部阻塞输入
|
case 'AbilityEffectApplied': // ⭐ 监听技能施放效果
|
||||||
|
$target = $event->getPayload()['target'];
|
||||||
|
if ($target instanceof Enemy && !$target->isAlive()) {
|
||||||
|
$this->handleWin();
|
||||||
|
return; // 战斗结束
|
||||||
|
}
|
||||||
|
// 如果是玩家治疗,则无需返回
|
||||||
|
break;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +101,8 @@ class BattleService implements EventListenerInterface {
|
||||||
|
|
||||||
if ($playerAction === 'A') {
|
if ($playerAction === 'A') {
|
||||||
$this->playerAttack();
|
$this->playerAttack();
|
||||||
|
}elseif ($playerAction === 'C') { // ⭐ 施法逻辑
|
||||||
|
$this->handleAbilityInput();
|
||||||
} elseif ($playerAction === 'R') {
|
} elseif ($playerAction === 'R') {
|
||||||
if ($this->tryRunAway()) {
|
if ($this->tryRunAway()) {
|
||||||
$this->endBattle(false);
|
$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 ? "<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 接口)
|
* 获取玩家战斗指令 (直接使用注入的 I/O 接口)
|
||||||
*/
|
*/
|
||||||
private function promptPlayerAction(): string {
|
private function promptPlayerAction(): string {
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
$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 接口
|
// 关键:使用注入的 I/O 接口
|
||||||
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
$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;
|
return $choice;
|
||||||
} else {
|
} else {
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的战斗指令。']));
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的战斗指令。']));
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
* 遵循单一职责原则:只处理输入和事件转发。
|
* 遵循单一职责原则:只处理输入和事件转发。
|
||||||
*/
|
*/
|
||||||
class InputHandler {
|
class InputHandler {
|
||||||
|
private ?SaveLoadService $saveLoadService = null; // ⭐ 接受 SaveLoadService
|
||||||
private EventDispatcher $dispatcher;
|
private EventDispatcher $dispatcher;
|
||||||
private InputInterface $input;
|
private InputInterface $input;
|
||||||
private OutputInterface $output;
|
private OutputInterface $output;
|
||||||
|
|
@ -33,11 +33,19 @@ class InputHandler {
|
||||||
// 1. 请求 UI 服务打印主菜单 (确保 UI 已输出提示)
|
// 1. 请求 UI 服务打印主菜单 (确保 UI 已输出提示)
|
||||||
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
$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) ?? '');
|
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
||||||
|
|
||||||
// 2. 解析并分派事件
|
// 2. 解析并分派事件
|
||||||
switch ($choice) {
|
switch ($choice) {
|
||||||
|
case 'L': // ⭐ 保存指令
|
||||||
|
if ($this->saveLoadService) {
|
||||||
|
$this->saveLoadService->saveGame();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'B': // ⭐ 新增背包指令
|
||||||
|
$this->handleInventoryInput();
|
||||||
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
$this->handleMoveInput();
|
$this->handleMoveInput();
|
||||||
break;
|
break;
|
||||||
|
|
@ -76,4 +84,30 @@ class InputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 可以在这里添加 handleBattleInput() 等,进一步解耦 BattleService
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
82
src/System/ItemService.php
Normal file
82
src/System/ItemService.php
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
namespace Game\System;
|
||||||
|
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Event\EventListenerInterface;
|
||||||
|
use Game\Event\EventDispatcher;
|
||||||
|
use Game\Model\Item;
|
||||||
|
use Game\Model\Player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ItemService: 负责处理物品的使用、装备和消耗逻辑。
|
||||||
|
*/
|
||||||
|
class ItemService implements EventListenerInterface {
|
||||||
|
|
||||||
|
private EventDispatcher $dispatcher;
|
||||||
|
private StateManager $stateManager;
|
||||||
|
|
||||||
|
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager) {
|
||||||
|
$this->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' => "使用了物品:<fg=green>{$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' => "❤️ 恢复了 <fg=red>{$healAmount}</> 点生命值!当前HP: {$player->getHealth()}/{$player->getMaxHealth()}"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 未来添加 'buff', 'damage', 'equip' 等效果
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,11 @@ class LootService implements EventListenerInterface {
|
||||||
$lootId = $event->getPayload()['lootId'];
|
$lootId = $event->getPayload()['lootId'];
|
||||||
$this->handleLootFound($lootId);
|
$this->handleLootFound($lootId);
|
||||||
break;
|
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 {
|
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' => "💰 获得了 <fg=yellow>{$goldAmount}</> 金币。"
|
||||||
|
]));
|
||||||
|
|
||||||
// 简化:总是掉落物品 ID 1 (小药水)
|
// 简化:总是掉落物品 ID 1 (小药水)
|
||||||
$roll = rand(1, 100);
|
$roll = rand(1, 100);
|
||||||
if ($roll <= 70) { // 70% 掉落几率
|
if ($roll <= 70) { // 70% 掉落几率
|
||||||
|
|
@ -89,11 +104,11 @@ class LootService implements EventListenerInterface {
|
||||||
* 模拟从配置中加载物品数据
|
* 模拟从配置中加载物品数据
|
||||||
*/
|
*/
|
||||||
private function loadItemData(int $id): array {
|
private function loadItemData(int $id): array {
|
||||||
// 实际项目中应从数据库或 JSON 加载
|
|
||||||
return match ($id) {
|
return match ($id) {
|
||||||
1 => ['name' => '小型治疗药水', 'type' => 'potion', 'description' => '恢复少量生命。', 'value' => 10],
|
1 => ['name' => '小型治疗药水', 'type' => 'potion', 'description' => '恢复少量生命。', 'value' => 10, 'effects' => ['heal' => 20]],
|
||||||
2 => ['name' => '破旧的短剑', 'type' => 'weapon', 'description' => '攻击力微弱。', 'value' => 50],
|
2 => ['name' => '破旧的短剑', 'type' => 'weapon', 'description' => '攻击力微弱。', 'value' => 50, 'effects' => []],
|
||||||
default => ['name' => '垃圾', 'type' => 'misc', 'description' => '毫无价值的杂物。', 'value' => 1],
|
3 => ['name' => '高级治疗药水', 'type' => 'potion', 'description' => '恢复大量生命。', 'value' => 200, 'effects' => ['heal' => 100]], // 新增
|
||||||
|
default => ['name' => '垃圾', 'type' => 'misc', 'description' => '毫无价值的杂物。', 'value' => 1, 'effects' => []],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
139
src/System/SaveLoadService.php
Normal file
139
src/System/SaveLoadService.php
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
<?php
|
||||||
|
namespace Game\System;
|
||||||
|
|
||||||
|
use Game\Model\Item;
|
||||||
|
use Game\Model\Player;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Event\EventDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SaveLoadService: 负责将玩家状态持久化到磁盘,并在启动时加载。
|
||||||
|
*/
|
||||||
|
class SaveLoadService {
|
||||||
|
|
||||||
|
private EventDispatcher $dispatcher;
|
||||||
|
private StateManager $stateManager;
|
||||||
|
private string $savePath;
|
||||||
|
|
||||||
|
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, string $savePath = 'save/player.json') {
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
src/System/ShopService.php
Normal file
195
src/System/ShopService.php
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?php
|
||||||
|
namespace Game\System;
|
||||||
|
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Event\EventListenerInterface;
|
||||||
|
use Game\Event\EventDispatcher;
|
||||||
|
use Game\Model\Item;
|
||||||
|
use Game\Model\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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShopService: 负责处理商店的购买和出售交易。
|
||||||
|
*/
|
||||||
|
class ShopService implements EventListenerInterface {
|
||||||
|
|
||||||
|
private EventDispatcher $dispatcher;
|
||||||
|
private StateManager $stateManager;
|
||||||
|
private InputInterface $input;
|
||||||
|
private OutputInterface $output;
|
||||||
|
private QuestionHelper $helper;
|
||||||
|
|
||||||
|
// 商店的固定库存(通常从配置加载)
|
||||||
|
private array $shopInventory;
|
||||||
|
|
||||||
|
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;
|
||||||
|
$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' => "💰 出售了 <fg=white>{$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("你的金币: <fg=yellow>{$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("[<fg=green>{$itemId}</>] {$name} | 价格: <fg=yellow>{$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("[<fg=green>{$index}</>] <fg=white>{$item->name}</> | 售价: <fg=yellow>{$sellPrice}</> 💰");
|
||||||
|
}
|
||||||
|
$this->output->writeln("--------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ use Game\Event\EventListenerInterface;
|
||||||
use Game\Model\Player;
|
use Game\Model\Player;
|
||||||
use Game\Model\MapTile; // 需要引入 MapTile 模型
|
use Game\Model\MapTile; // 需要引入 MapTile 模型
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use function Symfony\Component\String\b;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UIService: 负责所有终端输出的监听器。
|
* UIService: 负责所有终端输出的监听器。
|
||||||
|
|
@ -58,6 +59,9 @@ class UIService implements EventListenerInterface {
|
||||||
$this->output->writeln("<error>UI 错误:无法加载地图区域 {$tileId} {$e->getMessage()}。</error>");
|
$this->output->writeln("<error>UI 错误:无法加载地图区域 {$tileId} {$e->getMessage()}。</error>");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'ShowInventoryRequest': // ⭐ 新增背包显示请求
|
||||||
|
$this->displayInventory();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'StartBattleEvent':
|
case 'StartBattleEvent':
|
||||||
$this->output->writeln("\n\n<fg=red;options=bold>⚔️ 遭遇战触发!请选择战斗指令...</>");
|
$this->output->writeln("\n\n<fg=red;options=bold>⚔️ 遭遇战触发!请选择战斗指令...</>");
|
||||||
|
|
@ -71,18 +75,22 @@ class UIService implements EventListenerInterface {
|
||||||
* 打印玩家状态信息
|
* 打印玩家状态信息
|
||||||
*/
|
*/
|
||||||
private function displayPlayerStats(Player $player): void {
|
private function displayPlayerStats(Player $player): void {
|
||||||
$this->output->writeln("\n--- <info>角色状态:{$player->getName()}</info> ---");
|
$this->output->writeln("\n--- <fg=green;options=bold>角色状态</> ---");
|
||||||
$this->output->writeln("等级: <comment>{$player->getLevel()}</comment>");
|
$this->output->writeln("姓名: <fg=cyan>{$player->getName()}</>");
|
||||||
$this->output->writeln("HP: <fg=red>{$player->getHealth()}</>/{$player->getMaxHealth()}");
|
$this->output->writeln("等级: <fg=yellow>{$player->getLevel()}</> (XP: {$player->getCurrentXp()}/{$player->getXpToNextLevel()})");
|
||||||
$this->output->writeln("攻击力: <fg=yellow>{$player->getAttack()}</>");
|
$this->output->writeln("生命值: <fg=red>{$player->getHealth()}</>/{$player->getMaxHealth()}");
|
||||||
$this->output->writeln("防御力: <fg=blue>{$player->getDefense()}</>");
|
$this->output->writeln("魔法值: <fg=blue>{$player->getMana()}</>/{$player->getMaxMana()}");
|
||||||
$this->output->writeln("经验值: {$player->getCurrentXp()}/{$player->getXpToNextLevel()}");
|
$this->output->writeln("攻击力: <fg=yellow>{$player->getAttack()}</> | 防御力: <fg=blue>{$player->getDefense()}</>");
|
||||||
$this->output->writeln("--------------------------\n");
|
// ⭐ 新增金币显示
|
||||||
|
$this->output->writeln("金币: <fg=yellow>{$player->getGold()}</> 💰");
|
||||||
|
|
||||||
|
// TODO: 未来显示任务和物品数量
|
||||||
|
$this->output->writeln("--------------------------");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function displayMainMenu(): void {
|
private function displayMainMenu(): void {
|
||||||
$this->output->writeln("\n--- <fg=white>主菜单</> ---");
|
$this->output->writeln("\n--- <fg=white>主菜单</> ---");
|
||||||
$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(" -> 可移动方向: " . implode(' | ', $connections));
|
||||||
$this->output->writeln("===================================\n");
|
$this->output->writeln("===================================\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印玩家背包内容
|
||||||
|
*/
|
||||||
|
private function displayInventory(): void {
|
||||||
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$inventory = $player->getInventory();
|
||||||
|
$this->output->writeln("\n--- <fg=yellow;options=bold>背包 ({$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("[<fg=green>{$index}</>] <fg=white>{$item->name}</> ({$item->type}) | 效果: [{$effects}]");
|
||||||
|
}
|
||||||
|
$this->output->writeln("--------------------------");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user