功能完善
This commit is contained in:
parent
025c1ba2f2
commit
ce5a101ebe
18
config/abilities.json
Normal file
18
config/abilities.json
Normal 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
42
config/enemies.json
Normal 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
38
config/items.json
Normal 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}
|
||||
}
|
||||
]
|
||||
|
|
@ -1,20 +1,21 @@
|
|||
{
|
||||
"TOWN_01": {
|
||||
"name": "新手村",
|
||||
"description": "安全的小镇,可以休息。",
|
||||
"connections": {"N": "FIELD_01"},
|
||||
"eventPoolId": 0
|
||||
},
|
||||
"FIELD_01": {
|
||||
"name": "新手田野",
|
||||
"description": "微风习习,有低级怪物出没。",
|
||||
"connections": {"S": "TOWN_01", "E": "FOREST_01"},
|
||||
"eventPoolId": 1
|
||||
"description": "安全的小镇。",
|
||||
"connections": {"N": "FOREST_01"},
|
||||
"encounter_pool": null,
|
||||
"encounter_chance": 0.0,
|
||||
"npc_ids": ["VILLAGER_1", "BLACKSMITH"]
|
||||
},
|
||||
"FOREST_01": {
|
||||
"name": "幽暗密林",
|
||||
"description": "树木茂密,光线昏暗,怪物更强。",
|
||||
"connections": {"W": "FIELD_01"},
|
||||
"eventPoolId": 2
|
||||
"name": "新手森林",
|
||||
"description": "有一些弱小的怪物。",
|
||||
"connections": {"S": "TOWN_01", "E": "FOREST_02"},
|
||||
"encounter_chance": 0.6,
|
||||
"encounter_pool": [
|
||||
{"enemyId": "GOBLIN", "weight": 70},
|
||||
{"enemyId": "WOLF", "weight": 30}
|
||||
],
|
||||
"npc_ids": []
|
||||
}
|
||||
}
|
||||
18
config/npcs.json
Normal file
18
config/npcs.json
Normal 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
36
config/quests.json
Normal 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
|
||||
}
|
||||
]
|
||||
|
|
@ -1,21 +1,61 @@
|
|||
{
|
||||
"name": "hant",
|
||||
"health": 100,
|
||||
"maxHealth": 100,
|
||||
"attack": 15,
|
||||
"defense": 5,
|
||||
"level": 1,
|
||||
"currentXp": 10,
|
||||
"xpToNextLevel": 100,
|
||||
"gold": 7,
|
||||
"health": 130,
|
||||
"maxHealth": 130,
|
||||
"attack": 31,
|
||||
"defense": 9,
|
||||
"level": 3,
|
||||
"currentXp": 25,
|
||||
"xpToNextLevel": 225,
|
||||
"gold": 321,
|
||||
"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,
|
||||
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||
"type": "potion",
|
||||
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||
"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": [],
|
||||
|
|
|
|||
|
|
@ -2,10 +2,17 @@
|
|||
namespace Game\Core;
|
||||
|
||||
// 导入所有依赖和系统服务
|
||||
use Game\Database\AbilityRepository;
|
||||
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\EventListenerInterface;
|
||||
use Game\System\AbilityService;
|
||||
use Game\System\EquipmentService;
|
||||
use Game\System\SaveLoadService;
|
||||
use Game\System\StateManager;
|
||||
use Game\System\UIService;
|
||||
|
|
@ -39,6 +46,11 @@ class ServiceContainer {
|
|||
private SaveLoadService $saveLoadService; // 新增属性
|
||||
// 存储所有已实例化的服务
|
||||
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) {
|
||||
$this->input = $input;
|
||||
|
|
@ -66,20 +78,32 @@ class ServiceContainer {
|
|||
*/
|
||||
public function registerServices(): EventDispatcher {
|
||||
$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);
|
||||
// 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(MapSystem::class, new MapSystem($this->eventDispatcher, $this->stateManager,$this->npcRepository));
|
||||
$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(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 接口)
|
||||
$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,
|
||||
new BattleService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
|
||||
|
|
@ -89,12 +113,12 @@ class ServiceContainer {
|
|||
);
|
||||
|
||||
// ⭐ 实例化 AbilityService
|
||||
$abilityService = new AbilityService($this->eventDispatcher, $this->stateManager);
|
||||
$abilityService = new AbilityService($this->eventDispatcher, $this->stateManager, $this->abilityRepository);
|
||||
$this->register(AbilityService::class, $abilityService);
|
||||
|
||||
// 4. 输入处理服务 (驱动主循环,必须最后注册,因为它依赖于所有 I/O 组件)
|
||||
$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 使用
|
||||
|
|
@ -124,4 +148,9 @@ class ServiceContainer {
|
|||
public function getStateManager(): 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; }
|
||||
}
|
||||
19
src/Database/AbilityRepository.php
Normal file
19
src/Database/AbilityRepository.php
Normal 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;
|
||||
}
|
||||
}
|
||||
20
src/Database/EnemyRepository.php
Normal file
20
src/Database/EnemyRepository.php
Normal 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;
|
||||
}
|
||||
}
|
||||
45
src/Database/ItemRepository.php
Normal file
45
src/Database/ItemRepository.php
Normal 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'] ?? []
|
||||
);
|
||||
}
|
||||
}
|
||||
24
src/Database/JsonFileLoader.php
Normal file
24
src/Database/JsonFileLoader.php
Normal 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;
|
||||
}
|
||||
}
|
||||
38
src/Database/NPCRepository.php
Normal file
38
src/Database/NPCRepository.php
Normal 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']
|
||||
);
|
||||
}
|
||||
}
|
||||
59
src/Database/QuestRepository.php
Normal file
59
src/Database/QuestRepository.php
Normal 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);
|
||||
}
|
||||
}
|
||||
14
src/Database/RepositoryInterface.php
Normal file
14
src/Database/RepositoryInterface.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace Game\Database;
|
||||
|
||||
interface RepositoryInterface {
|
||||
/**
|
||||
* 根据 ID 查找单个实体数据
|
||||
*/
|
||||
public function find(int|string $id): ?array;
|
||||
|
||||
/**
|
||||
* 查找所有实体数据
|
||||
*/
|
||||
public function findAll(): array;
|
||||
}
|
||||
|
|
@ -14,10 +14,10 @@ class Character {
|
|||
// ⭐ 新增:魔法值 (MP/Mana)
|
||||
protected int $mana;
|
||||
protected int $maxMana;
|
||||
protected array $equipment = [];
|
||||
|
||||
// ⭐ 新增 Getter/Setter for Mana
|
||||
public function getMana(): int { return $this->mana; }
|
||||
public function getMaxMana(): int { return $this->maxMana; }
|
||||
|
||||
public function spendMana(int $cost): bool {
|
||||
if ($this->mana >= $cost) {
|
||||
|
|
@ -45,14 +45,65 @@ class Character {
|
|||
$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 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 getStatModifier(string $statName): int {
|
||||
$totalModifier = 0;
|
||||
foreach ($this->equipment as $item) {
|
||||
if ($item && isset($item->statModifiers[$statName])) {
|
||||
$totalModifier += $item->statModifiers[$statName];
|
||||
}
|
||||
}
|
||||
return $totalModifier;
|
||||
}
|
||||
/**
|
||||
* 接收伤害,返回实际受到的伤害量
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,18 +5,24 @@ namespace Game\Model;
|
|||
class Enemy extends Character {
|
||||
public string $id;
|
||||
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) 的构造函数来初始化核心属性
|
||||
parent::__construct($name, $health, $attack, $defense);
|
||||
|
||||
$this->id = $id;
|
||||
$this->xpValue = $xpValue;
|
||||
$this->lootTable = $lootTable; // ⭐ 赋值
|
||||
}
|
||||
|
||||
// Enemy 特有的 Getter 方法
|
||||
public function getId(): string { return $this->id; }
|
||||
public function getXpValue(): int { return $this->xpValue; }
|
||||
// ⭐ 新增 Getter
|
||||
public function getLootTable(): array { return $this->lootTable; }
|
||||
|
||||
// 注意:takeDamage() 和 isAlive() 等方法都直接继承自 Character,无需重复实现!
|
||||
}
|
||||
|
|
@ -11,13 +11,24 @@ class Item {
|
|||
public string $description;
|
||||
public int $value; // 卖出价格
|
||||
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->name = $name;
|
||||
$this->type = $type;
|
||||
$this->description = $description;
|
||||
$this->value = $value;
|
||||
$this->effects = $effects; // 赋值
|
||||
$this->effects = $effects;
|
||||
|
||||
// ⭐ 初始化新属性
|
||||
$this->slot = $slot;
|
||||
$this->statModifiers = $statModifiers;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
|
|
@ -29,6 +40,8 @@ class Item {
|
|||
'description' => $this->description,
|
||||
'value' => $this->value,
|
||||
'effects' => $this->effects,
|
||||
'slot' => $this->slot,
|
||||
'statModifiers' => $this->statModifiers,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,23 @@
|
|||
<?php
|
||||
namespace Game\Model;
|
||||
|
||||
/**
|
||||
* MapTile: 单个地图区域的数据模型。
|
||||
*/
|
||||
class MapTile {
|
||||
public string $id;
|
||||
public string $name;
|
||||
public string $description;
|
||||
public array $connections; // 存储连接:['N' => 'FOREST_02', 'S' => 'TOWN_01']
|
||||
public int $eventPoolId; // 区域对应的事件池ID
|
||||
|
||||
public function __construct(string $id, string $name, string $description, array $connections, int $eventPoolId) {
|
||||
public array $connections;
|
||||
// ⭐ 修正/新增属性:遇敌配置
|
||||
public ?array $encounterPool; // null 或包含 {"enemyId": "ID", "weight": N} 的数组
|
||||
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->name = $name;
|
||||
$this->description = $description;
|
||||
$this->connections = $connections;
|
||||
$this->eventPoolId = $eventPoolId;
|
||||
// ⭐ 赋值
|
||||
$this->encounterPool = $encounterPool;
|
||||
$this->encounterChance = $encounterChance;
|
||||
$this->npcIds = $npcIds; // ⭐ 赋值
|
||||
}
|
||||
}
|
||||
|
|
@ -9,12 +9,55 @@ class Player extends Character {
|
|||
|
||||
// ⭐ 新增:已学习的技能列表
|
||||
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) {
|
||||
// 调用父类 (Character) 的构造函数来初始化核心属性
|
||||
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 {
|
||||
$this->abilities[$ability->id] = $ability;
|
||||
|
|
@ -38,14 +81,27 @@ class Player extends Character {
|
|||
$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];
|
||||
/**
|
||||
* @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 {
|
||||
return $this->activeQuests;
|
||||
}
|
||||
|
|
@ -114,9 +170,8 @@ class Player extends Character {
|
|||
}
|
||||
|
||||
// ⭐ 新增 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 实例化
|
||||
|
|
@ -125,7 +180,6 @@ class Player extends Character {
|
|||
}
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,17 +2,21 @@
|
|||
namespace Game\Model;
|
||||
|
||||
/**
|
||||
* Quest: 任务模型
|
||||
* Quest: 任务模型 - 包含配置数据和运行时进度
|
||||
*/
|
||||
class Quest {
|
||||
// --- 配置数据 (来自 JSON) ---
|
||||
public string $id;
|
||||
public string $name;
|
||||
public string $description;
|
||||
public string $type; // e.g., 'kill', 'fetch', 'talk'
|
||||
public array $target; // 目标要求,e.g., ['targetId' => 'GOBLIN_1', 'count' => 5]
|
||||
public array $rewards; // 奖励,e.g., ['xp' => 100, 'itemId' => 1]
|
||||
public string $type;
|
||||
public array $target; // e.g., ['entityId' => 'GOBLIN', 'count' => 1]
|
||||
public array $rewards;
|
||||
public bool $isRepeatable = false;
|
||||
|
||||
// ⭐ 新增:运行时状态,默认为 0
|
||||
protected int $currentCount = 0;
|
||||
|
||||
public function __construct(string $id, string $name, string $description, string $type, array $target, array $rewards) {
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
|
|
@ -21,4 +25,58 @@ class Quest {
|
|||
$this->target = $target;
|
||||
$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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Game\System;
|
||||
|
||||
use Game\Database\AbilityRepository;
|
||||
use Game\Event\Event;
|
||||
use Game\Event\EventListenerInterface;
|
||||
use Game\Event\EventDispatcher;
|
||||
|
|
@ -15,34 +16,34 @@ class AbilityService implements EventListenerInterface {
|
|||
|
||||
private EventDispatcher $dispatcher;
|
||||
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->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 {
|
||||
// 确保 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']));
|
||||
$initialAbilities = ['FIREBALL', 'HEAL']; // 仍然可以硬编码初始技能的ID
|
||||
|
||||
foreach ($initialAbilities as $id) {
|
||||
// ⭐ 使用 Repository 获取数据
|
||||
$data = $this->abilityRepository->find($id);
|
||||
if ($data) {
|
||||
$player->learnAbility(new Ability(
|
||||
$data['id'],
|
||||
$data['name'],
|
||||
$data['type'],
|
||||
$data['mana_cost'],
|
||||
$data['power'],
|
||||
$data['scaling']
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use Game\Event\Event;
|
|||
use Game\Event\EventListenerInterface;
|
||||
use Game\Event\EventDispatcher;
|
||||
use Game\Model\Enemy;
|
||||
use Game\Model\Player; // 即使是 Party,也依赖 Player
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
|
|
@ -54,8 +53,7 @@ class BattleService implements EventListenerInterface {
|
|||
switch ($event->getType()) {
|
||||
case 'StartBattleEvent':
|
||||
// 收到 MapSystem 触发的战斗开始事件
|
||||
$enemyId = $event->getPayload()['enemyId'];
|
||||
$this->startBattle($enemyId);
|
||||
$this->startBattle($event->getPayload()['enemy']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,17 +61,8 @@ class BattleService implements EventListenerInterface {
|
|||
/**
|
||||
* 1. 初始化战斗状态
|
||||
*/
|
||||
private function startBattle(int $enemyId): void {
|
||||
// ... 初始化敌人逻辑 (继承自 Character) ...
|
||||
$enemyData = $this->loadEnemyData($enemyId);
|
||||
$this->currentEnemy = new Enemy(
|
||||
(string)$enemyId,
|
||||
$enemyData['name'],
|
||||
$enemyData['health'],
|
||||
$enemyData['attack'],
|
||||
$enemyData['defense'],
|
||||
$enemyData['xp']
|
||||
);
|
||||
private function startBattle(Enemy $enemy): void {
|
||||
$this->currentEnemy = $enemy;
|
||||
$this->inBattle = true;
|
||||
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||
|
|
|
|||
|
|
@ -58,19 +58,20 @@ class CharacterService implements EventListenerInterface {
|
|||
* 执行升级操作
|
||||
*/
|
||||
private function levelUp(Player $player): void {
|
||||
|
||||
|
||||
// 1. 扣除经验,提升等级
|
||||
$player->subtractXpToNextLevel(); // 假设我们在 Player 模型中添加此方法
|
||||
$player->incrementLevel(); // 假设我们在 Player 模型中添加此方法
|
||||
$player->subtractXpToNextLevel();
|
||||
$player->incrementLevel();
|
||||
|
||||
// 2. 提升属性 (简化版:每次升级增加固定属性)
|
||||
$player->increaseMaxHealth(10);
|
||||
$player->increaseAttack(2);
|
||||
$player->increaseDefense(1);
|
||||
// 2. 提升属性 (现在这些方法在 Character/Player 中都已实现)
|
||||
$player->increaseMaxHealth(15); // 增加生命
|
||||
$player->increaseAttack(3); // 增加攻击
|
||||
$player->increaseDefense(2); // 增加防御
|
||||
$player->restoreMana(10); // 恢复一些 Mana (或增加 maxMana)
|
||||
$player->fullRestore(); // 恢复所有生命和魔法
|
||||
|
||||
// 3. 升级后回满血
|
||||
$player->heal($player->getMaxHealth()); // 使用 Character 基类中的 heal()
|
||||
|
||||
// 4. 触发升级事件 (如果需要其他系统知道)
|
||||
// 3. 触发升级事件 (如果需要其他系统知道)
|
||||
$this->dispatcher->dispatch(new Event('LevelUpEvent', ['newLevel' => $player->getLevel()]));
|
||||
}
|
||||
}
|
||||
108
src/System/EquipmentService.php
Normal file
108
src/System/EquipmentService.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,10 +18,12 @@ class InputHandler {
|
|||
private InputInterface $input;
|
||||
private OutputInterface $output;
|
||||
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->input = $input;
|
||||
$this->stateManager = $stateManager;
|
||||
$this->output = $output;
|
||||
$this->helper = $helper;
|
||||
}
|
||||
|
|
@ -56,6 +58,9 @@ class InputHandler {
|
|||
// 状态显示请求,由于 StateManager 是状态持有者,EventDispatcher 会分发给 UIService
|
||||
$this->dispatcher->dispatch(new Event('ShowStatsRequest'));
|
||||
break;
|
||||
case 'T': // T 代表交谈 (Talk)
|
||||
$this->dispatcher->dispatch(new Event('AttemptTalkEvent'));
|
||||
break;
|
||||
case 'I': // ⭐ 新增交互指令
|
||||
// 模拟 MapSystem 发现了一个 NPC
|
||||
$this->dispatcher->dispatch(new Event('AttemptInteractEvent', ['npcId' => 'VILLAGER_1']));
|
||||
|
|
@ -88,10 +93,10 @@ class InputHandler {
|
|||
* 处理背包输入和子菜单
|
||||
*/
|
||||
private function handleInventoryInput(): void {
|
||||
// 通知 UI 打印背包内容
|
||||
// 1. 显示背包
|
||||
$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) ?? '');
|
||||
|
||||
if ($choice === 'X') {
|
||||
|
|
@ -101,10 +106,65 @@ class InputHandler {
|
|||
|
||||
if (is_numeric($choice)) {
|
||||
$itemIndex = (int)$choice;
|
||||
// 触发使用物品事件,ItemService 监听并处理
|
||||
$this->dispatcher->dispatch(new Event('UseItemEvent', ['itemIndex' => $itemIndex]));
|
||||
$this->handleItemAction($itemIndex); // ⭐ 移交给新方法处理
|
||||
} elseif ($choice === 'U') {
|
||||
$this->handleUnequipAction();
|
||||
} 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 {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Game\System;
|
||||
|
||||
use Game\Database\NPCRepository;
|
||||
use Game\Event\Event;
|
||||
use Game\Event\EventListenerInterface;
|
||||
use Game\Event\EventDispatcher;
|
||||
|
|
@ -17,15 +18,17 @@ class InteractionSystem implements EventListenerInterface {
|
|||
|
||||
private EventDispatcher $dispatcher;
|
||||
private StateManager $stateManager;
|
||||
|
||||
private NPCRepository $npcRepository; // ⭐ 新增属性
|
||||
// 输入依赖
|
||||
private InputInterface $input;
|
||||
private OutputInterface $output;
|
||||
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->stateManager = $stateManager;
|
||||
$this->npcRepository = $npcRepository; // ⭐ 赋值
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->helper = $helper;
|
||||
|
|
@ -38,6 +41,11 @@ class InteractionSystem implements EventListenerInterface {
|
|||
$npcId = $event->getPayload()['npcId'];
|
||||
$this->startInteraction($npcId);
|
||||
break;
|
||||
// ⭐ 修正事件名称
|
||||
case 'StartInteractionEvent':
|
||||
$npcId = $event->getPayload()['npcId'];
|
||||
$this->startInteraction($npcId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,9 +53,11 @@ class InteractionSystem implements EventListenerInterface {
|
|||
* 1. 初始化 NPC 交互
|
||||
*/
|
||||
private function startInteraction(string $npcId): void {
|
||||
$npc = $this->loadNPC($npcId);
|
||||
// ⭐ 修正:使用 Repository 加载 NPC
|
||||
$npc = $this->npcRepository->createNPC($npcId);
|
||||
|
||||
if (!$npc) {
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈。"]));
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈 ({$npcId} 不存在)。"]));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<?php
|
||||
namespace Game\System;
|
||||
|
||||
use Game\Database\EnemyRepository;
|
||||
use Game\Database\ItemRepository;
|
||||
use Game\Event\Event;
|
||||
use Game\Event\EventListenerInterface;
|
||||
use Game\Event\EventDispatcher;
|
||||
use Game\Model\Enemy;
|
||||
use Game\Model\Item;
|
||||
|
||||
/**
|
||||
|
|
@ -13,37 +16,83 @@ class LootService implements EventListenerInterface {
|
|||
|
||||
private EventDispatcher $dispatcher;
|
||||
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->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 {
|
||||
switch ($event->getType()) {
|
||||
case 'LootDropEvent':
|
||||
$enemyId = $event->getPayload()['enemyId'];
|
||||
// LootDropEvent 应该由 BattleService 在敌人死亡时触发
|
||||
$this->handleLootDrop($enemyId);
|
||||
break;
|
||||
case 'LootFoundEvent': // 响应 MapSystem 探索时发现的宝箱
|
||||
$lootId = $event->getPayload()['lootId'];
|
||||
$this->handleLootFound($lootId);
|
||||
break;
|
||||
// ... 现有事件 ...
|
||||
case 'ShopPurchaseEvent': // ⭐ 响应商店购买
|
||||
$itemId = $event->getPayload()['itemId'];
|
||||
$this->giveItemToPlayer($itemId);
|
||||
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 {
|
||||
|
||||
$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->gainGold($goldAmount);
|
||||
|
||||
|
|
@ -51,10 +100,19 @@ class LootService implements EventListenerInterface {
|
|||
'message' => "💰 获得了 <fg=yellow>{$goldAmount}</> 金币。"
|
||||
]));
|
||||
|
||||
// 简化:总是掉落物品 ID 1 (小药水)
|
||||
$roll = rand(1, 100);
|
||||
if ($roll <= 70) { // 70% 掉落几率
|
||||
$this->giveItemToPlayer(1);
|
||||
// --- 3. 掉落物品 (从配置的 loot_table 中获取) ---
|
||||
$lootTable = $enemyData['loot_table'] ?? [];
|
||||
|
||||
if (!empty($lootTable)) {
|
||||
$droppedItemId = $this->getRandomItemIdFromLootTable($lootTable);
|
||||
|
||||
if ($droppedItemId !== null) { // 成功掉落物品
|
||||
$this->giveItemToPlayer($droppedItemId);
|
||||
} else {
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||
'message' => "战利品很少,只找到了一些零钱。"
|
||||
]));
|
||||
}
|
||||
} else {
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||
'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 {
|
||||
// 假设 lootId 5 是一个宝箱,里面是物品 ID 2
|
||||
// ...
|
||||
if ($lootId === 5) {
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||
'message' => "🗝️ 你打开了宝箱!"
|
||||
|
|
@ -76,18 +161,16 @@ class LootService implements EventListenerInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* 核心逻辑:创建物品实例并添加到玩家背包
|
||||
* 核心逻辑:创建物品实例并添加到玩家背包 (保持不变)
|
||||
*/
|
||||
private function giveItemToPlayer(int $itemId): void {
|
||||
$itemData = $this->loadItemData($itemId);
|
||||
// ...
|
||||
$item = $this->itemRepository->createItem($itemId);
|
||||
|
||||
$item = new Item(
|
||||
$itemId,
|
||||
$itemData['name'],
|
||||
$itemData['type'],
|
||||
$itemData['description'],
|
||||
$itemData['value']
|
||||
);
|
||||
if (!$item) {
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 物品ID '{$itemId}' 不存在,无法获得。"]));
|
||||
return;
|
||||
}
|
||||
|
||||
$player = $this->stateManager->getPlayer();
|
||||
$player->addItem($item);
|
||||
|
|
@ -99,16 +182,4 @@ class LootService implements EventListenerInterface {
|
|||
// 触发 InventoryUpdateEvent (未来用于 UI 实时更新)
|
||||
$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' => []],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Game\System;
|
||||
|
||||
use Game\Database\NPCRepository;
|
||||
use Game\Event\Event;
|
||||
use Game\Event\EventListenerInterface;
|
||||
use Game\Event\EventDispatcher;
|
||||
|
|
@ -12,10 +13,12 @@ class MapSystem implements EventListenerInterface {
|
|||
private EventDispatcher $dispatcher;
|
||||
private StateManager $stateManager;
|
||||
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->stateManager = $stateManager;
|
||||
$this->npcRepository = $npcRepository;
|
||||
$this->loadMapData();
|
||||
|
||||
// 游戏启动时,设置初始区域状态到 StateManager
|
||||
|
|
@ -35,7 +38,16 @@ class MapSystem implements EventListenerInterface {
|
|||
throw new \Exception("MapTile ID '{$tileId}' not found in configuration.");
|
||||
}
|
||||
$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 {
|
||||
|
|
@ -51,8 +63,51 @@ class MapSystem implements EventListenerInterface {
|
|||
$initialTile = $this->stateManager->getCurrentTile();
|
||||
$this->dispatcher->dispatch(new Event('MapMoveEvent', ['newTileId' => $initialTile->id]));
|
||||
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
|
||||
$currentTile = $this->stateManager->getCurrentTile();
|
||||
|
||||
$roll = rand(1, 100);
|
||||
$roll = rand(1, 100) / 100;
|
||||
|
||||
if ($currentTile->eventPoolId === 0) { // 城镇
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "你在镇上休息了一会儿,没有发现任何危险。"]));
|
||||
} elseif ($roll <= 60) {
|
||||
// 60% 几率遭遇战斗
|
||||
$enemyId = $this->getEnemyIdForArea($currentTile->eventPoolId); // 使用当前 Tile 的 eventPoolId
|
||||
$this->dispatcher->dispatch(new StartBattleEvent($enemyId));
|
||||
} elseif ($roll <= 80) {
|
||||
// ⭐ 核心修正 1: 直接使用 Tile 中的几率
|
||||
if ($currentTile->encounterChance > 0 && $roll <= $currentTile->encounterChance) {
|
||||
// ⭐ 核心修正 2: 使用 Tile 中的 Pool 获取随机敌人 ID
|
||||
$enemyId = $this->getRandomEnemyIdFromPool($currentTile->encounterPool);
|
||||
|
||||
if ($enemyId) {
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❗️ 你遇到了麻烦..."]));
|
||||
$this->dispatcher->dispatch(new Event('EncounterEnemyEvent', ['enemyId' => $enemyId]));
|
||||
}
|
||||
|
||||
} elseif ($roll <= 0.8) {
|
||||
// 20% 几率发现宝箱/物品
|
||||
$this->dispatcher->dispatch(new Event('LootFoundEvent', ['lootId' => 5]));
|
||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "🎁 你发现了一个宝箱!"]));
|
||||
|
|
@ -104,12 +163,21 @@ class MapSystem implements EventListenerInterface {
|
|||
}
|
||||
}
|
||||
|
||||
// 简化的敌人 ID 获取 (应该从配置中获取)
|
||||
private function getEnemyIdForArea(int $poolId): int {
|
||||
return match ($poolId) {
|
||||
1 => rand(1, 2), // 新手区敌人 ID 1 或 2
|
||||
2 => 3, // 森林敌人 ID 3
|
||||
default => 1,
|
||||
};
|
||||
private function getRandomEnemyIdFromPool(?array $pool): ?string {
|
||||
if (!$pool) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$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; // 理论上不会到达这里
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Game\System;
|
||||
|
||||
use Game\Database\QuestRepository;
|
||||
use Game\Event\Event;
|
||||
use Game\Event\EventListenerInterface;
|
||||
use Game\Event\EventDispatcher;
|
||||
|
|
@ -14,30 +15,23 @@ class QuestService implements EventListenerInterface {
|
|||
|
||||
private EventDispatcher $dispatcher;
|
||||
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->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 {
|
||||
switch ($event->getType()) {
|
||||
case 'GameStartEvent':
|
||||
$this->initializeQuests(); // 游戏开始时检查是否有初始任务
|
||||
break;
|
||||
case 'QuestCheckEvent': // 响应 InteractionSystem 的任务请求
|
||||
$this->handleQuestCheck($event->getPayload()['npcId']);
|
||||
break;
|
||||
|
|
@ -146,9 +140,49 @@ class QuestService implements EventListenerInterface {
|
|||
* 模拟:根据 NPC ID 获取对应的任务 ID
|
||||
*/
|
||||
private function getQuestIdForNpc(string $npcId): ?string {
|
||||
return match ($npcId) {
|
||||
'VILLAGER_1' => 'RATS_1',
|
||||
default => null,
|
||||
};
|
||||
return $this->questRepository->find($npcId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始一个任务
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ use Game\Model\Item;
|
|||
use Game\Model\Player;
|
||||
use Game\Event\Event;
|
||||
use Game\Event\EventDispatcher;
|
||||
use Game\Model\Quest;
|
||||
|
||||
/**
|
||||
* SaveLoadService: 负责将玩家状态持久化到磁盘,并在启动时加载。
|
||||
|
|
@ -52,6 +53,11 @@ class SaveLoadService {
|
|||
// 将 Item 对象转换为数组以安全序列化 (可选,但更清晰)
|
||||
$serializedInventory[] = $item->toArray();
|
||||
}
|
||||
$activeQuests = [];
|
||||
foreach ($player->getActiveQuests() as $quest) {
|
||||
// 将 Item 对象转换为数组以安全序列化 (可选,但更清晰)
|
||||
$activeQuests[] = $quest->toArray();
|
||||
}
|
||||
// 序列化 Player 对象(我们假设 Player 模型包含了所有属性的 public/getter)
|
||||
$data = [
|
||||
'name' => $player->getName(),
|
||||
|
|
@ -64,7 +70,7 @@ class SaveLoadService {
|
|||
'xpToNextLevel' => $player->getXpToNextLevel(),
|
||||
'gold' => $player->getGold(),
|
||||
'inventory' => $serializedInventory, // 注意:复杂对象数组需要递归序列化/反序列化
|
||||
'activeQuests' => $player->getActiveQuests(),
|
||||
'activeQuests' => $activeQuests,
|
||||
'completedQuests' => $player->getCompletedQuests(),
|
||||
// TODO: 添加地图位置等其他状态
|
||||
];
|
||||
|
|
@ -121,10 +127,34 @@ class SaveLoadService {
|
|||
);
|
||||
$restoredInventory[] = $item;
|
||||
}
|
||||
$player->setInventory($restoredInventory); // 现在赋值的是 Item 对象的数组
|
||||
|
||||
$player->setActiveQuests($data['activeQuests']);
|
||||
$player->setCompletedQuests($data['completedQuests']);
|
||||
// ⭐ 恢复 Active Quests (修正逻辑)
|
||||
$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' => '📂 游戏存档已加载!']));
|
||||
return $player;
|
||||
|
|
|
|||
|
|
@ -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->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: 未来显示任务和物品数量
|
||||
$this->output->writeln("--------------------------");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user