任务
This commit is contained in:
parent
6f6125e640
commit
09383708de
|
|
@ -13,6 +13,12 @@
|
||||||
"greeting": "欢迎来到我的铁匠铺。",
|
"greeting": "欢迎来到我的铁匠铺。",
|
||||||
"quest_response": "你有空吗?我的铁矿用完了。",
|
"quest_response": "你有空吗?我的铁矿用完了。",
|
||||||
"shop_response": "看一看你需要什么工具和武器。"
|
"shop_response": "看一看你需要什么工具和武器。"
|
||||||
|
},
|
||||||
|
"hasShop": true,
|
||||||
|
"shopInventory": {
|
||||||
|
"2": {"price": 50},
|
||||||
|
"4": {"price": 150},
|
||||||
|
"5": {"price": 200}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"triggerValue": "VILLAGER_1",
|
"triggerValue": "VILLAGER_1",
|
||||||
"target": {
|
"target": {
|
||||||
"entityId": "GOBLIN",
|
"entityId": "GOBLIN",
|
||||||
"count": 1
|
"count": 10
|
||||||
},
|
},
|
||||||
"required_level": 1,
|
"required_level": 1,
|
||||||
"rewards": {
|
"rewards": {
|
||||||
|
|
|
||||||
192
save/player.json
192
save/player.json
|
|
@ -1,23 +1,133 @@
|
||||||
{
|
{
|
||||||
"player": {
|
"player": {
|
||||||
"name": "Hant",
|
"name": "AutoSaveTest",
|
||||||
"health": 91,
|
"health": 160,
|
||||||
"maxHealth": 100,
|
"maxHealth": 160,
|
||||||
"base_attack": 15,
|
"base_attack": 22,
|
||||||
"base_defense": 5,
|
"base_defense": 13,
|
||||||
"level": 1,
|
"level": 5,
|
||||||
"currentXp": 20,
|
"currentXp": 175,
|
||||||
"gold": 12,
|
"gold": 241,
|
||||||
"inventory": [
|
"inventory": [
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 1,
|
||||||
"name": "\u7834\u65e7\u7684\u77ed\u5251",
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
"type": "weapon",
|
"type": "potion",
|
||||||
"description": "\u653b\u51fb\u529b\u5fae\u5f31\u3002",
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
"value": 50,
|
"value": 10,
|
||||||
"effects": [],
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
"stats": [],
|
"stats": [],
|
||||||
"slot": "weapon"
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "\u5c0f\u578b\u6cbb\u7597\u836f\u6c34",
|
||||||
|
"type": "potion",
|
||||||
|
"description": "\u6062\u590d\u5c11\u91cf\u751f\u547d\u3002",
|
||||||
|
"value": 10,
|
||||||
|
"effects": {
|
||||||
|
"heal": 20
|
||||||
|
},
|
||||||
|
"stats": [],
|
||||||
|
"slot": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
|
@ -31,36 +141,34 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"equipment": {
|
"equipment": {
|
||||||
"weapon": null,
|
"weapon": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "\u7834\u65e7\u7684\u77ed\u5251",
|
||||||
|
"type": "weapon",
|
||||||
|
"description": "\u653b\u51fb\u529b\u5fae\u5f31\u3002",
|
||||||
|
"value": 50,
|
||||||
|
"effects": [],
|
||||||
|
"stats": [],
|
||||||
|
"slot": "weapon"
|
||||||
|
},
|
||||||
"armor": null,
|
"armor": null,
|
||||||
"helmet": null
|
"helmet": {
|
||||||
},
|
"id": 4,
|
||||||
"activeQuests": [
|
"name": "\u5e03\u7532\u5934\u76d4",
|
||||||
{
|
"type": "armor",
|
||||||
"config": {
|
"description": "\u63d0\u4f9b\u5c11\u91cf\u9632\u5fa1\u3002",
|
||||||
"id": "KILL_GOBLIN",
|
"value": 30,
|
||||||
"name": "\u65b0\u624b\u6311\u6218\uff1a\u51fb\u8d25\u54e5\u5e03\u6797",
|
"effects": [],
|
||||||
"description": "\u524d\u5f80\u9644\u8fd1\u7684\u68ee\u6797\uff0c\u51fb\u8d25\u4e00\u53ea\u54e5\u5e03\u6797\u6765\u8bc1\u660e\u4f60\u7684\u52c7\u6c14\u3002",
|
"stats": [],
|
||||||
"type": "kill",
|
"slot": "helmet"
|
||||||
"target": {
|
|
||||||
"entityId": "GOBLIN",
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"rewards": {
|
|
||||||
"xp": 50,
|
|
||||||
"gold": 20,
|
|
||||||
"item_id": 1,
|
|
||||||
"item_quantity": 1
|
|
||||||
},
|
|
||||||
"isRepeatable": false,
|
|
||||||
"currentCount": 0
|
|
||||||
},
|
|
||||||
"currentCount": 0
|
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"completedQuests": []
|
"activeQuests": [],
|
||||||
|
"completedQuests": [
|
||||||
|
"KILL_GOBLIN"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"world": {
|
"world": {
|
||||||
"currentTileId": "FOREST_01"
|
"currentTileId": "TOWN_01"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ class ServiceContainer {
|
||||||
$this->itemRepository = new ItemRepository($jsonLoader);
|
$this->itemRepository = new ItemRepository($jsonLoader);
|
||||||
$this->enemyRepository = new EnemyRepository($jsonLoader);
|
$this->enemyRepository = new EnemyRepository($jsonLoader);
|
||||||
$this->abilityRepository = new AbilityRepository($jsonLoader);
|
$this->abilityRepository = new AbilityRepository($jsonLoader);
|
||||||
$this->questionRepository = new QuestRepository($jsonLoader);
|
$this->questRepository = new QuestRepository($jsonLoader); // ⭐ 修正变量名
|
||||||
$this->npcRepository = new NPCRepository($jsonLoader);
|
$this->npcRepository = new NPCRepository($jsonLoader);
|
||||||
$this->mapRepository = new MapRepository($jsonLoader);
|
$this->mapRepository = new MapRepository($jsonLoader);
|
||||||
}
|
}
|
||||||
|
|
@ -91,15 +91,17 @@ class ServiceContainer {
|
||||||
// 状态管理器
|
// 状态管理器
|
||||||
$this->stateManager = new StateManager($this->dbManager->getConnection(),$this->mapRepository);
|
$this->stateManager = new StateManager($this->dbManager->getConnection(),$this->mapRepository);
|
||||||
$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, $this->questRepository));
|
||||||
|
|
||||||
// 2. 核心逻辑服务 (依赖 Dispatcher, StateManager)
|
// 2. 核心逻辑服务 (依赖 Dispatcher, StateManager)
|
||||||
$this->register(MapSystem::class, new MapSystem($this->eventDispatcher, $this->stateManager,$this->npcRepository,$this->mapRepository));
|
$this->register(MapSystem::class, new MapSystem($this->eventDispatcher, $this->stateManager,$this->npcRepository,$this->mapRepository));
|
||||||
$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->itemRepository, $this->enemyRepository));
|
$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->questionRepository));
|
$this->register(EquipmentService::class, new EquipmentService($this->eventDispatcher, $this->stateManager)); // ⭐ 注册装备服务
|
||||||
|
$this->register(QuestService::class, new QuestService($this->eventDispatcher, $this->stateManager, $this->questRepository));
|
||||||
// ⭐ 实例化 AbilityService
|
// ⭐ 实例化 AbilityService
|
||||||
$abilityService = new AbilityService($this->eventDispatcher, $this->stateManager, $this->abilityRepository);
|
$abilityService = new AbilityService($this->eventDispatcher, $this->stateManager, $this->abilityRepository);
|
||||||
$this->register(AbilityService::class, $abilityService);
|
$this->register(AbilityService::class, $abilityService);
|
||||||
|
|
@ -117,7 +119,7 @@ class ServiceContainer {
|
||||||
$this->eventDispatcher,
|
$this->eventDispatcher,
|
||||||
$this->stateManager,
|
$this->stateManager,
|
||||||
$this->npcRepository,
|
$this->npcRepository,
|
||||||
$this->questionRepository, // ⭐ 新增注入
|
$this->questRepository, // ⭐ 修正变量名
|
||||||
$dialogueService, // ⭐ 新增注入
|
$dialogueService, // ⭐ 新增注入
|
||||||
$this->input,
|
$this->input,
|
||||||
$this->output,
|
$this->output,
|
||||||
|
|
@ -128,10 +130,11 @@ class ServiceContainer {
|
||||||
new BattleService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
|
new BattleService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
|
||||||
);
|
);
|
||||||
$this->register(ShopService::class,
|
$this->register(ShopService::class,
|
||||||
new ShopService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper)
|
new ShopService($this->eventDispatcher, $this->stateManager, $this->input, $this->output, $this->questionHelper, $this->itemRepository)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ⭐ 注册SaveLoadService为事件监听器,实现自动保存
|
||||||
|
$this->register(SaveLoadService::class, $this->saveLoadService);
|
||||||
|
|
||||||
// 4. 输入处理服务 (驱动主循环,必须最后注册,因为它依赖于所有 I/O 组件)
|
// 4. 输入处理服务 (驱动主循环,必须最后注册,因为它依赖于所有 I/O 组件)
|
||||||
$this->register(InputHandler::class,
|
$this->register(InputHandler::class,
|
||||||
|
|
@ -170,4 +173,5 @@ class ServiceContainer {
|
||||||
public function getItemRepository(): ItemRepository { return $this->itemRepository; }
|
public function getItemRepository(): ItemRepository { return $this->itemRepository; }
|
||||||
public function getEnemyRepository(): EnemyRepository { return $this->enemyRepository; }
|
public function getEnemyRepository(): EnemyRepository { return $this->enemyRepository; }
|
||||||
public function getAbilityRepository(): AbilityRepository { return $this->abilityRepository; }
|
public function getAbilityRepository(): AbilityRepository { return $this->abilityRepository; }
|
||||||
|
public function getNpcRepository(): NPCRepository { return $this->npcRepository; } // ⭐ 新增
|
||||||
}
|
}
|
||||||
|
|
@ -28,11 +28,13 @@ class NPCRepository implements RepositoryInterface {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 假设 NPC 模型的构造函数是 public function __construct(string $id, string $name, array $dialogue)
|
// ⭐ 支持商店数据
|
||||||
return new NPC(
|
return new NPC(
|
||||||
$id,
|
$id,
|
||||||
$data['name'],
|
$data['name'],
|
||||||
$data['dialogue']
|
$data['dialogue'],
|
||||||
|
$data['hasShop'] ?? false,
|
||||||
|
$data['shopInventory'] ?? null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,9 @@ class MapTile {
|
||||||
public ?array $encounterPool; // null 或包含 {"enemyId": "ID", "weight": N} 的数组
|
public ?array $encounterPool; // null 或包含 {"enemyId": "ID", "weight": N} 的数组
|
||||||
public float $encounterChance; // 遇敌几率 (0.0 到 1.0)
|
public float $encounterChance; // 遇敌几率 (0.0 到 1.0)
|
||||||
public array $npcIds;
|
public array $npcIds;
|
||||||
public function __construct(string $id, string $name, string $description, array $connections, ?array $encounterPool, float $encounterChance,array $npcIds = []) {
|
public array $lootIds; // ⭐ 新增:宝箱/掉落物ID列表
|
||||||
|
|
||||||
|
public function __construct(string $id, string $name, string $description, array $connections, ?array $encounterPool, float $encounterChance, array $npcIds = [], array $lootIds = []) {
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->description = $description;
|
$this->description = $description;
|
||||||
|
|
@ -19,5 +21,6 @@ class MapTile {
|
||||||
$this->encounterPool = $encounterPool;
|
$this->encounterPool = $encounterPool;
|
||||||
$this->encounterChance = $encounterChance;
|
$this->encounterChance = $encounterChance;
|
||||||
$this->npcIds = $npcIds; // ⭐ 赋值
|
$this->npcIds = $npcIds; // ⭐ 赋值
|
||||||
|
$this->lootIds = $lootIds; // ⭐ 赋值
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,11 +8,15 @@ class NPC {
|
||||||
public string $id;
|
public string $id;
|
||||||
public string $name;
|
public string $name;
|
||||||
public array $dialogue; // 存储对话行或对话树结构
|
public array $dialogue; // 存储对话行或对话树结构
|
||||||
|
public bool $hasShop; // ⭐ 是否有商店
|
||||||
|
public ?array $shopInventory; // ⭐ 商店库存
|
||||||
|
|
||||||
public function __construct(string $id, string $name, array $dialogue) {
|
public function __construct(string $id, string $name, array $dialogue, bool $hasShop = false, ?array $shopInventory = null) {
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->dialogue = $dialogue;
|
$this->dialogue = $dialogue;
|
||||||
|
$this->hasShop = $hasShop;
|
||||||
|
$this->shopInventory = $shopInventory ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(): string {
|
public function getName(): string {
|
||||||
|
|
|
||||||
|
|
@ -110,14 +110,8 @@ class Player extends Character {
|
||||||
public function updateQuestProgress(string $questId, int $count = 1): void {
|
public function updateQuestProgress(string $questId, int $count = 1): void {
|
||||||
if (isset($this->activeQuests[$questId])) {
|
if (isset($this->activeQuests[$questId])) {
|
||||||
$progress = &$this->activeQuests[$questId]; // 使用引用
|
$progress = &$this->activeQuests[$questId]; // 使用引用
|
||||||
if (!$progress['isCompleted']) {
|
if (!$progress->isCompleted()) {
|
||||||
$progress['currentCount'] += $count;
|
$progress->incrementProgress($count);
|
||||||
if ($progress['currentCount'] >= $progress['targetCount']) {
|
|
||||||
$progress['currentCount'] = $progress['targetCount'];
|
|
||||||
$progress['isCompleted'] = true;
|
|
||||||
// 触发 QuestCompletedEventRequest
|
|
||||||
// 注意:实际的奖励和标记完成应在 QuestService 确认后进行
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,13 @@ class Quest {
|
||||||
$this->currentCount = min($targetCount, $this->currentCount + $amount);
|
$this->currentCount = min($targetCount, $this->currentCount + $amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⭐ 简便方法:增加进度(兼容旧API)
|
||||||
|
*/
|
||||||
|
public function incrementProgress(int $amount = 1): void {
|
||||||
|
$this->incrementCurrentCount($amount);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查任务是否完成
|
* 检查任务是否完成
|
||||||
*/
|
*/
|
||||||
|
|
@ -61,6 +68,11 @@ class Quest {
|
||||||
public function getType(): string { return $this->type; }
|
public function getType(): string { return $this->type; }
|
||||||
public function getTarget(): array { return $this->target; }
|
public function getTarget(): array { return $this->target; }
|
||||||
public function getRewards(): array { return $this->rewards; }
|
public function getRewards(): array { return $this->rewards; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⭐ 获取任务标题(兼容旧API)
|
||||||
|
*/
|
||||||
|
public function getTitle(): string { return $this->name; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前进度 (用于存档)
|
* 获取当前进度 (用于存档)
|
||||||
|
|
|
||||||
|
|
@ -248,24 +248,13 @@ class BattleService implements EventListenerInterface {
|
||||||
* 结束战斗状态
|
* 结束战斗状态
|
||||||
*/
|
*/
|
||||||
private function endBattle(bool $isWin): void {
|
private function endBattle(bool $isWin): void {
|
||||||
|
$enemy = $this->currentEnemy;
|
||||||
$this->inBattle = false;
|
$this->inBattle = false;
|
||||||
$this->currentEnemy = null;
|
$this->currentEnemy = null;
|
||||||
|
|
||||||
// 当战斗结束
|
// 当战斗结束
|
||||||
$this->stateManager->setMode(StateManager::MODE_MAP);
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
$this->dispatcher->dispatch(new Event('ShowMenuEvent')); // 切换回探索菜单
|
|
||||||
}
|
// ⭐ 触发战斗结束事件,用于自动保存
|
||||||
|
$this->dispatcher->dispatch(new Event('BattleEndEvent', ['isWin' => $isWin,'enemyId' => $enemy->getId()]));
|
||||||
/**
|
|
||||||
* 模拟从配置中加载敌人数据
|
|
||||||
*/
|
|
||||||
private function loadEnemyData(int $id): array {
|
|
||||||
// 在实际项目中,这应该从数据库或 JSON 文件加载
|
|
||||||
return match ($id) {
|
|
||||||
1 => ['name' => '弱小的哥布林', 'health' => 20, 'attack' => 5, 'defense' => 1, 'xp' => 10],
|
|
||||||
2 => ['name' => '愤怒的野猪', 'health' => 35, 'attack' => 8, 'defense' => 3, 'xp' => 25],
|
|
||||||
3 => ['name' => '森林狼', 'health' => 40, 'attack' => 10, 'defense' => 5, 'xp' => 40],
|
|
||||||
default => ['name' => '未知生物', 'health' => 1, 'attack' => 1, 'defense' => 0, 'xp' => 1],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,9 +140,6 @@ class DialogueService implements EventListenerInterface {
|
||||||
|
|
||||||
// 恢复地图模式 (或者之前的模式,稍微简化处理)
|
// 恢复地图模式 (或者之前的模式,稍微简化处理)
|
||||||
$this->stateManager->setMode(StateManager::MODE_MAP);
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
|
||||||
// 触发菜单显示,让玩家知道回到了地图
|
|
||||||
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleAction(string $actionString): void {
|
private function handleAction(string $actionString): void {
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ class InputHandler {
|
||||||
case 'I': // 状态 (Status)
|
case 'I': // 状态 (Status)
|
||||||
$this->dispatcher->dispatch(new Event('ShowStatsRequest'));
|
$this->dispatcher->dispatch(new Event('ShowStatsRequest'));
|
||||||
break;
|
break;
|
||||||
|
case 'J': // 任务 (Journal/Quest)
|
||||||
|
$this->dispatcher->dispatch(new Event('ShowQuestListRequest'));
|
||||||
|
break;
|
||||||
case 'L': // 保存 (Save/Load)
|
case 'L': // 保存 (Save/Load)
|
||||||
if ($this->saveLoadService) {
|
if ($this->saveLoadService) {
|
||||||
$this->saveLoadService->saveGame();
|
$this->saveLoadService->saveGame();
|
||||||
|
|
@ -137,19 +140,121 @@ class InputHandler {
|
||||||
$this->stateManager->setMode(StateManager::MODE_MAP);
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// ... 处理物品使用逻辑 ...
|
|
||||||
|
// ⭐ 处理物品使用逻辑
|
||||||
|
if (is_numeric($input)) {
|
||||||
|
$itemIndex = (int)$input;
|
||||||
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$inventory = $player->getInventory();
|
||||||
|
|
||||||
|
if (!isset($inventory[$itemIndex])) {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 编号为 {$itemIndex} 的物品不存在。"]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = $inventory[$itemIndex];
|
||||||
|
|
||||||
|
// 根据物品类型决定操作
|
||||||
|
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' => "❌ 该物品无法被使用或装备。"]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 请输入有效的物品编号。']));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 💬 对话逻辑:选项分支 */
|
/** 💬 对话逻辑:选项分支 */
|
||||||
private function handleDialogueInput(): bool {
|
private function handleDialogueInput(): bool {
|
||||||
$input = $this->ask("对话选择 (数字)> ");
|
// ⭐ 检查是否有待处理的商店NPC
|
||||||
|
$pendingShopNpc = $this->stateManager->getPendingShopNpc();
|
||||||
if (strtoupper($input) === 'X') {
|
if ($pendingShopNpc) {
|
||||||
$this->stateManager->setMode(StateManager::MODE_MAP);
|
$input = $this->ask("选择 (S/X)> ");
|
||||||
|
|
||||||
|
if ($input === 'S') {
|
||||||
|
// 打开商店
|
||||||
|
$this->stateManager->clearPendingShopNpc();
|
||||||
|
$this->dispatcher->dispatch(new Event('OpenShopEvent', [
|
||||||
|
'npc' => $pendingShopNpc
|
||||||
|
]));
|
||||||
|
} elseif ($input === 'X') {
|
||||||
|
// 离开
|
||||||
|
$this->stateManager->clearPendingShopNpc();
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '再见!']));
|
||||||
|
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||||||
|
} else {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '请输入 S 或 X。']));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$this->dispatcher->dispatch(new Event('DialogueChoice', ['choice' => $input]));
|
|
||||||
|
// ⭐ 检查是否有待交付的任务
|
||||||
|
$pendingQuestTurnIn = $this->stateManager->getPendingQuestTurnIn();
|
||||||
|
if ($pendingQuestTurnIn) {
|
||||||
|
$input = $this->ask("选择 (Y/N)> ");
|
||||||
|
|
||||||
|
if ($input === 'Y') {
|
||||||
|
// 确认交任务
|
||||||
|
$this->dispatcher->dispatch(new Event('QuestTurnInConfirm', ['questId' => $pendingQuestTurnIn]));
|
||||||
|
$this->stateManager->clearPendingQuestTurnIn();
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||||||
|
} elseif ($input === 'N') {
|
||||||
|
// 取消交任务
|
||||||
|
$this->stateManager->clearPendingQuestTurnIn();
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '已取消任务交付。']));
|
||||||
|
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||||||
|
} else {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '请输入 Y 或 N。']));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ 检查是否有待选择的NPC列表
|
||||||
|
$pendingNpcs = $this->stateManager->getPendingNpcSelection();
|
||||||
|
|
||||||
|
if (!empty($pendingNpcs)) {
|
||||||
|
// ⭐ 处理NPC选择
|
||||||
|
$input = $this->ask("选择NPC (数字或 X 退出)> ");
|
||||||
|
|
||||||
|
if (strtoupper($input) === 'X') {
|
||||||
|
$this->stateManager->clearPendingNpcSelection();
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($input)) {
|
||||||
|
$index = (int)$input;
|
||||||
|
if (isset($pendingNpcs[$index])) {
|
||||||
|
$npcId = $pendingNpcs[$index];
|
||||||
|
$this->stateManager->clearPendingNpcSelection();
|
||||||
|
// ⭐ 触发与选中NPC的交互
|
||||||
|
$this->dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => $npcId]));
|
||||||
|
} else {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的选择。']));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '请输入数字选择NPC。']));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ⭐ 处理任务对话选项
|
||||||
|
$input = $this->ask("对话选择 (X 离开)> ");
|
||||||
|
|
||||||
|
if (strtoupper($input) === 'X') {
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$this->dispatcher->dispatch(new Event('DialogueChoice', ['choice' => $input]));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ class InteractionSystem implements EventListenerInterface {
|
||||||
public function handleEvent(Event $event): void {
|
public function handleEvent(Event $event): void {
|
||||||
switch ($event->getType()) {
|
switch ($event->getType()) {
|
||||||
case 'AttemptInteractEvent':
|
case 'AttemptInteractEvent':
|
||||||
// 假设 MapSystem 会提供当前 Tile 上的 NPC ID
|
|
||||||
$npcId = $event->getPayload()['npcId'];
|
$npcId = $event->getPayload()['npcId'];
|
||||||
$this->startInteraction($npcId);
|
$this->startInteraction($npcId);
|
||||||
break;
|
break;
|
||||||
|
|
@ -72,6 +71,8 @@ class InteractionSystem implements EventListenerInterface {
|
||||||
|
|
||||||
if (!$npc) {
|
if (!$npc) {
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈 ({$npcId} 不存在)。"]));
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里没有人可以交谈 ({$npcId} 不存在)。"]));
|
||||||
|
// ⭐ 切换回地图模式
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,64 +80,38 @@ class InteractionSystem implements EventListenerInterface {
|
||||||
'message' => "👤 你走近了 <fg=cyan;options=bold>{$npc->getName()}</>。"
|
'message' => "👤 你走近了 <fg=cyan;options=bold>{$npc->getName()}</>。"
|
||||||
]));
|
]));
|
||||||
|
|
||||||
// 启动对话流程
|
// ⭐ 直接尝试触发任务对话
|
||||||
$this->dialogueLoop($npc);
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$questIds = $this->questRepository->getQuestsByNpc($npc->id);
|
||||||
// 交互结束后,重新打印主菜单请求
|
$foundQuest = false;
|
||||||
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
|
||||||
}
|
// ⭐ 优先检查是否有已完成的任务可以提交
|
||||||
|
foreach ($questIds as $questId) {
|
||||||
/**
|
$activeQuests = $player->getActiveQuests();
|
||||||
* 2. 核心对话循环
|
if (isset($activeQuests[$questId]) && $activeQuests[$questId]->isCompleted()) {
|
||||||
*/
|
// 找到已完成的任务,提示玩家交任务
|
||||||
/**
|
$this->dispatcher->dispatch(new Event('QuestTurnInPrompt', [
|
||||||
* 2. 核心交互循环
|
'questId' => $questId,
|
||||||
*/
|
'npcId' => $npc->id
|
||||||
private function dialogueLoop(NPC $npc): void {
|
]));
|
||||||
$running = true;
|
$foundQuest = true;
|
||||||
|
break;
|
||||||
while ($running) {
|
|
||||||
// 检查交互类型并获取玩家选择
|
|
||||||
$choice = $this->promptPlayerChoice($npc);
|
|
||||||
|
|
||||||
switch ($choice) {
|
|
||||||
case 'T': // 交谈 (可能触发任务)
|
|
||||||
if ($this->handleTalk($npc)) {
|
|
||||||
// 如果触发了对话系统,结束当前的 Interaction Loop,交由 DialogueMode 接管
|
|
||||||
$running = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'S': // 触发商店
|
|
||||||
$this->dispatcher->dispatch(new Event('OpenShopEvent', ['npcId' => $npc->id]));
|
|
||||||
break;
|
|
||||||
case 'E': // 结束对话
|
|
||||||
$running = false;
|
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "🤝 你结束了与 {$npc->getName()} 的对话。"]));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的交互指令。']));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private function handleTalk(NPC $npc): bool {
|
|
||||||
$player = $this->stateManager->getPlayer();
|
|
||||||
|
|
||||||
// 1. 查找此 NPC 提供的所有任务
|
// 如果没有已完成的任务,检查是否有新任务
|
||||||
$questIds = $this->questRepository->getQuestsByNpc($npc->id);
|
if (!$foundQuest) {
|
||||||
|
foreach ($questIds as $questId) {
|
||||||
$foundQuest = false;
|
// 检查任务状态:未接受 或 进行中
|
||||||
foreach ($questIds as $questId) {
|
if (!$player->isQuestCompleted($questId) && !isset($player->getActiveQuests()[$questId])) {
|
||||||
// 检查任务状态:未接受 或 进行中 (如果是进行中,可能需要不同的对话,比如询问进度)
|
// 找到了一个新任务!
|
||||||
// 简单起见,这里优先查找“未接受”的任务
|
$questData = $this->questRepository->find($questId);
|
||||||
if (!$player->isQuestCompleted($questId) && !isset($player->getActiveQuests()[$questId])) {
|
if ($questData && !empty($questData['dialogue'])) {
|
||||||
// 找到了一个新任务!
|
// ⭐ 使用新版对话系统
|
||||||
$questData = $this->questRepository->find($questId);
|
$this->dialogueService->startDialogue($questData['dialogue']);
|
||||||
if ($questData && !empty($questData['dialogue'])) {
|
$foundQuest = true;
|
||||||
// ⭐ 使用新版对话系统
|
break;
|
||||||
$this->dialogueService->startDialogue($questData['dialogue']);
|
}
|
||||||
// 对话已启动,返回 true 以退出 InteractionLoop
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,46 +120,25 @@ class InteractionSystem implements EventListenerInterface {
|
||||||
// 如果没有新任务,显示 NPC 默认闲聊
|
// 如果没有新任务,显示 NPC 默认闲聊
|
||||||
$defaultMsg = is_array($npc->dialogue) ? ($npc->dialogue['greeting'] ?? '...') : '...';
|
$defaultMsg = is_array($npc->dialogue) ? ($npc->dialogue['greeting'] ?? '...') : '...';
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
'message' => "<fg=cyan>{$npc->getName()}</>:<fg=white>{$defaultMsg}</>"
|
'message' => "<fg=cyan>{$npc->getName()}</><fg=white>{$defaultMsg}</>"
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
// ⭐ 检查NPC是否有商店
|
||||||
|
if ($npc->hasShop) {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "\n🛍️ <fg=yellow>{$npc->getName()}</> 还经营着一家商店。"
|
||||||
|
]));
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "<fg=gray>输入 [S] 打开商店,[X] 离开</>"
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 设置待处理的商店NPC
|
||||||
|
$this->stateManager->setPendingShopNpc($npc);
|
||||||
|
} else {
|
||||||
|
// ⭐ 没有任务也没有商店时切换回地图模式
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取玩家交互指令
|
|
||||||
*/
|
|
||||||
private function promptPlayerChoice(NPC $npc): string {
|
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
|
||||||
'message' => "\n--- 交互菜单 --- [T] 任务 | [S] 商店 | [E] 结束"
|
|
||||||
]));
|
|
||||||
|
|
||||||
$question = new Question("> 请选择指令 (T/S/E):");
|
|
||||||
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
|
||||||
|
|
||||||
return $choice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟从配置中加载 NPC 数据
|
|
||||||
*/
|
|
||||||
private function loadNPC(string $id): ?NPC {
|
|
||||||
$data = match ($id) {
|
|
||||||
'VILLAGER_1' => [
|
|
||||||
'name' => '老村长',
|
|
||||||
'dialogue' => [
|
|
||||||
'greeting' => '你好,旅行者。你看起来很强大。',
|
|
||||||
'quest_response' => '你想要帮忙吗?我们的地窖里有老鼠。',
|
|
||||||
'shop_response' => '我现在没有东西卖给你。',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($data) {
|
|
||||||
return new NPC($id, $data['name'], $data['dialogue']);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,14 +82,14 @@ class MapSystem implements EventListenerInterface {
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里寂静无声,没有人可以交谈。"]));
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "这里寂静无声,没有人可以交谈。"]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ 切换到对话模式
|
|
||||||
$this->stateManager->setMode(StateManager::MODE_DIALOGUE);
|
|
||||||
|
|
||||||
if (count($npcIds) === 1) {
|
if (count($npcIds) === 1) {
|
||||||
|
// ⭐ 只有一个NPC,直接交互
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_DIALOGUE);
|
||||||
$this->dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => $npcIds[0]]));
|
$this->dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => $npcIds[0]]));
|
||||||
} else {
|
} else {
|
||||||
// 多个 NPC 的处理逻辑...
|
// ⭐ 多个 NPC,切换到对话模式并存储待选择列表
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_DIALOGUE);
|
||||||
|
$this->stateManager->setPendingNpcSelection($npcIds);
|
||||||
$this->listNpcsForSelection($npcIds);
|
$this->listNpcsForSelection($npcIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,13 +147,13 @@ class MapSystem implements EventListenerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function listNpcsForSelection(array $npcIds): void {
|
private function listNpcsForSelection(array $npcIds): void {
|
||||||
$output = "👥 这里的居民:\n";
|
$output = "👥 请选择要交谈的对象:\n";
|
||||||
foreach ($npcIds as $index => $id) {
|
foreach ($npcIds as $index => $id) {
|
||||||
$npcData = $this->npcRepository->find($id);
|
$npcData = $this->npcRepository->find($id);
|
||||||
$name = $npcData['name'] ?? "神秘人";
|
$name = $npcData['name'] ?? "神秘人";
|
||||||
$output .= " <fg=yellow>[" . ($index + 1) . "]</> {$name}\n";
|
$output .= " <fg=yellow>[" . ($index) . "]</> {$name}\n";
|
||||||
}
|
}
|
||||||
|
$output .= " <fg=gray>[X] 取消</>";
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => $output]));
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => $output]));
|
||||||
$this->dispatcher->dispatch(new Event('AwaitingNpcSelection', ['options' => $npcIds]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,57 +32,27 @@ class QuestService implements EventListenerInterface {
|
||||||
case 'GameStartEvent':
|
case 'GameStartEvent':
|
||||||
$this->initializeQuests(); // 游戏开始时检查是否有初始任务
|
$this->initializeQuests(); // 游戏开始时检查是否有初始任务
|
||||||
break;
|
break;
|
||||||
case 'QuestCheckEvent': // 响应 InteractionSystem 的任务请求
|
|
||||||
$this->handleQuestCheck($event->getPayload()['npcId']);
|
|
||||||
break;
|
|
||||||
case 'BattleEndEvent': // 响应战斗结束,检查击杀目标
|
case 'BattleEndEvent': // 响应战斗结束,检查击杀目标
|
||||||
$this->checkKillQuests($event->getPayload()['enemyId']);
|
$payload = $event->getPayload();
|
||||||
|
if (isset($payload['enemyId'])) {
|
||||||
|
$this->checkKillQuests($payload['enemyId']);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'MapMoveEvent': // ⭐ 响应移动,检查地点触发任务
|
case 'MapMoveEvent': // ⭐ 响应移动,检查地点触发任务
|
||||||
$this->checkSystemTriggers('MAP_MOVE', $event->getPayload()['targetId'] ?? ''); // 假设 MapMoveEvent 携带 targetId (MapTile ID)
|
$this->checkSystemTriggers('MAP_MOVE', $event->getPayload()['targetId'] ?? ''); // 假设 MapMoveEvent 携带 targetId (MapTile ID)
|
||||||
break;
|
break;
|
||||||
case 'LevelUpEvent': // ⭐ 响应升级
|
case 'LevelUpEvent': // ⭐ 响应升级
|
||||||
$this->checkSystemTriggers('LEVEL_UP', (string)$event->getPayload()['level']);
|
$this->checkSystemTriggers('LEVEL_UP', (string)$event->getPayload()['newLevel']);
|
||||||
break;
|
break;
|
||||||
case 'QuestAcceptRequest': // ⭐ 响应对话中的接受任务请求
|
case 'QuestAcceptRequest': // ⭐ 响应对话中的接受任务请求
|
||||||
$this->startQuest($event->getPayload()['questId']);
|
$this->startQuest($event->getPayload()['questId']);
|
||||||
break;
|
break;
|
||||||
|
case 'QuestTurnInConfirm': // ⭐ 交付任务确认
|
||||||
|
$this->turnInQuest($event->getPayload()['questId']);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. 处理 NPC 交互时的任务检查/接受
|
|
||||||
*/
|
|
||||||
private function handleQuestCheck(string $npcId): void {
|
|
||||||
$player = $this->stateManager->getPlayer();
|
|
||||||
$questId = $this->getQuestIdForNpc($npcId);
|
|
||||||
|
|
||||||
if (!$questId) return;
|
|
||||||
|
|
||||||
if ($player->isQuestCompleted($questId)) {
|
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "(村长)我已经没什么可以教你的了,旅行者。"]));
|
|
||||||
} elseif (isset($player->getActiveQuests()[$questId])) {
|
|
||||||
$this->checkQuestCompletion($questId); // 检查是否可以提交
|
|
||||||
} else {
|
|
||||||
// 接受任务
|
|
||||||
$this->acceptQuest($questId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 2. 接受任务逻辑
|
|
||||||
*/
|
|
||||||
private function acceptQuest(string $questId): void {
|
|
||||||
$player = $this->stateManager->getPlayer();
|
|
||||||
$questConfig = $this->questData[$questId];
|
|
||||||
|
|
||||||
$player->addActiveQuest($questId, $questConfig['target']['count']);
|
|
||||||
|
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
|
||||||
'message' => "📜 <fg=yellow>接受任务:{$questConfig['name']}</> - 目标:{$questConfig['description']}"
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3. 检查击杀类任务进度
|
* 3. 检查击杀类任务进度
|
||||||
*/
|
*/
|
||||||
|
|
@ -91,17 +61,16 @@ class QuestService implements EventListenerInterface {
|
||||||
$activeQuests = $player->getActiveQuests();
|
$activeQuests = $player->getActiveQuests();
|
||||||
|
|
||||||
foreach ($activeQuests as $questId => $progress) {
|
foreach ($activeQuests as $questId => $progress) {
|
||||||
$questConfig = $this->questData[$questId];
|
$questConfig = $this->questRepository->find($questId);
|
||||||
|
if ($questConfig['type'] === 'kill' && $questConfig['target']['entityId'] == $killedEnemyId) {
|
||||||
if ($questConfig['type'] === 'kill' && $questConfig['target']['targetId'] == $killedEnemyId) {
|
|
||||||
// 更新玩家任务进度
|
// 更新玩家任务进度
|
||||||
$player->updateQuestProgress($questId, 1);
|
$player->updateQuestProgress($questId, 1);
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
'message' => "任务进度更新:[{$questConfig['name']}] {$player->getActiveQuests()[$questId]['currentCount']}/{$questConfig['target']['count']}"
|
'message' => "任务进度更新:[{$questConfig['name']}] {$player->getActiveQuests()[$questId]->getCurrentCount()}/{$questConfig['target']['count']}"
|
||||||
]));
|
]));
|
||||||
|
|
||||||
// 如果任务完成,触发 QuestCompletedEventRequest
|
// 如果任务完成,触发 QuestCompletedEventRequest
|
||||||
if ($player->getActiveQuests()[$questId]['isCompleted']) {
|
if ($player->getActiveQuests()[$questId]->isCompleted()) {
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
'message' => "任务 [{$questConfig['name']}] <fg=green;options=bold>已完成!</>请回去找NPC提交。"
|
'message' => "任务 [{$questConfig['name']}] <fg=green;options=bold>已完成!</>请回去找NPC提交。"
|
||||||
]));
|
]));
|
||||||
|
|
@ -187,11 +156,68 @@ class QuestService implements EventListenerInterface {
|
||||||
*/
|
*/
|
||||||
public function initializeQuests(): void {
|
public function initializeQuests(): void {
|
||||||
$player = $this->stateManager->getPlayer();
|
$player = $this->stateManager->getPlayer();
|
||||||
if (empty($player->getActiveQuests()) && empty($player->getCompletedQuests())) {
|
|
||||||
if ($startingQuestId) {
|
// if (empty($player->getActiveQuests()) && empty($player->getCompletedQuests())) {
|
||||||
$this->startQuest($startingQuestId);
|
// if ($startingQuestId) {
|
||||||
}
|
// $this->startQuest($startingQuestId);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⭐ 交付任务
|
||||||
|
*/
|
||||||
|
private function turnInQuest(string $questId): void {
|
||||||
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$activeQuests = $player->getActiveQuests();
|
||||||
|
|
||||||
|
if (!isset($activeQuests[$questId])) {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '任务不存在或已完成。']));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$quest = $activeQuests[$questId];
|
||||||
|
if (!$quest->isCompleted()) {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '任务还未完成,无法交付。']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$questData = $this->questRepository->find($questId);
|
||||||
|
if (!$questData) {
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '任务数据出错。']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发放奖励
|
||||||
|
$rewards = $questData['rewards'] ?? [];
|
||||||
|
|
||||||
|
// 经验值奖励
|
||||||
|
if (isset($rewards['xp'])) {
|
||||||
|
$player->gainXp($rewards['xp']);
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => " ✅ 获得 <fg=yellow>{$rewards['xp']}</> 经验值"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 金币奖励
|
||||||
|
if (isset($rewards['gold'])) {
|
||||||
|
$player->gainGold($rewards['gold']);
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => " ✅ 获得 <fg=yellow>{$rewards['gold']}</> 金币"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 物品奖励
|
||||||
|
if (isset($rewards['itemId'])) {
|
||||||
|
$this->dispatcher->dispatch(new Event('LootFoundEvent', ['lootId' => $rewards['itemId']]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记任务完成
|
||||||
|
$player->markQuestCompleted($questId);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "\n🎉 <fg=green;options=bold>任务已交付!</> 感谢你的帮助!"
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ 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\Event\EventListenerInterface;
|
||||||
use Game\Model\Quest;
|
use Game\Model\Quest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SaveLoadService: 负责将玩家状态持久化,并支持装备与地图位置的还原。
|
* SaveLoadService: 负责将玩家状态持久化,并支持装备与地图位置的还原。
|
||||||
*/
|
*/
|
||||||
class SaveLoadService {
|
class SaveLoadService implements EventListenerInterface {
|
||||||
|
|
||||||
private EventDispatcher $dispatcher;
|
private EventDispatcher $dispatcher;
|
||||||
private StateManager $stateManager;
|
private StateManager $stateManager;
|
||||||
|
|
@ -26,6 +27,77 @@ class SaveLoadService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理事件 - 在关键时刻自动保存
|
||||||
|
*/
|
||||||
|
public function handleEvent(Event $event): void {
|
||||||
|
switch ($event->getType()) {
|
||||||
|
case 'BattleEndEvent': // 战斗结束后自动保存
|
||||||
|
case 'LevelUpEvent': // 升级后自动保存
|
||||||
|
case 'QuestAcceptRequest': // 接受任务后自动保存
|
||||||
|
case 'MapMoveEvent': // 移动后自动保存(静默)
|
||||||
|
case 'UseItemEvent': // 使用物品后
|
||||||
|
case 'EquipItemEvent': // 穿装备后
|
||||||
|
case 'UnequipItemEvent': // 卸下装备后
|
||||||
|
$this->autoSave();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动保存(静默,不显示消息)
|
||||||
|
*/
|
||||||
|
private function autoSave(): void {
|
||||||
|
try {
|
||||||
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$currentTile = $this->stateManager->getCurrentTile();
|
||||||
|
|
||||||
|
if (!$player) return;
|
||||||
|
|
||||||
|
// 1. 序列化背包
|
||||||
|
$serializedInventory = array_map(fn(Item $item) => $this->itemToData($item), $player->getInventory());
|
||||||
|
|
||||||
|
// 2. 序列化装备
|
||||||
|
$serializedEquipment = [];
|
||||||
|
foreach ($player->getEquipment() as $slot => $item) {
|
||||||
|
$serializedEquipment[$slot] = $item ? $this->itemToData($item) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 序列化任务
|
||||||
|
$activeQuests = [];
|
||||||
|
foreach ($player->getActiveQuests() as $quest) {
|
||||||
|
$activeQuests[] = [
|
||||||
|
'config' => $quest->toArray(),
|
||||||
|
'currentCount' => $quest->getCurrentCount()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'player' => [
|
||||||
|
'name' => $player->getName(),
|
||||||
|
'health' => $player->getHealth(),
|
||||||
|
'maxHealth' => $player->getMaxHealth(),
|
||||||
|
'base_attack' => $player->attack,
|
||||||
|
'base_defense' => $player->defense,
|
||||||
|
'level' => $player->getLevel(),
|
||||||
|
'currentXp' => $player->getCurrentXp(),
|
||||||
|
'gold' => $player->getGold(),
|
||||||
|
'inventory' => $serializedInventory,
|
||||||
|
'equipment' => $serializedEquipment,
|
||||||
|
'activeQuests' => $activeQuests,
|
||||||
|
'completedQuests' => $player->getCompletedQuests(),
|
||||||
|
],
|
||||||
|
'world' => [
|
||||||
|
'currentTileId' => $currentTile->id
|
||||||
|
]
|
||||||
|
];
|
||||||
|
file_put_contents($this->savePath, json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// throw $e;
|
||||||
|
// 静默失败,不打扰玩家
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function hasSaveFile(): bool {
|
public function hasSaveFile(): bool {
|
||||||
return file_exists($this->savePath);
|
return file_exists($this->savePath);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ use Game\Event\EventListenerInterface;
|
||||||
use Game\Event\EventDispatcher;
|
use Game\Event\EventDispatcher;
|
||||||
use Game\Model\Item;
|
use Game\Model\Item;
|
||||||
use Game\Model\Player;
|
use Game\Model\Player;
|
||||||
|
use Game\Model\NPC; // ⭐ 新增
|
||||||
|
use Game\Database\ItemRepository; // ⭐ 新增
|
||||||
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;
|
||||||
|
|
@ -21,16 +23,26 @@ class ShopService implements EventListenerInterface {
|
||||||
private InputInterface $input;
|
private InputInterface $input;
|
||||||
private OutputInterface $output;
|
private OutputInterface $output;
|
||||||
private QuestionHelper $helper;
|
private QuestionHelper $helper;
|
||||||
|
private ItemRepository $itemRepository; // ⭐ 新增
|
||||||
|
|
||||||
// 商店的固定库存(通常从配置加载)
|
// 商店的固定库存(通常从DPC配置加载)
|
||||||
private array $shopInventory;
|
private array $shopInventory;
|
||||||
|
private ?NPC $currentShopNpc = null; // ⭐ 当前商店NPC
|
||||||
|
|
||||||
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, InputInterface $input, OutputInterface $output, QuestionHelper $helper) {
|
public function __construct(
|
||||||
|
EventDispatcher $dispatcher,
|
||||||
|
StateManager $stateManager,
|
||||||
|
InputInterface $input,
|
||||||
|
OutputInterface $output,
|
||||||
|
QuestionHelper $helper,
|
||||||
|
ItemRepository $itemRepository // ⭐ 新增
|
||||||
|
) {
|
||||||
$this->dispatcher = $dispatcher;
|
$this->dispatcher = $dispatcher;
|
||||||
$this->stateManager = $stateManager;
|
$this->stateManager = $stateManager;
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
$this->helper = $helper;
|
$this->helper = $helper;
|
||||||
|
$this->itemRepository = $itemRepository; // ⭐ 赋值
|
||||||
$this->loadShopInventory();
|
$this->loadShopInventory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,7 +58,15 @@ class ShopService implements EventListenerInterface {
|
||||||
public function handleEvent(Event $event): void {
|
public function handleEvent(Event $event): void {
|
||||||
switch ($event->getType()) {
|
switch ($event->getType()) {
|
||||||
case 'OpenShopEvent': // 响应 InteractionSystem 的请求
|
case 'OpenShopEvent': // 响应 InteractionSystem 的请求
|
||||||
$this->startShopping();
|
$payload = $event->getPayload();
|
||||||
|
$npc = $payload['npc'] ?? null;
|
||||||
|
if ($npc && $npc->hasShop) {
|
||||||
|
$this->currentShopNpc = $npc;
|
||||||
|
$this->shopInventory = $npc->shopInventory;
|
||||||
|
$this->startShopping();
|
||||||
|
} else {
|
||||||
|
$this->startShopping(); // 使用默认库存
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +75,8 @@ class ShopService implements EventListenerInterface {
|
||||||
* 启动商店界面和循环
|
* 启动商店界面和循环
|
||||||
*/
|
*/
|
||||||
private function startShopping(): void {
|
private function startShopping(): void {
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "\n欢迎光临!看看我有什么好东西。"]));
|
$npcName = $this->currentShopNpc ? $this->currentShopNpc->getName() : '商人';
|
||||||
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "\n🛍️ 欢迎光临 <fg=yellow>{$npcName}</> 的商店!"]));
|
||||||
$running = true;
|
$running = true;
|
||||||
|
|
||||||
while ($running) {
|
while ($running) {
|
||||||
|
|
@ -75,6 +96,9 @@ class ShopService implements EventListenerInterface {
|
||||||
case 'X':
|
case 'X':
|
||||||
$running = false;
|
$running = false;
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '下次再来!']));
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '下次再来!']));
|
||||||
|
// ⭐ 返回地图模式
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||||||
|
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的指令。']));
|
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的指令。']));
|
||||||
|
|
@ -165,17 +189,31 @@ class ShopService implements EventListenerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function displaySaleItems(): void {
|
private function displaySaleItems(): void {
|
||||||
$this->output->writeln("\n--- 🛒 商店出售 ---");
|
$this->output->writeln("\n--- 🛍️ 商店出售 ---");
|
||||||
// 模拟获取 Item 数据的服务(实际应通过 ItemService/DB)
|
|
||||||
$itemsData = [
|
if (empty($this->shopInventory)) {
|
||||||
1 => ['name' => '小型治疗药水', 'value' => 10, 'type' => 'potion'],
|
$this->output->writeln(" <fg=gray>商店没有货物。</>");
|
||||||
3 => ['name' => '高级治疗药水', 'value' => 200, 'type' => 'potion'], // 假设 ID 3
|
$this->output->writeln("--------------------------");
|
||||||
];
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->shopInventory as $itemId => $data) {
|
foreach ($this->shopInventory as $itemId => $data) {
|
||||||
$name = $itemsData[$itemId]['name'] ?? "未知物品";
|
// ⭐ 从 ItemRepository 获取真实数据
|
||||||
$price = $data['price'];
|
$itemData = $this->itemRepository->find($itemId);
|
||||||
$this->output->writeln("[<fg=green>{$itemId}</>] {$name} | 价格: <fg=yellow>{$price}</> 💰");
|
if ($itemData) {
|
||||||
|
$name = $itemData['name'] ?? "未知物品";
|
||||||
|
$price = $data['price'] ?? $itemData['value'] ?? 0;
|
||||||
|
$type = $itemData['type'] ?? '';
|
||||||
|
|
||||||
|
$typeLabel = match($type) {
|
||||||
|
'potion' => '<fg=green>药水</>',
|
||||||
|
'weapon' => '<fg=red>武器</>',
|
||||||
|
'armor' => '<fg=blue>护甲</>',
|
||||||
|
default => $type
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->output->writeln("[<fg=green>{$itemId}</>] <fg=white;options=bold>{$name}</> {$typeLabel} | 价格: <fg=yellow>{$price}</> 💰");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->output->writeln("--------------------------");
|
$this->output->writeln("--------------------------");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ class StateManager {
|
||||||
|
|
||||||
private string $currentMode = self::MODE_MAP;
|
private string $currentMode = self::MODE_MAP;
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
|
// ⭐ 新增:存储待选择的NPC列表
|
||||||
|
private array $pendingNpcSelection = [];
|
||||||
|
// ⭐ 新增:待交付的任务ID
|
||||||
|
private ?string $pendingQuestTurnIn = null;
|
||||||
|
// ⭐ 新增:待处理的商店NPC
|
||||||
|
private $pendingShopNpc = null;
|
||||||
|
|
||||||
public function setMode(string $mode): void {
|
public function setMode(string $mode): void {
|
||||||
$this->currentMode = $mode;
|
$this->currentMode = $mode;
|
||||||
|
|
@ -31,6 +38,46 @@ class StateManager {
|
||||||
public function getMode(): string {
|
public function getMode(): string {
|
||||||
return $this->currentMode;
|
return $this->currentMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⭐ 新增:NPC选择相关方法
|
||||||
|
public function setPendingNpcSelection(array $npcIds): void {
|
||||||
|
$this->pendingNpcSelection = $npcIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPendingNpcSelection(): array {
|
||||||
|
return $this->pendingNpcSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearPendingNpcSelection(): void {
|
||||||
|
$this->pendingNpcSelection = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ 交任务状态管理
|
||||||
|
public function setPendingQuestTurnIn(?string $questId): void {
|
||||||
|
$this->pendingQuestTurnIn = $questId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPendingQuestTurnIn(): ?string {
|
||||||
|
return $this->pendingQuestTurnIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearPendingQuestTurnIn(): void {
|
||||||
|
$this->pendingQuestTurnIn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⭐ 商店NPC状态管理
|
||||||
|
public function setPendingShopNpc($npc): void {
|
||||||
|
$this->pendingShopNpc = $npc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPendingShopNpc() {
|
||||||
|
return $this->pendingShopNpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearPendingShopNpc(): void {
|
||||||
|
$this->pendingShopNpc = null;
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(Connection $db,MapRepository $mapRepository) {
|
public function __construct(Connection $db,MapRepository $mapRepository) {
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
$this->mapRepository = $mapRepository;
|
$this->mapRepository = $mapRepository;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use Game\Event\EventListenerInterface;
|
||||||
use Game\Model\Player;
|
use Game\Model\Player;
|
||||||
use Game\Model\Enemy;
|
use Game\Model\Enemy;
|
||||||
use Game\Model\MapTile;
|
use Game\Model\MapTile;
|
||||||
|
use Game\Database\QuestRepository; // ⭐ 新增
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -15,10 +16,12 @@ class UIService implements EventListenerInterface {
|
||||||
|
|
||||||
private OutputInterface $output;
|
private OutputInterface $output;
|
||||||
private StateManager $stateManager;
|
private StateManager $stateManager;
|
||||||
|
private QuestRepository $questRepository; // ⭐ 新增
|
||||||
|
|
||||||
public function __construct(OutputInterface $output, StateManager $stateManager) {
|
public function __construct(OutputInterface $output, StateManager $stateManager, QuestRepository $questRepository) {
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
$this->stateManager = $stateManager;
|
$this->stateManager = $stateManager;
|
||||||
|
$this->questRepository = $questRepository; // ⭐ 赋值
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleEvent(Event $event): void {
|
public function handleEvent(Event $event): void {
|
||||||
|
|
@ -27,6 +30,7 @@ class UIService implements EventListenerInterface {
|
||||||
case 'ShowMenuEvent': // 👈 监听这个信号
|
case 'ShowMenuEvent': // 👈 监听这个信号
|
||||||
// 根据模式渲染不同的 UI 底部
|
// 根据模式渲染不同的 UI 底部
|
||||||
if ($mode === StateManager::MODE_MAP) {
|
if ($mode === StateManager::MODE_MAP) {
|
||||||
|
$this->displayLocation($this->stateManager->getCurrentTile());
|
||||||
$this->displayMapMenu();
|
$this->displayMapMenu();
|
||||||
} elseif ($mode === StateManager::MODE_BATTLE) {
|
} elseif ($mode === StateManager::MODE_BATTLE) {
|
||||||
$this->displayBattleMenu();
|
$this->displayBattleMenu();
|
||||||
|
|
@ -35,12 +39,18 @@ class UIService implements EventListenerInterface {
|
||||||
case 'ShowStatsRequest': //
|
case 'ShowStatsRequest': //
|
||||||
$this->displayPlayerStats(); // 👈 真正调用显示逻辑
|
$this->displayPlayerStats(); // 👈 真正调用显示逻辑
|
||||||
break;
|
break;
|
||||||
|
case 'ShowQuestListRequest': // ⭐ 新增:显示任务列表
|
||||||
|
$this->displayQuestList();
|
||||||
|
break;
|
||||||
|
case 'QuestTurnInPrompt': // ⭐ 任务交付提示
|
||||||
|
$this->displayQuestTurnInPrompt($event->getPayload());
|
||||||
|
break;
|
||||||
case 'SystemMessage':
|
case 'SystemMessage':
|
||||||
$this->output->writeln("📣 <fg=magenta>{$event->getPayload()['message']}</>");
|
$this->output->writeln("📣 <fg=magenta>{$event->getPayload()['message']}</>");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'MapMoveEvent':
|
case 'MapMoveEvent':
|
||||||
$this->refreshScreen(); // 刷新整个视野
|
// $this->refreshScreen(); // 刷新整个视野
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'StatUpdateEvent':
|
case 'StatUpdateEvent':
|
||||||
|
|
@ -67,43 +77,14 @@ class UIService implements EventListenerInterface {
|
||||||
* 🗺️ 地图模式菜单:强调移动和探索
|
* 🗺️ 地图模式菜单:强调移动和探索
|
||||||
*/
|
*/
|
||||||
private function displayMapMenu(): void {
|
private function displayMapMenu(): void {
|
||||||
$this->output->writeln("\n<fg=black;bg=cyan;options=bold> 🌍 探索模式指令 </>");
|
$this->output->writeln("\n<fg=cyan>[W/A/S/D]移动 [E]探索 [T]交谈 [B]背包 [I]状态 [J]任务 [L]保存 [Q]退出</>");
|
||||||
|
|
||||||
// 移动指令
|
|
||||||
$this->output->writeln(" <fg=yellow>W/A/S/D</> : 移动角色");
|
|
||||||
|
|
||||||
// 交互与查看
|
|
||||||
$this->output->writeln(sprintf(
|
|
||||||
" <fg=yellow>%-5s</> : %-15s | <fg=yellow>%-5s</> : %-15s",
|
|
||||||
"E", "探索区域", "T", "与 NPC 交谈"
|
|
||||||
));
|
|
||||||
|
|
||||||
// 系统指令
|
|
||||||
$this->output->writeln(sprintf(
|
|
||||||
" <fg=yellow>%-5s</> : %-15s | <fg=yellow>%-5s</> : %-15s",
|
|
||||||
"B", "打开背包(Inv)", "I", "查看详细状态"
|
|
||||||
));
|
|
||||||
|
|
||||||
$this->output->writeln(sprintf(
|
|
||||||
" <fg=yellow>%-5s</> : %-15s | <fg=yellow>%-5s</> : %-15s",
|
|
||||||
"L", "保存进度", "Q", "退出游戏"
|
|
||||||
));
|
|
||||||
$this->output->writeln("<fg=gray>" . str_repeat("-", 50) . "</>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⚔️ 战斗模式菜单:强调数字键操作
|
* ⚔️ 战斗模式菜单:强调数字键操作
|
||||||
*/
|
*/
|
||||||
private function displayBattleMenu(): void {
|
private function displayBattleMenu(): void {
|
||||||
$this->output->writeln("\n<fg=white;bg=red;options=bold> ⚔️ 战斗模式指令 (输入数字) </>");
|
$this->output->writeln("\n<fg=red>[1]攻击 [2]技能 [3]物品 [4]逃跑</>");
|
||||||
|
|
||||||
// 战斗选项
|
|
||||||
$this->output->writeln(" <fg=red>[1]</> <fg=white;options=bold>普通攻击</> - 对敌人造成基础伤害");
|
|
||||||
$this->output->writeln(" <fg=blue>[2]</> <fg=white;options=bold>技能攻击</> - 消耗魔法值(MP)释放技能");
|
|
||||||
$this->output->writeln(" <fg=green>[3]</> <fg=white;options=bold>使用物品</> - 恢复生命值或其他效果");
|
|
||||||
$this->output->writeln(" <fg=yellow>[4]</> <fg=white;options=bold>尝试逃跑</> - 概率返回上一个地图格");
|
|
||||||
|
|
||||||
$this->output->writeln("<fg=gray>" . str_repeat("~", 50) . "</>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -220,19 +201,32 @@ class UIService implements EventListenerInterface {
|
||||||
* 打印当前地图区域信息 (增强版)
|
* 打印当前地图区域信息 (增强版)
|
||||||
*/
|
*/
|
||||||
private function displayLocation(MapTile $tile): void {
|
private function displayLocation(MapTile $tile): void {
|
||||||
$this->output->writeln("\n<fg=black;bg=cyan;options=bold> 📍 区域:{$tile->name} </>");
|
$this->output->writeln("\n<fg=black;bg=cyan;options=bold> 📍 {$tile->name} </>");
|
||||||
$this->output->writeln(" <fg=white>{$tile->description}</>");
|
$this->output->writeln(" <fg=white>{$tile->description}</>");
|
||||||
|
|
||||||
// 显示出口
|
// 显示出口 - 使用方向箭头表示
|
||||||
$moveOptions = [];
|
$directionMap = [
|
||||||
|
'N' => ['arrow' => '↑', 'label' => '北'],
|
||||||
|
'S' => ['arrow' => '↓', 'label' => '南'],
|
||||||
|
'E' => ['arrow' => '→', 'label' => '东'],
|
||||||
|
'W' => ['arrow' => '←', 'label' => '西']
|
||||||
|
];
|
||||||
|
|
||||||
|
$moveDisplay = [];
|
||||||
foreach ($tile->connections as $dir => $targetId) {
|
foreach ($tile->connections as $dir => $targetId) {
|
||||||
$moveOptions[] = "<fg=green;options=bold>{$dir}</> (<fg=yellow>{$targetId}</>)";
|
if (isset($directionMap[$dir])) {
|
||||||
|
$arrow = $directionMap[$dir]['arrow'];
|
||||||
|
$moveDisplay[] = "<fg=green>{$arrow}</> {$directionMap[$dir]['label']}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($moveDisplay)) {
|
||||||
|
$this->output->writeln(" 🚪 " . implode(' ', $moveDisplay));
|
||||||
}
|
}
|
||||||
$this->output->writeln(" 🚪 出路: " . implode(' | ', $moveOptions));
|
|
||||||
|
|
||||||
// 如果有 NPC,也显示出来
|
// 如果有 NPC,也显示出来
|
||||||
if (!empty($tile->npcIds)) {
|
if (!empty($tile->npcIds)) {
|
||||||
$this->output->writeln(" 👥 附近的人: <fg=cyan>" . implode(', ', $tile->npcIds) . "</>");
|
$this->output->writeln(" 👥 " . implode(', ', $tile->npcIds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +238,7 @@ class UIService implements EventListenerInterface {
|
||||||
$inventory = $player->getInventory();
|
$inventory = $player->getInventory();
|
||||||
|
|
||||||
$this->output->writeln("\n🎒 <fg=yellow;options=bold>个人背包内容</>");
|
$this->output->writeln("\n🎒 <fg=yellow;options=bold>个人背包内容</>");
|
||||||
$this->output->writeln("<fg=gray>" . str_repeat("=", 30) . "</>");
|
$this->output->writeln("<fg=gray>" . str_repeat("=", 40) . "</>");
|
||||||
|
|
||||||
if (empty($inventory)) {
|
if (empty($inventory)) {
|
||||||
$this->output->writeln(" (空空如也)");
|
$this->output->writeln(" (空空如也)");
|
||||||
|
|
@ -253,12 +247,42 @@ class UIService implements EventListenerInterface {
|
||||||
// 简化效果显示
|
// 简化效果显示
|
||||||
$effectStr = "";
|
$effectStr = "";
|
||||||
if (!empty($item->effects)) {
|
if (!empty($item->effects)) {
|
||||||
$effectStr = " | <fg=gray>效果: " . json_encode($item->effects) . "</>";
|
$parts = [];
|
||||||
|
foreach ($item->effects as $key => $value) {
|
||||||
|
$parts[] = "{$key}+{$value}";
|
||||||
|
}
|
||||||
|
$effectStr = " <fg=green>" . implode(', ', $parts) . "</>";
|
||||||
}
|
}
|
||||||
$this->output->writeln(sprintf(" [<fg=green>%d</>] <fg=white>%s</> (%s)%s", $index, $item->name, $item->type, $effectStr));
|
|
||||||
|
// 显示装备属性加成
|
||||||
|
$statStr = "";
|
||||||
|
if (!empty($item->statModifiers)) {
|
||||||
|
$parts = [];
|
||||||
|
foreach ($item->statModifiers as $key => $value) {
|
||||||
|
$parts[] = "{$key}+{$value}";
|
||||||
|
}
|
||||||
|
$statStr = " <fg=cyan>" . implode(', ', $parts) . "</>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeLabel = match($item->type) {
|
||||||
|
'potion' => '<fg=green>药水</>',
|
||||||
|
'weapon' => '<fg=red>武器</>',
|
||||||
|
'armor' => '<fg=blue>护甲</>',
|
||||||
|
default => $item->type
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->output->writeln(sprintf(
|
||||||
|
" [<fg=yellow>%d</>] <fg=white;options=bold>%s</> %s%s%s",
|
||||||
|
$index,
|
||||||
|
$item->name,
|
||||||
|
$typeLabel,
|
||||||
|
$effectStr,
|
||||||
|
$statStr
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
$this->output->writeln("\n<fg=gray>提示:输入编号使用/装备物品,输入 X 退出</>");
|
||||||
}
|
}
|
||||||
$this->output->writeln("<fg=gray>" . str_repeat("=", 30) . "</>\n");
|
$this->output->writeln("<fg=gray>" . str_repeat("=", 40) . "</>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function displayMainMenu(): void {
|
private function displayMainMenu(): void {
|
||||||
|
|
@ -267,4 +291,101 @@ class UIService implements EventListenerInterface {
|
||||||
$this->output->writeln(" 角色: <fg=yellow>I</> (状态) | <fg=yellow>B</> (背包) | <fg=yellow>L</> (保存)");
|
$this->output->writeln(" 角色: <fg=yellow>I</> (状态) | <fg=yellow>B</> (背包) | <fg=yellow>L</> (保存)");
|
||||||
$this->output->writeln(" <fg=gray>提示: 输入 'USE 0' 可直接使用背包第一格物品</>");
|
$this->output->writeln(" <fg=gray>提示: 输入 'USE 0' 可直接使用背包第一格物品</>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⭐ 显示任务交付提示
|
||||||
|
*/
|
||||||
|
private function displayQuestTurnInPrompt(array $payload): void {
|
||||||
|
$questId = $payload['questId'];
|
||||||
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$quest = $player->getActiveQuests()[$questId];
|
||||||
|
$questData = $this->questRepository->find($questId);
|
||||||
|
|
||||||
|
$this->output->writeln("\n🎉 <fg=green;options=bold>任务完成!</>");
|
||||||
|
$this->output->writeln("<fg=gray>" . str_repeat("=", 50) . "</>");
|
||||||
|
$this->output->writeln(" <fg=yellow;options=bold>{$quest->getTitle()}</>");
|
||||||
|
$this->output->writeln(" <fg=gray>{$quest->getDescription()}</>");
|
||||||
|
$this->output->writeln("");
|
||||||
|
|
||||||
|
// 显示奖励
|
||||||
|
$rewards = $questData['rewards'] ?? [];
|
||||||
|
if (!empty($rewards)) {
|
||||||
|
$this->output->writeln(" <fg=cyan>奖励:</>");
|
||||||
|
if (isset($rewards['xp'])) {
|
||||||
|
$this->output->writeln(" • <fg=yellow>经验值: +{$rewards['xp']}</>");
|
||||||
|
}
|
||||||
|
if (isset($rewards['gold'])) {
|
||||||
|
$this->output->writeln(" • <fg=yellow>金币: +{$rewards['gold']}</>");
|
||||||
|
}
|
||||||
|
if (isset($rewards['itemId'])) {
|
||||||
|
$this->output->writeln(" • <fg=green>物品: {$rewards['itemId']}</>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln("");
|
||||||
|
$this->output->writeln("<fg=gray>" . str_repeat("=", 50) . "</>");
|
||||||
|
$this->output->writeln("<fg=white>输入 [Y] 交付任务,[N] 取消</>");
|
||||||
|
|
||||||
|
// 设置状态,等待玩家输入
|
||||||
|
$this->stateManager->setMode(StateManager::MODE_DIALOGUE);
|
||||||
|
$this->stateManager->setPendingQuestTurnIn($questId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ⭐ 显示任务列表
|
||||||
|
*/
|
||||||
|
private function displayQuestList(): void {
|
||||||
|
$player = $this->stateManager->getPlayer();
|
||||||
|
$activeQuests = $player->getActiveQuests();
|
||||||
|
$completedQuests = $player->getCompletedQuests();
|
||||||
|
|
||||||
|
$this->output->writeln("\n📖 <fg=yellow;options=bold>任务日志</>");
|
||||||
|
$this->output->writeln("<fg=gray>" . str_repeat("=", 60) . "</>");
|
||||||
|
|
||||||
|
// 显示进行中的任务
|
||||||
|
$this->output->writeln("\n<fg=cyan;options=bold>进行中的任务</>");
|
||||||
|
if (empty($activeQuests)) {
|
||||||
|
$this->output->writeln(" <fg=gray>暂无任务</>");
|
||||||
|
} else {
|
||||||
|
foreach ($activeQuests as $quest) {
|
||||||
|
$progress = "";
|
||||||
|
if ($quest->getType() === 'kill' || $quest->getType() === 'collect') {
|
||||||
|
$current = $quest->getCurrentCount();
|
||||||
|
$targetData = $quest->getTarget();
|
||||||
|
$target = $targetData['count'] ?? 1; // ⭐ target是数组,需要取count
|
||||||
|
$percent = $target > 0 ? (int)(($current / $target) * 100) : 0;
|
||||||
|
$progress = sprintf(" <fg=yellow>[%d/%d - %d%%]</>", $current, $target, $percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln(sprintf(
|
||||||
|
" • <fg=white;options=bold>%s</> %s",
|
||||||
|
$quest->getTitle(),
|
||||||
|
$progress
|
||||||
|
));
|
||||||
|
$this->output->writeln(sprintf(" <fg=gray>%s</>", $quest->getDescription()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示已完成的任务
|
||||||
|
$this->output->writeln("\n<fg=green;options=bold>已完成的任务</>");
|
||||||
|
if (empty($completedQuests)) {
|
||||||
|
$this->output->writeln(" <fg=gray>暂无完成任务</>");
|
||||||
|
} else {
|
||||||
|
$count = 0;
|
||||||
|
foreach ($completedQuests as $questId) {
|
||||||
|
$this->output->writeln(" ✓ <fg=green>{$questId}</>");
|
||||||
|
$count++;
|
||||||
|
if ($count >= 10) {
|
||||||
|
$remaining = count($completedQuests) - 10;
|
||||||
|
if ($remaining > 0) {
|
||||||
|
$this->output->writeln(" <fg=gray>... 还有 {$remaining} 个已完成任务</>");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln("\n<fg=gray>" . str_repeat("=", 60) . "</>");
|
||||||
|
$this->output->writeln("<fg=gray>统计:进行中 " . count($activeQuests) . " | 已完成 " . count($completedQuests) . "</>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
63
tests/test_autosave.php
Normal file
63
tests/test_autosave.php
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("AutoSaveTest", 100, 10, 5);
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== 自动保存功能测试 ===\n\n";
|
||||||
|
|
||||||
|
// 删除旧存档
|
||||||
|
$savePath = __DIR__ . '/../save/player.json';
|
||||||
|
if (file_exists($savePath)) {
|
||||||
|
unlink($savePath);
|
||||||
|
echo "✓ 已清理旧存档\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n1. 测试地图移动触发自动保存...\n";
|
||||||
|
$dispatcher->dispatch(new Event('MapMoveEvent', ['newTileId' => 'FOREST_01']));
|
||||||
|
if (file_exists($savePath)) {
|
||||||
|
echo "✓ 地图移动后自动保存成功\n";
|
||||||
|
$data = json_decode(file_get_contents($savePath), true);
|
||||||
|
echo " - 当前位置: " . $data['world']['currentTileId'] . "\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ 自动保存失败\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n2. 测试升级触发自动保存...\n";
|
||||||
|
$player->gainXp(80);
|
||||||
|
$dispatcher->dispatch(new Event('LevelUpEvent', ['newLevel' => 2]));
|
||||||
|
if (file_exists($savePath)) {
|
||||||
|
$data = json_decode(file_get_contents($savePath), true);
|
||||||
|
echo "✓ 升级后自动保存成功\n";
|
||||||
|
echo " - 当前等级: " . $data['player']['level'] . "\n";
|
||||||
|
echo " - 当前经验: " . $data['player']['currentXp'] . "\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ 自动保存失败\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n3. 测试任务接受触发自动保存...\n";
|
||||||
|
$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'KILL_GOBLIN']));
|
||||||
|
if (file_exists($savePath)) {
|
||||||
|
echo "✓ 任务接受后自动保存成功\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ 自动保存失败\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n=== 测试完成 ===\n";
|
||||||
58
tests/test_inventory.php
Normal file
58
tests/test_inventory.php
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Model\Item;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("ItemTest", 50, 10, 5);
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== 物品使用功能测试 ===\n\n";
|
||||||
|
|
||||||
|
// 添加测试物品
|
||||||
|
$potion = new Item(1, "小型治疗药水", "potion", "恢复20点生命", 10, ['heal' => 20]);
|
||||||
|
$weapon = new Item(2, "铁剑", "weapon", "攻击+5", 50, [], 'weapon', ['attack' => 5]);
|
||||||
|
|
||||||
|
$player->addItem($potion);
|
||||||
|
$player->addItem($weapon);
|
||||||
|
|
||||||
|
echo "1. 初始状态:\n";
|
||||||
|
echo " - 生命值: {$player->getHealth()}/{$player->getMaxHealth()}\n";
|
||||||
|
echo " - 攻击力: {$player->getAttack()}\n";
|
||||||
|
echo " - 背包物品数: " . count($player->getInventory()) . "\n\n";
|
||||||
|
|
||||||
|
// 扣血测试药水
|
||||||
|
$player->takeDamage(30);
|
||||||
|
echo "2. 受到30点伤害后:\n";
|
||||||
|
echo " - 生命值: {$player->getHealth()}/{$player->getMaxHealth()}\n\n";
|
||||||
|
|
||||||
|
// 测试使用药水
|
||||||
|
echo "3. 使用药水 (编号0)...\n";
|
||||||
|
$dispatcher->dispatch(new Event('UseItemEvent', ['itemIndex' => 0]));
|
||||||
|
echo " - 生命值: {$player->getHealth()}/{$player->getMaxHealth()}\n";
|
||||||
|
echo " - 背包物品数: " . count($player->getInventory()) . "\n\n";
|
||||||
|
|
||||||
|
// 测试装备武器
|
||||||
|
echo "4. 装备武器 (编号0,因为药水已被使用)...\n";
|
||||||
|
$dispatcher->dispatch(new Event('EquipItemEvent', ['itemIndex' => 0]));
|
||||||
|
echo " - 攻击力: {$player->getAttack()}\n";
|
||||||
|
echo " - 背包物品数: " . count($player->getInventory()) . "\n";
|
||||||
|
$equipment = $player->getEquipment();
|
||||||
|
echo " - 武器槽: " . ($equipment['weapon'] ? $equipment['weapon']->name : '空') . "\n\n";
|
||||||
|
|
||||||
|
echo "=== 测试完成 ===\n";
|
||||||
48
tests/test_npc_interaction.php
Normal file
48
tests/test_npc_interaction.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
// Mock Input with stream
|
||||||
|
$stream = fopen('php://memory', 'r+');
|
||||||
|
fwrite($stream, "0\n0\n"); // 选择对话选项0,然后再选0接受任务
|
||||||
|
rewind($stream);
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$input->setStream($stream);
|
||||||
|
$input->setInteractive(true);
|
||||||
|
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("TestHero", 100, 10, 5);
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
|
||||||
|
// 设置地图位置到新手村
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== 测试开始 ===\n";
|
||||||
|
echo "当前位置: " . $stateManager->getCurrentTile()->name . "\n";
|
||||||
|
echo "NPC列表: " . implode(', ', $stateManager->getCurrentTile()->npcIds) . "\n\n";
|
||||||
|
|
||||||
|
// 触发与NPC交谈
|
||||||
|
echo "触发 StartInteractionEvent...\n";
|
||||||
|
$dispatcher->dispatch(new Event('AttemptTalkEvent'));
|
||||||
|
|
||||||
|
// 获取输出
|
||||||
|
$display = $output->fetch();
|
||||||
|
echo "\n=== 输出结果 ===\n";
|
||||||
|
echo $display;
|
||||||
|
$dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => 'VILLAGER_1']));
|
||||||
|
dd($stateManager->getPendingNpcSelection());
|
||||||
|
|
||||||
80
tests/test_npc_shop.php
Normal file
80
tests/test_npc_shop.php
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("ShopTest", 100, 10, 5);
|
||||||
|
$player->gainGold(200); // 给玩家一些金币用于测试
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== NPC商店功能测试 ===\n\n";
|
||||||
|
|
||||||
|
// 获取NPC仓库
|
||||||
|
$npcRepo = $container->getNpcRepository();
|
||||||
|
$npc = $npcRepo->createNPC('BLACKSMITH');
|
||||||
|
|
||||||
|
if (!$npc) {
|
||||||
|
die("❌ 无法创建铁匠NPC\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "1. 创建NPC: {$npc->getName()}\n";
|
||||||
|
echo " 是否有商店: " . ($npc->hasShop ? '是' : '否') . "\n";
|
||||||
|
echo " 商店库存数量: " . count($npc->shopInventory) . "\n\n";
|
||||||
|
|
||||||
|
echo "2. 玩家初始状态:\n";
|
||||||
|
echo " - 金币: {$player->getGold()}\n";
|
||||||
|
echo " - 背包物品数: " . count($player->getInventory()) . "\n\n";
|
||||||
|
|
||||||
|
echo "3. 模拟与NPC交互并打开商店...\n";
|
||||||
|
// 模拟InteractionSystem的行为
|
||||||
|
$dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "👤 你走近了 <fg=cyan;options=bold>{$npc->getName()}</>。"
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 模拟NPC对话
|
||||||
|
$defaultMsg = is_array($npc->dialogue) ? ($npc->dialogue['greeting'] ?? '...') : '...';
|
||||||
|
$dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "<fg=cyan>{$npc->getName()}</>:<fg=white>{$defaultMsg}</>"
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 检查商店
|
||||||
|
if ($npc->hasShop) {
|
||||||
|
$dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "\n🛍️ <fg=yellow>{$npc->getName()}</> 还经营着一家商店。"
|
||||||
|
]));
|
||||||
|
$dispatcher->dispatch(new Event('SystemMessage', [
|
||||||
|
'message' => "<fg=gray>输入 [S] 打开商店,[X] 离开</>"
|
||||||
|
]));
|
||||||
|
|
||||||
|
// 设置待处理的商店NPC
|
||||||
|
$stateManager->setPendingShopNpc($npc);
|
||||||
|
|
||||||
|
// 模拟输入S打开商店
|
||||||
|
echo "\n模拟输入 S 打开商店...\n";
|
||||||
|
$dispatcher->dispatch(new Event('OpenShopEvent', [
|
||||||
|
'npc' => $npc
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n" . $output->fetch() . "\n";
|
||||||
|
|
||||||
|
echo "\n4. 测试完成后的状态:\n";
|
||||||
|
echo " - 金币: {$player->getGold()}\n";
|
||||||
|
echo " - 背包物品数: " . count($player->getInventory()) . "\n";
|
||||||
|
|
||||||
|
echo "\n=== 测试完成 ===\n";
|
||||||
48
tests/test_npc_shop_simple.php
Normal file
48
tests/test_npc_shop_simple.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("ShopTest", 100, 10, 5);
|
||||||
|
$player->gainGold(200); // 给玩家一些金币用于测试
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== NPC商店功能测试 ===\n\n";
|
||||||
|
|
||||||
|
// 获取NPC仓库
|
||||||
|
$npcRepo = $container->getNpcRepository();
|
||||||
|
$npc = $npcRepo->createNPC('BLACKSMITH');
|
||||||
|
|
||||||
|
if (!$npc) {
|
||||||
|
die("❌ 无法创建铁匠NPC\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "1. 创建NPC: {$npc->getName()}\n";
|
||||||
|
echo " 是否有商店: " . ($npc->hasShop ? '是' : '否') . "\n";
|
||||||
|
echo " 商店库存数量: " . count($npc->shopInventory) . "\n\n";
|
||||||
|
|
||||||
|
echo "2. 玩家初始状态:\n";
|
||||||
|
echo " - 金币: {$player->getGold()}\n";
|
||||||
|
echo " - 背包物品数: " . count($player->getInventory()) . "\n\n";
|
||||||
|
|
||||||
|
echo "3. NPC商店数据验证:\n";
|
||||||
|
foreach ($npc->shopInventory as $itemId => $data) {
|
||||||
|
echo " - 商品ID: {$itemId}, 价格: {$data['price']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n=== 测试完成 ===\n";
|
||||||
67
tests/test_quest_list.php
Normal file
67
tests/test_quest_list.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Model\Quest;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("QuestTest", 100, 10, 5);
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== 任务列表功能测试 ===\n\n";
|
||||||
|
|
||||||
|
// 添加一些测试任务
|
||||||
|
$quest1 = new Quest(
|
||||||
|
'QUEST_001',
|
||||||
|
'清理野兽',
|
||||||
|
'击败5只野狼',
|
||||||
|
'kill',
|
||||||
|
['entityId' => 'wolf_001', 'count' => 5],
|
||||||
|
['gold' => 100, 'xp' => 50]
|
||||||
|
);
|
||||||
|
$quest1->incrementProgress(); // 已击败 1 只
|
||||||
|
|
||||||
|
$quest2 = new Quest(
|
||||||
|
'QUEST_002',
|
||||||
|
'收集草药',
|
||||||
|
'收集10个草药',
|
||||||
|
'collect',
|
||||||
|
['entityId' => 'herb_basic', 'count' => 10],
|
||||||
|
['gold' => 50, 'xp' => 30]
|
||||||
|
);
|
||||||
|
$quest2->incrementProgress();
|
||||||
|
$quest2->incrementProgress();
|
||||||
|
$quest2->incrementProgress(); // 已收集 3 个
|
||||||
|
|
||||||
|
$player->addActiveQuest($quest1);
|
||||||
|
$player->addActiveQuest($quest2);
|
||||||
|
|
||||||
|
// 添加已完成的任务
|
||||||
|
$player->markQuestCompleted('TUTORIAL_QUEST');
|
||||||
|
$player->markQuestCompleted('FIRST_BATTLE');
|
||||||
|
|
||||||
|
echo "1. 初始状态:\n";
|
||||||
|
echo " - 进行中任务: " . count($player->getActiveQuests()) . "\n";
|
||||||
|
echo " - 已完成任务: " . count($player->getCompletedQuests()) . "\n\n";
|
||||||
|
|
||||||
|
// 测试显示任务列表
|
||||||
|
echo "2. 显示任务列表...\n";
|
||||||
|
$dispatcher->dispatch(new Event('ShowQuestListRequest'));
|
||||||
|
|
||||||
|
echo "\n" . $output->fetch() . "\n";
|
||||||
|
|
||||||
|
echo "=== 测试完成 ===\n";
|
||||||
74
tests/test_quest_turnin.php
Normal file
74
tests/test_quest_turnin.php
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Game\Core\ServiceContainer;
|
||||||
|
use Game\Event\Event;
|
||||||
|
use Game\Model\Quest;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
|
||||||
|
$input = new ArrayInput([]);
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$helperSet = new HelperSet(['question' => new QuestionHelper()]);
|
||||||
|
|
||||||
|
$container = new ServiceContainer($input, $output, $helperSet);
|
||||||
|
$dispatcher = $container->registerServices();
|
||||||
|
$stateManager = $container->getStateManager();
|
||||||
|
|
||||||
|
// 创建测试玩家
|
||||||
|
$player = new \Game\Model\Player("QuestTester", 100, 10, 5);
|
||||||
|
$stateManager->setPlayer($player);
|
||||||
|
$stateManager->setCurrentTileId('TOWN_01');
|
||||||
|
|
||||||
|
echo "=== 任务交付功能测试 ===\n\n";
|
||||||
|
|
||||||
|
// 创建一个测试任务
|
||||||
|
$quest = new Quest(
|
||||||
|
'TEST_QUEST_001',
|
||||||
|
'清理野狼',
|
||||||
|
'击败3只野狼',
|
||||||
|
'kill',
|
||||||
|
['entityId' => 'wolf_001', 'count' => 3],
|
||||||
|
['gold' => 100, 'xp' => 50]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加任务并完成它
|
||||||
|
$player->addActiveQuest($quest);
|
||||||
|
echo "1. 添加任务: {$quest->getTitle()}\n";
|
||||||
|
echo " 状态: 进行中 (0/3)\n\n";
|
||||||
|
|
||||||
|
// 模拟击杀进度
|
||||||
|
$quest->incrementProgress();
|
||||||
|
$quest->incrementProgress();
|
||||||
|
$quest->incrementProgress();
|
||||||
|
|
||||||
|
echo "2. 完成任务目标:\n";
|
||||||
|
echo " 状态: 已完成 (3/3)\n";
|
||||||
|
echo " 是否完成: " . ($quest->isCompleted() ? '是' : '否') . "\n\n";
|
||||||
|
|
||||||
|
echo "3. 初始状态:\n";
|
||||||
|
echo " - 金币: {$player->getGold()}\n";
|
||||||
|
echo " - 经验值: {$player->getCurrentXp()}\n";
|
||||||
|
echo " - 等级: {$player->getLevel()}\n\n";
|
||||||
|
|
||||||
|
// 测试交任务 - 直接调用turnInQuest方法前,需要模拟一个repo数据
|
||||||
|
echo "4. 模拟交付任务逻辑...\n";
|
||||||
|
|
||||||
|
// 直接调用player的方法模拟奖励
|
||||||
|
$player->gainGold(100);
|
||||||
|
$player->gainXp(50);
|
||||||
|
$player->markQuestCompleted('TEST_QUEST_001');
|
||||||
|
|
||||||
|
echo "\n✅ 模拟奖励发放:\n";
|
||||||
|
echo " - 金币 +100\n";
|
||||||
|
echo " - 经验值 +50\n";
|
||||||
|
|
||||||
|
echo "\n5. 交付后状态:\n";
|
||||||
|
echo " - 金币: {$player->getGold()}\n";
|
||||||
|
echo " - 经验值: {$player->getCurrentXp()}\n";
|
||||||
|
echo " - 等级: {$player->getLevel()}\n";
|
||||||
|
echo " - 已完成任务: " . (in_array('TEST_QUEST_001', $player->getCompletedQuests()) ? '是' : '否') . "\n";
|
||||||
|
|
||||||
|
echo "\n=== 测试完成 ===\n";
|
||||||
Loading…
Reference in New Issue
Block a user