功能完善

This commit is contained in:
hant 2025-12-16 23:35:55 +08:00
parent 025c1ba2f2
commit ce5a101ebe
32 changed files with 1189 additions and 186 deletions

18
config/abilities.json Normal file
View File

@ -0,0 +1,18 @@
[
{
"id": "FIREBALL",
"name": "火球术",
"type": "damage",
"mana_cost": 10,
"power": 25,
"scaling": "mana"
},
{
"id": "HEAL",
"name": "初级治疗",
"type": "heal",
"mana_cost": 8,
"power": 15,
"scaling": "mana"
}
]

42
config/enemies.json Normal file
View File

@ -0,0 +1,42 @@
[
{
"id": "GOBLIN",
"name": "哥布林",
"health": 30,
"attack": 8,
"defense": 2,
"xp_reward": 20,
"min_gold": 5,
"max_gold": 15,
"loot_table": [
{
"item_id": 1,
"chance": 70
},
{
"item_id": 2,
"chance": 10
}
]
},
{
"id": "WOLF",
"name": "野狼",
"health": 45,
"attack": 12,
"defense": 1,
"xp_reward": 35,
"min_gold": 10,
"max_gold": 25,
"loot_table": [
{
"item_id": 1,
"chance": 50
},
{
"item_id": 4,
"chance": 20
}
]
}
]

38
config/items.json Normal file
View File

@ -0,0 +1,38 @@
[
{
"id": 1,
"name": "小型治疗药水",
"type": "potion",
"description": "恢复少量生命。",
"value": 10,
"effects": {"heal": 20}
},
{
"id": 2,
"name": "破旧的短剑",
"type": "weapon",
"description": "攻击力微弱。",
"value": 50,
"effects": {},
"slot": "weapon",
"stat_modifiers": {"attack": 5}
},
{
"id": 3,
"name": "高级治疗药水",
"type": "potion",
"description": "恢复大量生命。",
"value": 200,
"effects": {"heal": 100}
},
{
"id": 4,
"name": "布甲头盔",
"type": "armor",
"description": "提供少量防御。",
"value": 30,
"effects": {},
"slot": "helmet",
"stat_modifiers": {"defense": 3, "health": 10}
}
]

View File

@ -1,20 +1,21 @@
{ {
"TOWN_01": { "TOWN_01": {
"name": "新手村", "name": "新手村",
"description": "安全的小镇,可以休息。", "description": "安全的小镇。",
"connections": {"N": "FIELD_01"}, "connections": {"N": "FOREST_01"},
"eventPoolId": 0 "encounter_pool": null,
}, "encounter_chance": 0.0,
"FIELD_01": { "npc_ids": ["VILLAGER_1", "BLACKSMITH"]
"name": "新手田野",
"description": "微风习习,有低级怪物出没。",
"connections": {"S": "TOWN_01", "E": "FOREST_01"},
"eventPoolId": 1
}, },
"FOREST_01": { "FOREST_01": {
"name": "幽暗密林", "name": "新手森林",
"description": "树木茂密,光线昏暗,怪物更强。", "description": "有一些弱小的怪物。",
"connections": {"W": "FIELD_01"}, "connections": {"S": "TOWN_01", "E": "FOREST_02"},
"eventPoolId": 2 "encounter_chance": 0.6,
"encounter_pool": [
{"enemyId": "GOBLIN", "weight": 70},
{"enemyId": "WOLF", "weight": 30}
],
"npc_ids": []
} }
} }

18
config/npcs.json Normal file
View File

@ -0,0 +1,18 @@
{
"VILLAGER_1": {
"name": "老村长",
"dialogue": {
"greeting": "你好,旅行者。你看起来很强大。",
"quest_response": "你想要帮忙吗?我们的地窖里有老鼠。",
"shop_response": "我现在没有东西卖给你。"
}
},
"BLACKSMITH": {
"name": "铁匠李奥",
"dialogue": {
"greeting": "欢迎来到我的铁匠铺。",
"quest_response": "你有空吗?我的铁矿用完了。",
"shop_response": "看一看你需要什么工具和武器。"
}
}
}

36
config/quests.json Normal file
View File

@ -0,0 +1,36 @@
[
{
"id": "KILL_GOBLIN",
"name": "新手挑战:击败哥布林",
"description": "前往附近的森林,击败一只哥布林来证明你的勇气。",
"type": "kill",
"target_npc_id": "VILLAGER_1",
"target": {
"entityId": "GOBLIN",
"count": 1
},
"required_level": 1,
"rewards": {
"xp": 50,
"gold": 20,
"item_id": 1,
"item_quantity": 1
},
"next_quest_id": "FIND_NPC"
},
{
"id": "FIND_NPC",
"name": "寻找铁匠",
"description": "铁匠似乎有重要的消息要告诉你,去镇中心找他。",
"type": "talk",
"target": {
"npcId": "BLACKSMITH",
"count": 1
},
"required_level": 1,
"rewards": {
"xp": 100
},
"next_quest_id": null
}
]

View File

@ -1,21 +1,61 @@
{ {
"name": "hant", "name": "hant",
"health": 100, "health": 130,
"maxHealth": 100, "maxHealth": 130,
"attack": 15, "attack": 31,
"defense": 5, "defense": 9,
"level": 1, "level": 3,
"currentXp": 10, "currentXp": 25,
"xpToNextLevel": 100, "xpToNextLevel": 225,
"gold": 7, "gold": 321,
"inventory": [ "inventory": [
{
"id": 2,
"name": "\u7834\u65e7\u7684\u77ed\u5251",
"type": "weapon",
"description": "\u653b\u51fb\u529b\u5fae\u5f31\u3002",
"value": 50,
"effects": [],
"slot": "weapon",
"statModifiers": {
"attack": 5
}
},
{ {
"id": 1, "id": 1,
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34", "name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
"type": "potion", "type": "potion",
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002", "description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
"value": 10, "value": 10,
"effects": [] "effects": {
"heal": 20
},
"slot": null,
"statModifiers": []
},
{
"id": 1,
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
"type": "potion",
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
"value": 10,
"effects": {
"heal": 20
},
"slot": null,
"statModifiers": []
},
{
"id": 1,
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
"type": "potion",
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
"value": 10,
"effects": {
"heal": 20
},
"slot": null,
"statModifiers": []
} }
], ],
"activeQuests": [], "activeQuests": [],

View File

@ -2,10 +2,17 @@
namespace Game\Core; namespace Game\Core;
// 导入所有依赖和系统服务 // 导入所有依赖和系统服务
use Game\Database\AbilityRepository;
use Game\Database\DatabaseManager; use Game\Database\DatabaseManager;
use Game\Database\EnemyRepository;
use Game\Database\ItemRepository;
use Game\Database\JsonFileLoader;
use Game\Database\NPCRepository;
use Game\Database\QuestRepository;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\System\AbilityService; use Game\System\AbilityService;
use Game\System\EquipmentService;
use Game\System\SaveLoadService; use Game\System\SaveLoadService;
use Game\System\StateManager; use Game\System\StateManager;
use Game\System\UIService; use Game\System\UIService;
@ -39,6 +46,11 @@ class ServiceContainer {
private SaveLoadService $saveLoadService; // 新增属性 private SaveLoadService $saveLoadService; // 新增属性
// 存储所有已实例化的服务 // 存储所有已实例化的服务
private array $services = []; private array $services = [];
private ItemRepository $itemRepository;
private EnemyRepository $enemyRepository;
private AbilityRepository $abilityRepository;
private QuestRepository $questionRepository;
private NPCRepository $npcRepository;
public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) { public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet) {
$this->input = $input; $this->input = $input;
@ -66,20 +78,32 @@ class ServiceContainer {
*/ */
public function registerServices(): EventDispatcher { public function registerServices(): EventDispatcher {
$this->initializeInfrastructure(); $this->initializeInfrastructure();
// ⭐ 实例化 Repository 层
$jsonLoader = new JsonFileLoader();
$this->itemRepository = new ItemRepository($jsonLoader);
$this->enemyRepository = new EnemyRepository($jsonLoader);
$this->abilityRepository = new AbilityRepository($jsonLoader);
$this->questionRepository = new QuestRepository($jsonLoader);
$this->npcRepository = new NPCRepository($jsonLoader);
$this->saveLoadService = new SaveLoadService($this->eventDispatcher, $this->stateManager); $this->saveLoadService = new SaveLoadService($this->eventDispatcher, $this->stateManager);
// 1. UI 服务 (只需要 Dispatcher 和 StateManager) // 1. UI 服务 (只需要 Dispatcher 和 StateManager)
$this->register(UIService::class, new UIService($this->output, $this->stateManager)); $this->register(UIService::class, new UIService($this->output, $this->stateManager));
// 2. 核心逻辑服务 (依赖 Dispatcher, StateManager) // 2. 核心逻辑服务 (依赖 Dispatcher, StateManager)
$this->register(MapSystem::class, new MapSystem($this->eventDispatcher, $this->stateManager)); $this->register(MapSystem::class, new MapSystem($this->eventDispatcher, $this->stateManager,$this->npcRepository));
$this->register(CharacterService::class, new CharacterService($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(LootService::class, new LootService($this->eventDispatcher, $this->stateManager, $this->itemRepository, $this->enemyRepository));
$this->register(ItemService::class, new ItemService($this->eventDispatcher, $this->stateManager)); $this->register(ItemService::class, new ItemService($this->eventDispatcher, $this->stateManager));
$this->register(QuestService::class, new QuestService($this->eventDispatcher, $this->stateManager)); $this->register(QuestService::class, new QuestService($this->eventDispatcher, $this->stateManager, $this->questionRepository));
// ⭐ 实例化 EquipmentService
$this->register(EquipmentService::class, new EquipmentService($this->eventDispatcher, $this->stateManager));
// 3. I/O 交互服务 (依赖 Dispatcher, StateManager, I/O 接口) // 3. I/O 交互服务 (依赖 Dispatcher, StateManager, I/O 接口)
$this->register(InteractionSystem::class, $this->register(InteractionSystem::class,
new InteractionSystem($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper) new InteractionSystem($this->eventDispatcher, $this->stateManager, $this->npcRepository, $this->input, $this->output, $this->questionHelper)
); );
$this->register(BattleService::class, $this->register(BattleService::class,
new BattleService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper) new BattleService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
@ -89,12 +113,12 @@ class ServiceContainer {
); );
// ⭐ 实例化 AbilityService // ⭐ 实例化 AbilityService
$abilityService = new AbilityService($this->eventDispatcher, $this->stateManager); $abilityService = new AbilityService($this->eventDispatcher, $this->stateManager, $this->abilityRepository);
$this->register(AbilityService::class, $abilityService); $this->register(AbilityService::class, $abilityService);
// 4. 输入处理服务 (驱动主循环,必须最后注册,因为它依赖于所有 I/O 组件) // 4. 输入处理服务 (驱动主循环,必须最后注册,因为它依赖于所有 I/O 组件)
$this->register(InputHandler::class, $this->register(InputHandler::class,
new InputHandler($this->eventDispatcher, $this->input, $this->output, $this->questionHelper) new InputHandler($this->eventDispatcher, $this->input, $this->output, $this->questionHelper,$this->stateManager)
); );
// 返回分发器和状态管理器,供 GameCommand 使用 // 返回分发器和状态管理器,供 GameCommand 使用
@ -124,4 +148,9 @@ class ServiceContainer {
public function getStateManager(): StateManager { public function getStateManager(): StateManager {
return $this->stateManager; return $this->stateManager;
} }
// ⭐ 新增 Repository Getter (供需要配置数据的服务使用)
public function getItemRepository(): ItemRepository { return $this->itemRepository; }
public function getEnemyRepository(): EnemyRepository { return $this->enemyRepository; }
public function getAbilityRepository(): AbilityRepository { return $this->abilityRepository; }
} }

View File

@ -0,0 +1,19 @@
<?php
namespace Game\Database;
class AbilityRepository implements RepositoryInterface {
private array $data;
public function __construct(JsonFileLoader $loader) {
$this->data = $loader->load('abilities.json');
$this->data = array_combine(array_column($this->data, 'id'), $this->data);
}
public function find(int|string $id): ?array {
return $this->data[$id] ?? null;
}
public function findAll(): array {
return $this->data;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Game\Database;
class EnemyRepository implements RepositoryInterface {
private array $data;
public function __construct(JsonFileLoader $loader) {
$this->data = $loader->load('enemies.json');
// 将数组键设为 ID方便查找
$this->data = array_combine(array_column($this->data, 'id'), $this->data);
}
public function find(int|string $id): ?array {
return $this->data[$id] ?? null;
}
public function findAll(): array {
return $this->data;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Game\Database;
use Game\Model\Item;
class ItemRepository implements RepositoryInterface {
private array $data;
public function __construct(JsonFileLoader $loader) {
// 加载 items.json 文件
$this->data = $loader->load('items.json');
// 将数组键设为 ID方便查找
$this->data = array_combine(array_column($this->data, 'id'), $this->data);
}
public function find(int|string $id): ?array {
return $this->data[$id] ?? null;
}
public function findAll(): array {
return $this->data;
}
/**
* 辅助方法:通过 ID 创建 Item 实例
*/
public function createItem(int $id): ?Item {
$itemData = $this->find($id);
if (!$itemData) {
return null;
}
// 确保所有必要的键都存在,并处理下划线到驼峰的转换
return new Item(
$itemData['id'],
$itemData['name'],
$itemData['type'],
$itemData['description'],
$itemData['value'],
$itemData['effects'] ?? [],
$itemData['slot'] ?? null,
$itemData['stat_modifiers'] ?? []
);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Game\Database;
class JsonFileLoader {
private string $basePath = __DIR__ . '/../../config/';
/**
* 从指定文件加载数据
*/
public function load(string $filename): array {
$filePath = $this->basePath . $filename;
if (!file_exists($filePath)) {
throw new \RuntimeException("配置加载失败:文件不存在于 {$filePath}");
}
$content = file_get_contents($filePath);
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException("配置加载失败:{$filename} 不是有效的 JSON 格式。");
}
return $data;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Game\Database;
use Game\Model\NPC;
class NPCRepository implements RepositoryInterface {
private array $data;
public function __construct(JsonFileLoader $loader) {
// NPC 配置通常以 ID 为键存储在 JSON 文件的顶层,所以直接加载
$this->data = $loader->load('npcs.json');
}
public function find(int|string $id): ?array {
return $this->data[$id] ?? null;
}
public function findAll(): array {
return $this->data;
}
/**
* 辅助方法:创建 NPC 模型实例
*/
public function createNPC(string $id): ?NPC {
$data = $this->find($id);
if (!$data) {
return null;
}
// 假设 NPC 模型的构造函数是 public function __construct(string $id, string $name, array $dialogue)
return new NPC(
$id,
$data['name'],
$data['dialogue']
);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Game\Database;
class QuestRepository implements RepositoryInterface {
private array $data;
// ⭐ 新增属性:按 NPC ID 索引的任务列表
private array $questsByNpc = [];
public function __construct(JsonFileLoader $loader) {
$loadedData = $loader->load('quests.json');
// 使用 ID 作为键进行存储 (原始 data)
$this->data = array_combine(array_column($loadedData, 'id'), $loadedData);
// ⭐ 构建 questsByNpc 索引
$this->buildNpcQuestIndex();
}
/**
* 新增方法:构建按 NPC ID 查找任务的索引
*/
private function buildNpcQuestIndex(): void {
foreach ($this->data as $questId => $questData) {
// 检查任务是否有目标 NPC ID (target_npc_id) 或目标实体 ID (target_entity_id)
// 根据您提供的 quests.json
$targetId = $questData['target_npc_id'] ?? null;
if ($targetId) {
// 将任务 ID 添加到该 NPC 对应的列表
$this->questsByNpc[$targetId][] = $questId;
}
}
}
public function find(int|string $id): ?array {
return $this->data[$id] ?? null;
}
public function findAll(): array {
return $this->data;
}
/**
* 新增功能:通过 NPC ID 获取所有相关的任务 ID 列表
* 这个列表可能包含起始任务、后续任务等,需要业务逻辑进一步筛选
*/
public function getQuestsByNpc(string $npcId): array {
// 返回该 NPC ID 对应的所有任务 ID 列表,如果没有则返回空数组
return $this->questsByNpc[$npcId] ?? [];
}
/**
* 辅助方法:获取起始任务的 ID (方便游戏启动)
*/
public function getStartingQuestId(): ?string {
// 假设第一个任务就是起始任务
return array_key_first($this->data);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Game\Database;
interface RepositoryInterface {
/**
* 根据 ID 查找单个实体数据
*/
public function find(int|string $id): ?array;
/**
* 查找所有实体数据
*/
public function findAll(): array;
}

View File

@ -14,10 +14,10 @@ class Character {
// ⭐ 新增:魔法值 (MP/Mana) // ⭐ 新增:魔法值 (MP/Mana)
protected int $mana; protected int $mana;
protected int $maxMana; protected int $maxMana;
protected array $equipment = [];
// ⭐ 新增 Getter/Setter for Mana // ⭐ 新增 Getter/Setter for Mana
public function getMana(): int { return $this->mana; } public function getMana(): int { return $this->mana; }
public function getMaxMana(): int { return $this->maxMana; }
public function spendMana(int $cost): bool { public function spendMana(int $cost): bool {
if ($this->mana >= $cost) { if ($this->mana >= $cost) {
@ -45,14 +45,65 @@ class Character {
$this->mana = $maxMana; $this->mana = $maxMana;
} }
// ⭐ 新增方法:属性提升
public function increaseMaxHealth(int $amount): void {
$this->maxHealth += $amount;
// 提升最大生命值后,也要恢复当前生命值到新上限
$this->health = $this->maxHealth;
}
public function increaseAttack(int $amount): void {
$this->attack += $amount;
}
public function increaseDefense(int $amount): void {
$this->defense += $amount;
}
// ⭐ 新增方法:恢复所有资源 (升级时的福利)
public function fullRestore(): void {
$this->health = $this->maxHealth;
if (property_exists($this, 'mana')) { // 检查 Mana 属性是否存在
$this->mana = $this->maxMana;
}
}
public function getAttack(): int {
// 基础攻击力 + 装备修正
return $this->attack + $this->getStatModifier('attack');
}
/**
* 获取修正后的防御力
*/
public function getDefense(): int {
// 基础防御力 + 装备修正
return $this->defense + $this->getStatModifier('defense');
}
// MaxHealth 和 MaxMana 也可以类似处理
public function getMaxHealth(): int {
return $this->maxHealth + $this->getStatModifier('health');
}
public function getMaxMana(): int {
return $this->maxMana + $this->getStatModifier('mana');
}
public function getName(): string { return $this->name; } public function getName(): string { return $this->name; }
public function getHealth(): int { return $this->health; } public function getHealth(): int { return $this->health; }
public function getMaxHealth(): int { return $this->maxHealth; }
public function getAttack(): int { return $this->attack; }
public function getDefense(): int { return $this->defense; }
public function isAlive(): bool { return $this->health > 0; } public function isAlive(): bool { return $this->health > 0; }
// ⭐ 新增:获取某个槽位的属性修正总和
public function getStatModifier(string $statName): int {
$totalModifier = 0;
foreach ($this->equipment as $item) {
if ($item && isset($item->statModifiers[$statName])) {
$totalModifier += $item->statModifiers[$statName];
}
}
return $totalModifier;
}
/** /**
* 接收伤害,返回实际受到的伤害量 * 接收伤害,返回实际受到的伤害量
*/ */

View File

@ -5,18 +5,24 @@ namespace Game\Model;
class Enemy extends Character { class Enemy extends Character {
public string $id; public string $id;
public int $xpValue; // 击败后获得的经验值 public int $xpValue; // 击败后获得的经验值
// ⭐ 新增属性:存储该敌人的掉落表配置
protected array $lootTable;
public function __construct(string $id, string $name, int $health, int $attack, int $defense, int $xpValue) { // ⭐ 修正构造函数:增加 $lootTable 参数
public function __construct(string $id, string $name, int $health, int $attack, int $defense, int $xpValue, array $lootTable) {
// 调用父类 (Character) 的构造函数来初始化核心属性 // 调用父类 (Character) 的构造函数来初始化核心属性
parent::__construct($name, $health, $attack, $defense); parent::__construct($name, $health, $attack, $defense);
$this->id = $id; $this->id = $id;
$this->xpValue = $xpValue; $this->xpValue = $xpValue;
$this->lootTable = $lootTable; // ⭐ 赋值
} }
// Enemy 特有的 Getter 方法 // Enemy 特有的 Getter 方法
public function getId(): string { return $this->id; } public function getId(): string { return $this->id; }
public function getXpValue(): int { return $this->xpValue; } public function getXpValue(): int { return $this->xpValue; }
// ⭐ 新增 Getter
public function getLootTable(): array { return $this->lootTable; }
// 注意takeDamage() 和 isAlive() 等方法都直接继承自 Character无需重复实现 // 注意takeDamage() 和 isAlive() 等方法都直接继承自 Character无需重复实现
} }

View File

@ -11,13 +11,24 @@ class Item {
public string $description; public string $description;
public int $value; // 卖出价格 public int $value; // 卖出价格
public array $effects; // ⭐ 新增:存储效果参数 e.g., ['heal' => 20] public array $effects; // ⭐ 新增:存储效果参数 e.g., ['heal' => 20]
public function __construct(int $id, string $name, string $type, string $description, int $value, array $effects = []) {
// ⭐ 新增:装备槽位 (null/none 表示非装备品)
public ?string $slot = null; // e.g., 'weapon', 'helmet', 'armor'
// ⭐ 新增:装备属性修正 (影响角色属性)
public array $statModifiers = []; // e.g., ['attack' => 5, 'defense' => 2]
public function __construct(int $id, string $name, string $type, string $description, int $value, array $effects = [], ?string $slot = null, array $statModifiers = []) {
$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; // 赋值 $this->effects = $effects;
// ⭐ 初始化新属性
$this->slot = $slot;
$this->statModifiers = $statModifiers;
} }
public function toArray() public function toArray()
@ -29,6 +40,8 @@ class Item {
'description' => $this->description, 'description' => $this->description,
'value' => $this->value, 'value' => $this->value,
'effects' => $this->effects, 'effects' => $this->effects,
'slot' => $this->slot,
'statModifiers' => $this->statModifiers,
]; ];
} }
} }

View File

@ -1,21 +1,23 @@
<?php <?php
namespace Game\Model; namespace Game\Model;
/**
* MapTile: 单个地图区域的数据模型。
*/
class MapTile { class MapTile {
public string $id; public string $id;
public string $name; public string $name;
public string $description; public string $description;
public array $connections; // 存储连接:['N' => 'FOREST_02', 'S' => 'TOWN_01'] public array $connections;
public int $eventPoolId; // 区域对应的事件池ID // ⭐ 修正/新增属性:遇敌配置
public ?array $encounterPool; // null 或包含 {"enemyId": "ID", "weight": N} 的数组
public function __construct(string $id, string $name, string $description, array $connections, int $eventPoolId) { public float $encounterChance; // 遇敌几率 (0.0 到 1.0)
public array $npcIds;
public function __construct(string $id, string $name, string $description, array $connections, ?array $encounterPool, float $encounterChance,array $npcIds = []) {
$this->id = $id; $this->id = $id;
$this->name = $name; $this->name = $name;
$this->description = $description; $this->description = $description;
$this->connections = $connections; $this->connections = $connections;
$this->eventPoolId = $eventPoolId; // ⭐ 赋值
$this->encounterPool = $encounterPool;
$this->encounterChance = $encounterChance;
$this->npcIds = $npcIds; // ⭐ 赋值
} }
} }

View File

@ -9,12 +9,55 @@ class Player extends Character {
// ⭐ 新增:已学习的技能列表 // ⭐ 新增:已学习的技能列表
protected array $abilities = []; // 存储 Ability 实例 protected array $abilities = []; // 存储 Ability 实例
// ⭐ 新增:装备槽位,键是槽位名,值是 Item 对象
protected array $equipment = [
'weapon' => null,
'armor' => null,
'helmet' => null,
// 可根据需要添加 'ring', 'amulet' 等
];
public function __construct(string $name, int $maxHealth, int $attack, int $defense,int $maxMana = 50) { public function __construct(string $name, int $maxHealth, int $attack, int $defense,int $maxMana = 50) {
// 调用父类 (Character) 的构造函数来初始化核心属性 // 调用父类 (Character) 的构造函数来初始化核心属性
parent::__construct($name, $maxHealth, $attack, $defense,$maxMana); parent::__construct($name, $maxHealth, $attack, $defense,$maxMana);
} }
// ⭐ 新增:获取装备
public function getEquipment(): array {
return $this->equipment;
}
// ⭐ 新增:设置装备 (由 EquipmentService 调用)
public function setEquipment(string $slot, ?Item $item): void {
$this->equipment[$slot] = $item;
}
// ⭐ 新增方法:提升等级
public function incrementLevel(): void {
$this->level++;
}
// ⭐ 新增方法:扣除升级所需的经验值并计算新的经验值目标
public function subtractXpToNextLevel(): void {
// 计算溢出的经验值(如果有)
$excessXp = $this->currentXp - $this->xpToNextLevel;
// 设置新的经验值
$this->currentXp = $excessXp;
// 计算下一次升级所需的经验值 (简化公式:增加 50% 难度)
$this->xpToNextLevel = (int)($this->xpToNextLevel * 1.5);
}
// ⭐ 新增方法:设置经验值 (用于存档加载)
public function setXpToNextLevel(int $xp): void {
$this->xpToNextLevel = $xp;
}
// ⭐ 确保 setLevel 存在 (用于存档加载)
public function setLevel(int $level): void {
$this->level = $level;
}
// ⭐ 新增方法:学习技能 // ⭐ 新增方法:学习技能
public function learnAbility(Ability $ability): void { public function learnAbility(Ability $ability): void {
$this->abilities[$ability->id] = $ability; $this->abilities[$ability->id] = $ability;
@ -38,14 +81,27 @@ class Player extends Character {
$this->gold += $amount; $this->gold += $amount;
} }
protected array $activeQuests = []; // 存储进行中的任务进度,格式: ['questId' => ['currentCount' => 0, 'isCompleted' => false]]
protected array $completedQuests = []; // 存储已完成的任务 ID protected array $completedQuests = []; // 存储已完成的任务 ID
// ⭐ 新增方法:添加/接受任务 // ⭐ 新增方法:添加/接受任务
public function addActiveQuest(string $questId, int $targetCount): void { /**
$this->activeQuests[$questId] = ['currentCount' => 0, 'isCompleted' => false, 'targetCount' => $targetCount]; * @var Quest[] 当前活跃任务列表 (键为 Quest ID)
*/
protected array $activeQuests = [];
// ...
// ⭐ 修正方法:接受 Quest 对象
public function addActiveQuest(Quest $quest): void {
// 使用任务 ID 作为键,存储整个 Quest 对象
$this->activeQuests[$quest->getId()] = $quest;
}
// ⭐ 确保 setInventory 存在 (用于存档加载)
// 如果存档时将 Quest 对象序列化了,这里可能需要反序列化逻辑,
// 但现在我们只确保它可以接受数组。
public function setActiveQuests(array $quests): void {
$this->activeQuests = $quests;
} }
// ⭐ 新增方法:获取进行中的任务
public function getActiveQuests(): array { public function getActiveQuests(): array {
return $this->activeQuests; return $this->activeQuests;
} }
@ -114,9 +170,8 @@ class Player extends Character {
} }
// ⭐ 新增 Player 特有的 Setter // ⭐ 新增 Player 特有的 Setter
public function setLevel(int $level): void { $this->level = $level; }
public function setCurrentXp(int $xp): void { $this->currentXp = $xp; } 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 setGold(int $gold): void { $this->gold = $gold; }
public function setInventory(array $inventory): void { public function setInventory(array $inventory): void {
// WARNING: 这里的 inventory 数组可能只包含序列化数据,需要确保 Item 实例化 // WARNING: 这里的 inventory 数组可能只包含序列化数据,需要确保 Item 实例化
@ -125,7 +180,6 @@ class Player extends Character {
} }
public function getCompletedQuests(): array { return $this->completedQuests; } // 确保这个 Getter 存在 public function getCompletedQuests(): array { return $this->completedQuests; } // 确保这个 Getter 存在
public function setCompletedQuests(array $quests): void { $this->completedQuests = $quests; } public function setCompletedQuests(array $quests): void { $this->completedQuests = $quests; }
public function setActiveQuests(array $quests): void { $this->activeQuests = $quests; }
public function setHealth(mixed $health) public function setHealth(mixed $health)
{ {

View File

@ -2,17 +2,21 @@
namespace Game\Model; namespace Game\Model;
/** /**
* Quest: 任务模型 * Quest: 任务模型 - 包含配置数据和运行时进度
*/ */
class Quest { class Quest {
// --- 配置数据 (来自 JSON) ---
public string $id; public string $id;
public string $name; public string $name;
public string $description; public string $description;
public string $type; // e.g., 'kill', 'fetch', 'talk' public string $type;
public array $target; // 目标要求e.g., ['targetId' => 'GOBLIN_1', 'count' => 5] public array $target; // e.g., ['entityId' => 'GOBLIN', 'count' => 1]
public array $rewards; // 奖励e.g., ['xp' => 100, 'itemId' => 1] public array $rewards;
public bool $isRepeatable = false; public bool $isRepeatable = false;
// ⭐ 新增:运行时状态,默认为 0
protected int $currentCount = 0;
public function __construct(string $id, string $name, string $description, string $type, array $target, array $rewards) { public function __construct(string $id, string $name, string $description, string $type, array $target, array $rewards) {
$this->id = $id; $this->id = $id;
$this->name = $name; $this->name = $name;
@ -21,4 +25,58 @@ class Quest {
$this->target = $target; $this->target = $target;
$this->rewards = $rewards; $this->rewards = $rewards;
} }
// --- 进度管理方法 (用于业务逻辑) ---
/**
* 增加任务当前进度
*/
public function incrementCurrentCount(int $amount = 1): void {
// 目标数量存储在 $this->target['count'] 中
$targetCount = $this->target['count'] ?? 1;
$this->currentCount = min($targetCount, $this->currentCount + $amount);
}
/**
* 检查任务是否完成
*/
public function isCompleted(): bool {
$targetCount = $this->target['count'] ?? 1;
return $this->currentCount >= $targetCount;
}
// --- Getter/Setter (用于 UI 和 SaveLoadService) ---
public function getName(): string { return $this->name; }
public function getDescription(): string { return $this->description; }
public function getId(): string { return $this->id; }
public function getType(): string { return $this->type; }
public function getTarget(): array { return $this->target; }
public function getRewards(): array { return $this->rewards; }
/**
* 获取当前进度 (用于存档)
*/
public function getCurrentCount(): int { return $this->currentCount; }
/**
* 核心修正:设置当前进度 (用于存档加载)
*/
public function setCurrentCount(int $count): void {
$this->currentCount = $count;
}
public function toArray()
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'type' => $this->type,
'target' => $this->target,
'rewards' => $this->rewards,
'isRepeatable' => $this->isRepeatable,
'currentCount' => $this->currentCount,
];
}
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Game\System; namespace Game\System;
use Game\Database\AbilityRepository;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
@ -15,34 +16,34 @@ class AbilityService implements EventListenerInterface {
private EventDispatcher $dispatcher; private EventDispatcher $dispatcher;
private StateManager $stateManager; private StateManager $stateManager;
private array $abilityData; // 存储所有技能配置
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager) { // ... 现有属性 ...
private AbilityRepository $abilityRepository;
// ⭐ 修正构造函数:注入 AbilityRepository
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, AbilityRepository $abilityRepository) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->stateManager = $stateManager; $this->stateManager = $stateManager;
$this->loadAbilityData(); $this->abilityRepository = $abilityRepository;
} }
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 { public function learnInitialAbilities(Player $player): void {
// 确保 FIREBALL 存在 $initialAbilities = ['FIREBALL', 'HEAL']; // 仍然可以硬编码初始技能的ID
if (isset($this->abilityData['FIREBALL'])) {
$data = $this->abilityData['FIREBALL']; foreach ($initialAbilities as $id) {
$player->learnAbility(new Ability('FIREBALL', $data['name'], $data['type'], $data['cost'], $data['power'], $data['scaling'])); // ⭐ 使用 Repository 获取数据
} $data = $this->abilityRepository->find($id);
if (isset($this->abilityData['HEAL'])) { if ($data) {
$data = $this->abilityData['HEAL']; $player->learnAbility(new Ability(
$player->learnAbility(new Ability('HEAL', $data['name'], $data['type'], $data['cost'], $data['power'], $data['scaling'])); $data['id'],
$data['name'],
$data['type'],
$data['mana_cost'],
$data['power'],
$data['scaling']
));
}
} }
} }

View File

@ -5,7 +5,6 @@ use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
use Game\Model\Enemy; use Game\Model\Enemy;
use Game\Model\Player; // 即使是 Party也依赖 Player
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\QuestionHelper;
@ -54,8 +53,7 @@ class BattleService implements EventListenerInterface {
switch ($event->getType()) { switch ($event->getType()) {
case 'StartBattleEvent': case 'StartBattleEvent':
// 收到 MapSystem 触发的战斗开始事件 // 收到 MapSystem 触发的战斗开始事件
$enemyId = $event->getPayload()['enemyId']; $this->startBattle($event->getPayload()['enemy']);
$this->startBattle($enemyId);
break; break;
} }
} }
@ -63,17 +61,8 @@ class BattleService implements EventListenerInterface {
/** /**
* 1. 初始化战斗状态 * 1. 初始化战斗状态
*/ */
private function startBattle(int $enemyId): void { private function startBattle(Enemy $enemy): void {
// ... 初始化敌人逻辑 (继承自 Character) ... $this->currentEnemy = $enemy;
$enemyData = $this->loadEnemyData($enemyId);
$this->currentEnemy = new Enemy(
(string)$enemyId,
$enemyData['name'],
$enemyData['health'],
$enemyData['attack'],
$enemyData['defense'],
$enemyData['xp']
);
$this->inBattle = true; $this->inBattle = true;
$this->dispatcher->dispatch(new Event('SystemMessage', [ $this->dispatcher->dispatch(new Event('SystemMessage', [

View File

@ -58,19 +58,20 @@ class CharacterService implements EventListenerInterface {
* 执行升级操作 * 执行升级操作
*/ */
private function levelUp(Player $player): void { private function levelUp(Player $player): void {
// 1. 扣除经验,提升等级 // 1. 扣除经验,提升等级
$player->subtractXpToNextLevel(); // 假设我们在 Player 模型中添加此方法 $player->subtractXpToNextLevel();
$player->incrementLevel(); // 假设我们在 Player 模型中添加此方法 $player->incrementLevel();
// 2. 提升属性 (简化版:每次升级增加固定属性) // 2. 提升属性 (现在这些方法在 Character/Player 中都已实现)
$player->increaseMaxHealth(10); $player->increaseMaxHealth(15); // 增加生命
$player->increaseAttack(2); $player->increaseAttack(3); // 增加攻击
$player->increaseDefense(1); $player->increaseDefense(2); // 增加防御
$player->restoreMana(10); // 恢复一些 Mana (或增加 maxMana)
$player->fullRestore(); // 恢复所有生命和魔法
// 3. 升级后回满血 // 3. 触发升级事件 (如果需要其他系统知道)
$player->heal($player->getMaxHealth()); // 使用 Character 基类中的 heal()
// 4. 触发升级事件 (如果需要其他系统知道)
$this->dispatcher->dispatch(new Event('LevelUpEvent', ['newLevel' => $player->getLevel()])); $this->dispatcher->dispatch(new Event('LevelUpEvent', ['newLevel' => $player->getLevel()]));
} }
} }

View File

@ -0,0 +1,108 @@
<?php
namespace Game\System;
use Game\Event\Event;
use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher;
use Game\Model\Item;
use Game\Model\Player;
/**
* EquipmentService: 负责处理玩家装备/卸下物品的逻辑。
*/
class EquipmentService 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 'EquipItemEvent':
$payload = $event->getPayload();
$this->handleEquipItem($payload['itemIndex']);
break;
case 'UnequipItemEvent':
$payload = $event->getPayload();
$this->handleUnequipItem($payload['slot']);
break;
}
}
/**
* 处理装备请求
*/
private function handleEquipItem(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];
$slot = $item->slot;
if (!$slot) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 该物品不是装备品。']));
return;
}
// 1. 尝试卸下当前装备
$currentEquipment = $player->getEquipment()[$slot];
if ($currentEquipment) {
$this->unequipItem($player, $slot, $currentEquipment);
}
// 2. 装备新物品
$player->setEquipment($slot, $item);
// 3. 将物品从背包移除(它现在在装备槽中)
$player->removeItemByIndex($itemIndex);
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "⚔️ 装备了 <fg=cyan>{$item->name}</> 到 {$slot} 槽位!"
]));
// 触发状态/UI更新
$this->dispatcher->dispatch(new Event('StatUpdateEvent'));
$this->dispatcher->dispatch(new Event('InventoryUpdateEvent'));
}
/**
* 辅助方法:卸下物品
*/
private function unequipItem(Player $player, string $slot, Item $itemToUnequip): void {
// 1. 将物品放回背包 (需要 ItemService 保证 Item 对象正确放回)
$player->addItem($itemToUnequip);
// 2. 清空槽位
$player->setEquipment($slot, null);
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "🗑️ 卸下了 {$itemToUnequip->name} 并放回背包。"
]));
}
/**
* 处理卸下请求
*/
private function handleUnequipItem(string $slot): void {
$player = $this->stateManager->getPlayer();
$item = $player->getEquipment()[$slot] ?? null;
if (!$item) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "{$slot} 槽位没有装备物品。"]));
return;
}
$this->unequipItem($player, $slot, $item);
$this->dispatcher->dispatch(new Event('StatUpdateEvent'));
$this->dispatcher->dispatch(new Event('InventoryUpdateEvent'));
}
}

View File

@ -18,10 +18,12 @@ class InputHandler {
private InputInterface $input; private InputInterface $input;
private OutputInterface $output; private OutputInterface $output;
private QuestionHelper $helper; private QuestionHelper $helper;
private StateManager $stateManager;
public function __construct(EventDispatcher $dispatcher, InputInterface $input, OutputInterface $output, QuestionHelper $helper) { public function __construct(EventDispatcher $dispatcher, InputInterface $input, OutputInterface $output, QuestionHelper $helper, StateManager $stateManager) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->input = $input; $this->input = $input;
$this->stateManager = $stateManager;
$this->output = $output; $this->output = $output;
$this->helper = $helper; $this->helper = $helper;
} }
@ -56,6 +58,9 @@ class InputHandler {
// 状态显示请求,由于 StateManager 是状态持有者EventDispatcher 会分发给 UIService // 状态显示请求,由于 StateManager 是状态持有者EventDispatcher 会分发给 UIService
$this->dispatcher->dispatch(new Event('ShowStatsRequest')); $this->dispatcher->dispatch(new Event('ShowStatsRequest'));
break; break;
case 'T': // T 代表交谈 (Talk)
$this->dispatcher->dispatch(new Event('AttemptTalkEvent'));
break;
case 'I': // ⭐ 新增交互指令 case 'I': // ⭐ 新增交互指令
// 模拟 MapSystem 发现了一个 NPC // 模拟 MapSystem 发现了一个 NPC
$this->dispatcher->dispatch(new Event('AttemptInteractEvent', ['npcId' => 'VILLAGER_1'])); $this->dispatcher->dispatch(new Event('AttemptInteractEvent', ['npcId' => 'VILLAGER_1']));
@ -88,10 +93,10 @@ class InputHandler {
* 处理背包输入和子菜单 * 处理背包输入和子菜单
*/ */
private function handleInventoryInput(): void { private function handleInventoryInput(): void {
// 通知 UI 打印背包内容 // 1. 显示背包
$this->dispatcher->dispatch(new Event('ShowInventoryRequest')); $this->dispatcher->dispatch(new Event('ShowInventoryRequest'));
$question = new Question("> 请输入要使用的物品编号 (或 [X] 退出)"); $question = new Question("> 请输入物品编号进行操作 ([E]装备/使用 | [U]卸下 | [X]退出)");
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? ''); $choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
if ($choice === 'X') { if ($choice === 'X') {
@ -101,10 +106,65 @@ class InputHandler {
if (is_numeric($choice)) { if (is_numeric($choice)) {
$itemIndex = (int)$choice; $itemIndex = (int)$choice;
// 触发使用物品事件ItemService 监听并处理 $this->handleItemAction($itemIndex); // ⭐ 移交给新方法处理
$this->dispatcher->dispatch(new Event('UseItemEvent', ['itemIndex' => $itemIndex])); } elseif ($choice === 'U') {
$this->handleUnequipAction();
} else { } else {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的编号。'])); $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的指令。请使用编号E, U, X。']));
}
}
/**
* 新增:处理单个物品的使用/装备逻辑
*/
private function handleItemAction(int $itemIndex): void {
$player = $this->stateManager->getPlayer();
$item = $player->getInventory()[$itemIndex] ?? null;
if (!$item) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的物品编号。']));
return;
}
if ($item->type === 'potion') {
// 消耗品:直接使用
$this->dispatcher->dispatch(new Event('UseItemEvent', ['itemIndex' => $itemIndex]));
} elseif ($item->slot) {
// 装备品:触发装备事件
$this->dispatcher->dispatch(new Event('EquipItemEvent', ['itemIndex' => $itemIndex]));
} else {
// 杂物:无操作
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '该物品无法使用或装备。']));
}
}
/**
* 新增:处理卸下逻辑
*/
private function handleUnequipAction(): void {
$player = $this->stateManager->getPlayer();
$equipment = $player->getEquipment();
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '--- 选择要卸下的槽位 ---']));
$availableSlots = [];
foreach ($equipment as $slot => $item) {
if ($item) {
$this->dispatcher->dispatch(new Event('SystemMessage', [" [{$slot}] : {$item->name}"]));
$availableSlots[] = $slot;
}
}
if (empty($availableSlots)) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '没有物品可以卸下。']));
return;
}
$question = new Question("> 输入槽位名称 (e.g., weapon) 或 X 取消:");
$slotChoice = strtolower($this->helper->ask($this->input, $this->output, $question) ?? '');
if ($slotChoice !== 'x' && in_array($slotChoice, $availableSlots)) {
$this->dispatcher->dispatch(new Event('UnequipItemEvent', ['slot' => $slotChoice]));
} }
} }
public function setSaveLoadService(SaveLoadService $service): void { public function setSaveLoadService(SaveLoadService $service): void {

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Game\System; namespace Game\System;
use Game\Database\NPCRepository;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
@ -17,15 +18,17 @@ class InteractionSystem implements EventListenerInterface {
private EventDispatcher $dispatcher; private EventDispatcher $dispatcher;
private StateManager $stateManager; private StateManager $stateManager;
private NPCRepository $npcRepository; // ⭐ 新增属性
// 输入依赖 // 输入依赖
private InputInterface $input; private InputInterface $input;
private OutputInterface $output; private OutputInterface $output;
private QuestionHelper $helper; private QuestionHelper $helper;
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, InputInterface $input, OutputInterface $output, QuestionHelper $helper) { // ⭐ 修正构造函数:注入 NPCRepository
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, NPCRepository $npcRepository, InputInterface $input, OutputInterface $output, QuestionHelper $helper) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->stateManager = $stateManager; $this->stateManager = $stateManager;
$this->npcRepository = $npcRepository; // ⭐ 赋值
$this->input = $input; $this->input = $input;
$this->output = $output; $this->output = $output;
$this->helper = $helper; $this->helper = $helper;
@ -38,6 +41,11 @@ class InteractionSystem implements EventListenerInterface {
$npcId = $event->getPayload()['npcId']; $npcId = $event->getPayload()['npcId'];
$this->startInteraction($npcId); $this->startInteraction($npcId);
break; break;
// ⭐ 修正事件名称
case 'StartInteractionEvent':
$npcId = $event->getPayload()['npcId'];
$this->startInteraction($npcId);
break;
} }
} }
@ -45,9 +53,11 @@ class InteractionSystem implements EventListenerInterface {
* 1. 初始化 NPC 交互 * 1. 初始化 NPC 交互
*/ */
private function startInteraction(string $npcId): void { private function startInteraction(string $npcId): void {
$npc = $this->loadNPC($npcId); // ⭐ 修正:使用 Repository 加载 NPC
$npc = $this->npcRepository->createNPC($npcId);
if (!$npc) { if (!$npc) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈。"])); $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈 ({$npcId} 不存在)"]));
return; return;
} }

View File

@ -1,9 +1,12 @@
<?php <?php
namespace Game\System; namespace Game\System;
use Game\Database\EnemyRepository;
use Game\Database\ItemRepository;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
use Game\Model\Enemy;
use Game\Model\Item; use Game\Model\Item;
/** /**
@ -13,37 +16,83 @@ class LootService implements EventListenerInterface {
private EventDispatcher $dispatcher; private EventDispatcher $dispatcher;
private StateManager $stateManager; private StateManager $stateManager;
// ... 现有属性 ...
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager) { private ItemRepository $itemRepository;
private EnemyRepository $enemyRepository;
// ⭐ 修正构造函数:注入 Repositories
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, ItemRepository $itemRepository, EnemyRepository $enemyRepository) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->stateManager = $stateManager; $this->stateManager = $stateManager;
$this->itemRepository = $itemRepository;
$this->enemyRepository = $enemyRepository;
}
public function createEnemy(string $id): ?Enemy {
// ⭐ 使用 Repository 获取数据
$data = $this->enemyRepository->find($id);
if (!$data) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 错误敌人ID '{$id}' 不存在。"]));
return null;
}
// ⭐ 修正实例化 Enemy传入 loot_table
return new Enemy(
$data['id'],
$data['name'],
$data['health'],
$data['attack'],
$data['defense'],
$data['xp_reward'],
$data['loot_table'] ?? [] // 传递 loot_table
);
} }
public function handleEvent(Event $event): void { public function handleEvent(Event $event): void {
switch ($event->getType()) { switch ($event->getType()) {
case 'LootDropEvent': case 'LootDropEvent':
$enemyId = $event->getPayload()['enemyId']; $enemyId = $event->getPayload()['enemyId'];
// LootDropEvent 应该由 BattleService 在敌人死亡时触发
$this->handleLootDrop($enemyId); $this->handleLootDrop($enemyId);
break; break;
case 'LootFoundEvent': // 响应 MapSystem 探索时发现的宝箱 case 'LootFoundEvent': // 响应 MapSystem 探索时发现的宝箱
$lootId = $event->getPayload()['lootId']; $lootId = $event->getPayload()['lootId'];
$this->handleLootFound($lootId); $this->handleLootFound($lootId);
break; break;
// ... 现有事件 ...
case 'ShopPurchaseEvent': // ⭐ 响应商店购买 case 'ShopPurchaseEvent': // ⭐ 响应商店购买
$itemId = $event->getPayload()['itemId']; $itemId = $event->getPayload()['itemId'];
$this->giveItemToPlayer($itemId); $this->giveItemToPlayer($itemId);
break; break;
// ⭐ 响应遇敌事件
case 'EncounterEnemyEvent':
$enemyId = $event->getPayload()['enemyId'];
$newEnemy = $this->createEnemy($enemyId);
if ($newEnemy) {
// 通知 BattleService 启动战斗
$this->dispatcher->dispatch(new Event('StartBattleEvent', ['enemy' => $newEnemy]));
}
break;
} }
} }
/** /**
* 处理敌人死亡时的掉落逻辑 * 修正方法:处理敌人死亡时的掉落逻辑(从配置中加载数据)
*/ */
private function handleLootDrop(string $enemyId): void { private function handleLootDrop(string $enemyId): void {
$goldAmount = rand(5, 15); // 随机掉落 5 到 15 金币 // 1. 获取敌人配置数据
// 1. 发放金币 $enemyData = $this->enemyRepository->find($enemyId);
if (!$enemyData) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 错误:无法找到敌人 {$enemyId} 的掉落配置。"]));
return;
}
// --- 2. 掉落金币 (从配置中获取范围) ---
$minGold = $enemyData['min_gold'] ?? 5; // 使用配置,默认 5
$maxGold = $enemyData['max_gold'] ?? 15; // 使用配置,默认 15
$goldAmount = rand($minGold, $maxGold);
$player = $this->stateManager->getPlayer(); $player = $this->stateManager->getPlayer();
$player->gainGold($goldAmount); $player->gainGold($goldAmount);
@ -51,10 +100,19 @@ class LootService implements EventListenerInterface {
'message' => "💰 获得了 <fg=yellow>{$goldAmount}</> 金币。" 'message' => "💰 获得了 <fg=yellow>{$goldAmount}</> 金币。"
])); ]));
// 简化:总是掉落物品 ID 1 (小药水) // --- 3. 掉落物品 (从配置的 loot_table 中获取) ---
$roll = rand(1, 100); $lootTable = $enemyData['loot_table'] ?? [];
if ($roll <= 70) { // 70% 掉落几率
$this->giveItemToPlayer(1); if (!empty($lootTable)) {
$droppedItemId = $this->getRandomItemIdFromLootTable($lootTable);
if ($droppedItemId !== null) { // 成功掉落物品
$this->giveItemToPlayer($droppedItemId);
} else {
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "战利品很少,只找到了一些零钱。"
]));
}
} else { } else {
$this->dispatcher->dispatch(new Event('SystemMessage', [ $this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "战利品很少,只找到了一些零钱。" 'message' => "战利品很少,只找到了一些零钱。"
@ -63,10 +121,37 @@ class LootService implements EventListenerInterface {
} }
/** /**
* 处理探索时发现的宝箱/固定物品 * 新增方法:根据权重随机选择物品 ID
*/
private function getRandomItemIdFromLootTable(array $lootTable): ?int {
$totalChance = array_sum(array_column($lootTable, 'chance'));
// 随机数范围是 1 到 100
$randValue = rand(1, 100);
// 如果随机值大于总几率,则表示未掉落任何物品
// 例如:总几率为 80随机数是 85则不掉落
if ($randValue > $totalChance) {
return null;
}
$currentChance = 0;
foreach ($lootTable as $item) {
$currentChance += $item['chance'];
if ($randValue <= $currentChance) {
// 确保 item_id 是整数类型
return (int)$item['item_id'];
}
}
return null;
}
/**
* 处理探索时发现的宝箱/固定物品 (保持不变)
*/ */
private function handleLootFound(int $lootId): void { private function handleLootFound(int $lootId): void {
// 假设 lootId 5 是一个宝箱,里面是物品 ID 2 // ...
if ($lootId === 5) { if ($lootId === 5) {
$this->dispatcher->dispatch(new Event('SystemMessage', [ $this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "🗝️ 你打开了宝箱!" 'message' => "🗝️ 你打开了宝箱!"
@ -76,18 +161,16 @@ class LootService implements EventListenerInterface {
} }
/** /**
* 核心逻辑:创建物品实例并添加到玩家背包 * 核心逻辑:创建物品实例并添加到玩家背包 (保持不变)
*/ */
private function giveItemToPlayer(int $itemId): void { private function giveItemToPlayer(int $itemId): void {
$itemData = $this->loadItemData($itemId); // ...
$item = $this->itemRepository->createItem($itemId);
$item = new Item( if (!$item) {
$itemId, $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 物品ID '{$itemId}' 不存在,无法获得。"]));
$itemData['name'], return;
$itemData['type'], }
$itemData['description'],
$itemData['value']
);
$player = $this->stateManager->getPlayer(); $player = $this->stateManager->getPlayer();
$player->addItem($item); $player->addItem($item);
@ -99,16 +182,4 @@ class LootService implements EventListenerInterface {
// 触发 InventoryUpdateEvent (未来用于 UI 实时更新) // 触发 InventoryUpdateEvent (未来用于 UI 实时更新)
$this->dispatcher->dispatch(new Event('InventoryUpdateEvent', ['playerInventory' => $player->getInventory()])); $this->dispatcher->dispatch(new Event('InventoryUpdateEvent', ['playerInventory' => $player->getInventory()]));
} }
/**
* 模拟从配置中加载物品数据
*/
private function loadItemData(int $id): array {
return match ($id) {
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' => []],
};
}
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Game\System; namespace Game\System;
use Game\Database\NPCRepository;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
@ -12,10 +13,12 @@ class MapSystem implements EventListenerInterface {
private EventDispatcher $dispatcher; private EventDispatcher $dispatcher;
private StateManager $stateManager; private StateManager $stateManager;
private array $mapData; private array $mapData;
private NPCRepository $npcRepository;
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager) { public function __construct(EventDispatcher $dispatcher, StateManager $stateManager,NPCRepository $npcRepository) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->stateManager = $stateManager; $this->stateManager = $stateManager;
$this->npcRepository = $npcRepository;
$this->loadMapData(); $this->loadMapData();
// 游戏启动时,设置初始区域状态到 StateManager // 游戏启动时,设置初始区域状态到 StateManager
@ -35,7 +38,16 @@ class MapSystem implements EventListenerInterface {
throw new \Exception("MapTile ID '{$tileId}' not found in configuration."); throw new \Exception("MapTile ID '{$tileId}' not found in configuration.");
} }
$data = $this->mapData[$tileId]; $data = $this->mapData[$tileId];
return new MapTile($tileId, $data['name'], $data['description'], $data['connections'], $data['eventPoolId']); // ⭐ 修正 MapTile 实例化参数
return new MapTile(
$tileId,
$data['name'],
$data['description'],
$data['connections'],
$data['encounter_pool'] ?? null, // 传入遇敌池
$data['encounter_chance'] ?? 0.0, // 传入遇敌几率
$data['npc_ids'] ?? [] // 传入遇敌几率
);
} }
public function handleEvent(Event $event): void { public function handleEvent(Event $event): void {
@ -51,8 +63,51 @@ class MapSystem implements EventListenerInterface {
$initialTile = $this->stateManager->getCurrentTile(); $initialTile = $this->stateManager->getCurrentTile();
$this->dispatcher->dispatch(new Event('MapMoveEvent', ['newTileId' => $initialTile->id])); $this->dispatcher->dispatch(new Event('MapMoveEvent', ['newTileId' => $initialTile->id]));
break; break;
// ... 现有 case ...
case 'AttemptTalkEvent': // ⭐ 响应新的交谈尝试事件
$this->handleTalkAttempt();
break;
case 'InteractWithNpcEvent': // ⭐ 玩家已选择 NPC交给 InteractionSystem
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "➡️ 启动与 {$event->getPayload()['npcId']} 的交互流程。"
]));
} }
} }
/**
* 新增方法:处理交谈尝试,列出 NPC
*/
private function handleTalkAttempt(): void {
$currentTile = $this->stateManager->getCurrentTile();
$npcIds = $currentTile->npcIds;
if (empty($npcIds)) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈。"]));
return;
}
if (count($npcIds) === 1) {
// 只有一个 NPC直接开始交互
$npcId = $npcIds[0];
$this->dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => $npcId]));
return;
}
// 多个 NPC需要玩家选择
$options = [];
$i = 1;
foreach ($npcIds as $id) {
// 使用 NPC Repository 查找 NPC 名字
$npcData = $this->npcRepository->find($id); // 假设 MapSystem 已经注入了 NPCRepository
$options[] = "<fg=yellow>[{$i}]</> {$npcData['name']}";
$i++;
}
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "👥 你想和谁交谈?\n" . implode("\n", $options)
]));
// 🚨 这一步需要更复杂的输入处理来获取用户选择,我们暂时简化为直接转发到 InteractionSystem
$this->dispatcher->dispatch(new Event('AwaitingNpcSelection', ['options' => $npcIds]));
}
/** /**
* 处理玩家移动逻辑 * 处理玩家移动逻辑
@ -86,15 +141,19 @@ class MapSystem implements EventListenerInterface {
// 从 StateManager 获取当前 Tile // 从 StateManager 获取当前 Tile
$currentTile = $this->stateManager->getCurrentTile(); $currentTile = $this->stateManager->getCurrentTile();
$roll = rand(1, 100); $roll = rand(1, 100) / 100;
if ($currentTile->eventPoolId === 0) { // 城镇 // ⭐ 核心修正 1: 直接使用 Tile 中的几率
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "你在镇上休息了一会儿,没有发现任何危险。"])); if ($currentTile->encounterChance > 0 && $roll <= $currentTile->encounterChance) {
} elseif ($roll <= 60) { // ⭐ 核心修正 2: 使用 Tile 中的 Pool 获取随机敌人 ID
// 60% 几率遭遇战斗 $enemyId = $this->getRandomEnemyIdFromPool($currentTile->encounterPool);
$enemyId = $this->getEnemyIdForArea($currentTile->eventPoolId); // 使用当前 Tile 的 eventPoolId
$this->dispatcher->dispatch(new StartBattleEvent($enemyId)); if ($enemyId) {
} elseif ($roll <= 80) { $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❗️ 你遇到了麻烦..."]));
$this->dispatcher->dispatch(new Event('EncounterEnemyEvent', ['enemyId' => $enemyId]));
}
} elseif ($roll <= 0.8) {
// 20% 几率发现宝箱/物品 // 20% 几率发现宝箱/物品
$this->dispatcher->dispatch(new Event('LootFoundEvent', ['lootId' => 5])); $this->dispatcher->dispatch(new Event('LootFoundEvent', ['lootId' => 5]));
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "🎁 你发现了一个宝箱!"])); $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "🎁 你发现了一个宝箱!"]));
@ -104,12 +163,21 @@ class MapSystem implements EventListenerInterface {
} }
} }
// 简化的敌人 ID 获取 (应该从配置中获取) private function getRandomEnemyIdFromPool(?array $pool): ?string {
private function getEnemyIdForArea(int $poolId): int { if (!$pool) {
return match ($poolId) { return null;
1 => rand(1, 2), // 新手区敌人 ID 1 或 2 }
2 => 3, // 森林敌人 ID 3
default => 1, $totalWeight = array_sum(array_column($pool, 'weight'));
}; $randValue = mt_rand(1, $totalWeight);
$currentWeight = 0;
foreach ($pool as $item) {
$currentWeight += $item['weight'];
if ($randValue <= $currentWeight) {
return $item['enemyId'];
}
}
return null; // 理论上不会到达这里
} }
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Game\System; namespace Game\System;
use Game\Database\QuestRepository;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
@ -14,30 +15,23 @@ class QuestService implements EventListenerInterface {
private EventDispatcher $dispatcher; private EventDispatcher $dispatcher;
private StateManager $stateManager; private StateManager $stateManager;
private array $questData; // 存储所有任务配置 private QuestRepository $questRepository; // ⭐ 新增属性
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager) { // ⭐ 修正构造函数:注入 QuestRepository
private $questData = [];
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, QuestRepository $questRepository) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->stateManager = $stateManager; $this->stateManager = $stateManager;
$this->loadQuestData(); $this->questRepository = $questRepository; // ⭐ 赋值
} }
private function loadQuestData(): void {
// 模拟从配置加载任务数据
$this->questData = [
'RATS_1' => [
'name' => '地窖里的老鼠',
'description' => '为老村长清除地窖里 5 只弱小的哥布林。',
'type' => 'kill',
'target' => ['targetId' => 1, 'count' => 5], // 目标敌人 ID 1
'rewards' => ['xp' => 100, 'itemId' => 1]
],
// ... 其他任务 ...
];
}
public function handleEvent(Event $event): void { public function handleEvent(Event $event): void {
switch ($event->getType()) { switch ($event->getType()) {
case 'GameStartEvent':
$this->initializeQuests(); // 游戏开始时检查是否有初始任务
break;
case 'QuestCheckEvent': // 响应 InteractionSystem 的任务请求 case 'QuestCheckEvent': // 响应 InteractionSystem 的任务请求
$this->handleQuestCheck($event->getPayload()['npcId']); $this->handleQuestCheck($event->getPayload()['npcId']);
break; break;
@ -146,9 +140,49 @@ class QuestService implements EventListenerInterface {
* 模拟:根据 NPC ID 获取对应的任务 ID * 模拟:根据 NPC ID 获取对应的任务 ID
*/ */
private function getQuestIdForNpc(string $npcId): ?string { private function getQuestIdForNpc(string $npcId): ?string {
return match ($npcId) { return $this->questRepository->find($npcId);
'VILLAGER_1' => 'RATS_1', }
default => null,
}; /**
* 开始一个任务
*/
public function startQuest(string $questId): void {
$player = $this->stateManager->getPlayer();
// ⭐ 使用 Repository 获取数据
$questData = $this->questRepository->find($questId);
if (!$questData) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 错误任务ID '{$questId}' 不存在。"]));
return;
}
// 实例化 Quest 模型 (假设 Quest 模型已适配 JSON 键名)
$quest = new Quest(
$questData['id'],
$questData['name'],
$questData['description'],
$questData['type'],
$questData['target'],
$questData['rewards'],
$questData['next_quest_id'] ?? null
// ... 其它必要的 Quest 构造参数
);
$player->addActiveQuest($quest);
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "📝 接受任务: <fg=yellow>{$quest->getName()}</> - {$quest->getDescription()}"
]));
}
/**
* 游戏开始时尝试加载初始任务 (或通过 NPC 交互触发)
*/
public function initializeQuests(): void {
$player = $this->stateManager->getPlayer();
if (empty($player->getActiveQuests()) && empty($player->getCompletedQuests())) {
$startingQuestId = $this->questRepository->getStartingQuestId();
if ($startingQuestId) {
$this->startQuest($startingQuestId);
}
}
} }
} }

View File

@ -5,6 +5,7 @@ use Game\Model\Item;
use Game\Model\Player; use Game\Model\Player;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
use Game\Model\Quest;
/** /**
* SaveLoadService: 负责将玩家状态持久化到磁盘,并在启动时加载。 * SaveLoadService: 负责将玩家状态持久化到磁盘,并在启动时加载。
@ -52,6 +53,11 @@ class SaveLoadService {
// 将 Item 对象转换为数组以安全序列化 (可选,但更清晰) // 将 Item 对象转换为数组以安全序列化 (可选,但更清晰)
$serializedInventory[] = $item->toArray(); $serializedInventory[] = $item->toArray();
} }
$activeQuests = [];
foreach ($player->getActiveQuests() as $quest) {
// 将 Item 对象转换为数组以安全序列化 (可选,但更清晰)
$activeQuests[] = $quest->toArray();
}
// 序列化 Player 对象(我们假设 Player 模型包含了所有属性的 public/getter // 序列化 Player 对象(我们假设 Player 模型包含了所有属性的 public/getter
$data = [ $data = [
'name' => $player->getName(), 'name' => $player->getName(),
@ -64,7 +70,7 @@ class SaveLoadService {
'xpToNextLevel' => $player->getXpToNextLevel(), 'xpToNextLevel' => $player->getXpToNextLevel(),
'gold' => $player->getGold(), 'gold' => $player->getGold(),
'inventory' => $serializedInventory, // 注意:复杂对象数组需要递归序列化/反序列化 'inventory' => $serializedInventory, // 注意:复杂对象数组需要递归序列化/反序列化
'activeQuests' => $player->getActiveQuests(), 'activeQuests' => $activeQuests,
'completedQuests' => $player->getCompletedQuests(), 'completedQuests' => $player->getCompletedQuests(),
// TODO: 添加地图位置等其他状态 // TODO: 添加地图位置等其他状态
]; ];
@ -121,10 +127,34 @@ class SaveLoadService {
); );
$restoredInventory[] = $item; $restoredInventory[] = $item;
} }
$player->setInventory($restoredInventory); // 现在赋值的是 Item 对象的数组
$player->setActiveQuests($data['activeQuests']); // ⭐ 恢复 Active Quests (修正逻辑)
$player->setCompletedQuests($data['completedQuests']); $restoredActiveQuests = [];
// ... (Repository 获取逻辑) ...
foreach ($data['activeQuests'] as $questId => $serializedQuestData) {
$questConfig = $serializedQuestData;
if ($questConfig) {
// ⭐ 核心修正:直接从配置中获取 target 数组
$targetArray = $questConfig['target'] ?? ['count' => 1];
// 重新实例化 Quest (使用配置数据)
$quest = new Quest(
$questConfig['id'], $questConfig['name'], $questConfig['description'],
$questConfig['type'], $targetArray, $questConfig['rewards']
);
// 恢复运行时进度 (currentCount)
if (isset($serializedQuestData['currentCount'])) {
$quest->setCurrentCount($serializedQuestData['currentCount']);
}
$restoredActiveQuests[$questId] = $quest;
}
}
$player->setActiveQuests($restoredActiveQuests);
$player->setInventory($restoredInventory); // 现在赋值的是 Item 对象的数组
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '📂 游戏存档已加载!'])); $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '📂 游戏存档已加载!']));
return $player; return $player;

View File

@ -83,7 +83,13 @@ class UIService implements EventListenerInterface {
$this->output->writeln("攻击力: <fg=yellow>{$player->getAttack()}</> | 防御力: <fg=blue>{$player->getDefense()}</>"); $this->output->writeln("攻击力: <fg=yellow>{$player->getAttack()}</> | 防御力: <fg=blue>{$player->getDefense()}</>");
// ⭐ 新增金币显示 // ⭐ 新增金币显示
$this->output->writeln("金币: <fg=yellow>{$player->getGold()}</> 💰"); $this->output->writeln("金币: <fg=yellow>{$player->getGold()}</> 💰");
// ⭐ 显示当前装备
$this->output->writeln("\n--- <fg=white>装备</> ---");
$equipment = $player->getEquipment();
foreach ($equipment as $slot => $item) {
$itemName = $item ? "<fg=cyan>{$item->name}</>" : '空';
$this->output->writeln(" {$slot}: {$itemName}");
}
// TODO: 未来显示任务和物品数量 // TODO: 未来显示任务和物品数量
$this->output->writeln("--------------------------"); $this->output->writeln("--------------------------");
} }