diff --git a/config/items.json b/config/items.json index 6bb12e0..4772a71 100644 --- a/config/items.json +++ b/config/items.json @@ -1,6 +1,6 @@ { "POTION_01": { - "id": 1, + "id": "POTION_01", "name": "疗伤药", "type": "potion", "description": "普通的疗伤药,可以恢复少量生命值。", @@ -10,7 +10,7 @@ } }, "POTION_02": { - "id": 2, + "id": "POTION_02", "name": "回气丹", "type": "potion", "description": "可以恢复一定量的真气。", @@ -20,7 +20,7 @@ } }, "POTION_03": { - "id": 3, + "id": "POTION_03", "name": "长春丹", "type": "potion", "description": "传说中的长春丹,据说有延年益寿之效。", @@ -31,7 +31,7 @@ } }, "WEAPON_01": { - "id": 4, + "id": "WEAPON_01", "name": "精钢剑", "type": "weapon", "description": "一柄精钢打造的长剑,锋利坚韧。", @@ -42,7 +42,7 @@ } }, "ARMOR_01": { - "id": 5, + "id": "ARMOR_01", "name": "铁布衫", "type": "armor", "description": "一件铁布衫,可以有效防护。", @@ -53,21 +53,21 @@ } }, "CAOYAO": { - "id": 6, + "id": "CAOYAO", "name": "灵草药", "type": "material", "description": "一株灵草药,可用于炼制丹药。", "value": 50 }, "ZHENGBI": { - "id": 7, + "id": "CAOYAO", "name": "银两", "type": "currency", "description": "七玄门通行的货币,可用于交易。", "value": 1 }, "ZHUTIANPING": { - "id": 8, + "id": "ZHUTIANPING", "name": "掌天瓶", "type": "treasure", "description": "传说中的至宝掌天瓶,具有神秘力量。", diff --git a/config/map.json b/config/map.json index b19d327..9e43a77 100644 --- a/config/map.json +++ b/config/map.json @@ -7,7 +7,13 @@ "E": "VILLAGE_01", "W": "FOREST_01" }, - "encounterPool": ["GOBLIN", "WOLF"], + "encounterPool": [{ + "id": "GOBLIN", + "weight": 60 + }, { + "id": "WOLF", + "weight": 40 + }], "encounterChance": 0.5, "npcIds": ["VILLAGER_1"], "lootIds": ["POTION_01"] @@ -20,7 +26,15 @@ "W": "TOWN_01", "N": "CAVE_01" }, - "encounterPool": ["WOLF", "BEAR"], + "encounterPool": [ + { + "id": "WOLF", + "weight": 60 + }, + { + "id": "BEAR", + "weight": 40 + }], "encounterChance": 0.5, "npcIds": [], "lootIds": ["HERB_BASIC"] @@ -32,7 +46,12 @@ "connections": { "E": "TOWN_01" }, - "encounterPool": ["STRANGE_CULTIVATOR"], + "encounterPool": [ + { + "id": "STRANGE_CULTIVATOR", + "weight": 60 + } + ], "encounterChance": 0.5, "npcIds": ["BLACKSMITH"], "lootIds": ["CAOYAO"] @@ -44,7 +63,12 @@ "connections": { "S": "VILLAGE_01" }, - "encounterPool": ["GUARD"], + "encounterPool": [ + { + "id": "GUARD", + "weight": 60 + } + ], "encounterChance": 0.5, "npcIds": ["MODOCTOR"], "lootIds": ["ZHENGBI", "ZHUTIANPING"] diff --git a/config/quests.json b/config/quests.json index f459b95..2c56bdd 100644 --- a/config/quests.json +++ b/config/quests.json @@ -3,88 +3,171 @@ "id": "QUEST_001", "name": "七玄门初入江湖", "description": "作为新入门的弟子,需要熟悉门派环境,完成基础任务。", - "type": "collect", - "target": { - "entityId": "ZHENGBI", - "count": 10 - }, - "rewards": { - "xp": 50, - "gold": 20, - "itemId": "POTION_01" - }, - "triggerType": "NPC", - "triggerValue": "VILLAGER_1", - "dialogue": { - "start": "师弟,门中最近需要一些银两,你能帮我去后山收集10两银子吗?", - "progress": "还差一些银两,继续努力。", - "complete": "很好,师弟表现不错,这是你的奖励。" + "npcId": "VILLAGER_1", + "nodes": { + "START": { + "type": "dialogue", + "text": "师弟,门中最近需要一些银两,你能帮我去后山收集10两银子吗?", + "options": [ + { "text": "好,我去收集银两。", "next_node": "ACCEPT" }, + { "text": "现在没时间,下次再说。", "next_node": "END" } + ], + "preConditions": [ + { "type": "level", "value": 1 } + ] + }, + "ACCEPT": { + "type": "objective", + "goal": { "type": "collect", "entityId": "ZHENGBI", "count": 10 }, + "on_complete": "REWARD" + }, + "REWARD": { + "type": "dialogue", + "text": "很好,师弟表现不错,这是你的奖励。", + "options": [ + { "text": "感谢奖励", "next_node": null } + ], + "actions": [ + { "type": "give_xp", "amount": 50 }, + { "type": "give_gold", "amount": 20 }, + { "type": "give_item", "itemId": "POTION_01", "quantity": 1 }, + { "type": "complete_quest", "questId": "QUEST_001" } + ] + }, + "END": { + "type": "dialogue", + "text": "好吧,有时间再帮忙。", + "options": [] + } } }, "QUEST_002": { "id": "QUEST_002", "name": "后山除害", - "description": "后山出现了一些野兽,威胁门派安全,需要清除。", - "type": "kill", - "target": { - "entityId": "WOLF", - "count": 3 - }, - "rewards": { - "xp": 100, - "gold": 30, - "itemId": "WEAPON_01" - }, - "triggerType": "NPC", - "triggerValue": "VILLAGER_1", - "dialogue": { - "start": "师弟,后山有三只野狼威胁弟子安全,你能帮我们解决吗?", - "progress": "野狼还未全部清除,小心应对。", - "complete": "太好了,后山安全了,这是给你的奖励。" + "description": "后山出现了一些野狼,威胁门派安全,需要清除。", + "npcId": "VILLAGER_1", + "nodes": { + "START": { + "type": "dialogue", + "text": "师弟,后山有三只野狼威胁弟子安全,你能帮我们解决吗?", + "options": [ + { "text": "野狼?我可以帮忙处理。", "next_node": "ACCEPT" }, + { "text": "太危险了,我不去。", "next_node": "END" } + ], + "preConditions": [ + { "type": "level", "value": 2 } + ] + }, + "ACCEPT": { + "type": "objective", + "goal": { "type": "kill", "entityId": "WOLF", "count": 3 }, + "on_complete": "REWARD" + }, + "REWARD": { + "type": "dialogue", + "text": "太好了,后山安全了,这是给你的奖励。", + "options": [ + { "text": "收下奖励", "next_node": null } + ], + "actions": [ + { "type": "give_xp", "amount": 100 }, + { "type": "give_gold", "amount": 30 }, + { "type": "give_item", "itemId": "WEAPON_01", "quantity": 1 }, + { "type": "complete_quest", "questId": "QUEST_002" } + ] + }, + "END": { + "type": "dialogue", + "text": "好吧,注意安全。", + "options": [] + } } }, "QUEST_003": { "id": "QUEST_003", "name": "意外救墨大夫", "description": "在地牢中发现被困的神秘人物,需要帮助他脱困。", - "type": "explore", - "target": { - "entityId": "CAVE_01", - "count": 1 - }, - "rewards": { - "xp": 200, - "gold": 100, - "itemId": "POTION_03" - }, - "triggerType": "SYSTEM", - "triggerValue": "EXPLORE_CAVE", - "dialogue": { - "start": "小友,老夫被困在此地多日,若能助我脱困,必有重谢。", - "progress": "需要找到方法帮助墨大夫。", - "complete": "多谢小友相助,这是老夫的一点心意。" + "npcId": "MODOCTOR", + "nodes": { + "START": { + "type": "dialogue", + "text": "小友,老夫被困在此地多日,若能助我脱困,必有重谢。", + "options": [ + { "text": "墨大夫,我来救你。", "next_node": "ACCEPT" }, + { "text": "我还有事,改日再来。", "next_node": "END" } + ], + "preConditions": [ + { "type": "level", "value": 3 } + ] + }, + "ACCEPT": { + "type": "objective", + "goal": { "type": "explore", "entityId": "CAVE_01", "count": 1 }, + "on_complete": "REWARD" + }, + "REWARD": { + "type": "dialogue", + "text": "多谢小友相助!这是老夫的一点心意。", + "options": [ + { "text": "收下奖励", "next_node": null } + ], + "actions": [ + { "type": "give_xp", "amount": 200 }, + { "type": "give_gold", "amount": 100 }, + { "type": "give_item", "itemId": "POTION_03", "quantity": 1 }, + { "type": "complete_quest", "questId": "QUEST_003" } + ] + }, + "END": { + "type": "dialogue", + "text": "希望下次能帮上忙。", + "options": [] + } } }, "QUEST_004": { "id": "QUEST_004", "name": "发现长春功", "description": "从墨大夫处学习到神秘功法,需要修炼至小成。", - "type": "level", - "target": { - "entityId": "PLAYER", - "count": 2 - }, - "rewards": { - "xp": 300, - "gold": 200, - "itemId": "ZHUTIANPING" - }, - "triggerType": "NPC", - "triggerValue": "MODOCTOR", - "dialogue": { - "start": "小友,老夫观你骨骼精奇,可传授你长春功,但需谨慎修炼。", - "progress": "修炼需要时间,循序渐进。", - "complete": "恭喜小友,长春功已初见成效,这是掌天瓶,望善用之。" + "npcId": "MODOCTOR", + "nodes": { + "START": { + "type": "dialogue", + "text": "小友,老夫观你骨骼精奇,可传授你长春功,但需谨慎修炼。", + "options": [ + { "text": "请墨大夫传授功法。", "next_node": "PATH_ACCEPT" }, + { "text": "我现在还不想学。", "next_node": "PATH_DECLINE" } + ], + "preConditions": [ + { "type": "completed_quest", "questId": "QUEST_003" } + ] + }, + "PATH_ACCEPT": { + "type": "objective", + "goal": { "type": "level", "value": 5 }, + "on_complete": "REWARD" + }, + "PATH_DECLINE": { + "type": "dialogue", + "text": "功法修炼需要机缘,日后再谈。", + "options": [], + "actions": [ + { "type": "complete_quest", "questId": "QUEST_004" } + ] + }, + "REWARD": { + "type": "dialogue", + "text": "恭喜小友,长春功已初见成效,这是掌天瓶,望善用之。", + "options": [ + { "text": "收下奖励", "next_node": null } + ], + "actions": [ + { "type": "give_xp", "amount": 300 }, + { "type": "give_gold", "amount": 200 }, + { "type": "give_item", "itemId": "ZHUTIANPING", "quantity": 1 }, + { "type": "complete_quest", "questId": "QUEST_004" } + ] + } } } } \ No newline at end of file diff --git a/save/player.json b/save/player.json index 65d278e..78d69f2 100644 --- a/save/player.json +++ b/save/player.json @@ -1,174 +1,23 @@ { "player": { - "name": "AutoSaveTest", - "health": 160, - "maxHealth": 160, - "base_attack": 22, - "base_defense": 13, - "level": 5, - "currentXp": 175, - "gold": 241, - "inventory": [ - { - "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": 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, - "name": "\u7834\u65e7\u7684\u77ed\u5251", - "type": "weapon", - "description": "\u653b\u51fb\u529b\u5fae\u5f31\u3002", - "value": 50, - "effects": [], - "stats": [], - "slot": "weapon" - } - ], + "name": "hant", + "health": 58, + "maxHealth": 100, + "base_attack": 15, + "base_defense": 5, + "level": 1, + "currentXp": 40, + "gold": 21, + "inventory": [], "equipment": { - "weapon": { - "id": 2, - "name": "\u7834\u65e7\u7684\u77ed\u5251", - "type": "weapon", - "description": "\u653b\u51fb\u529b\u5fae\u5f31\u3002", - "value": 50, - "effects": [], - "stats": [], - "slot": "weapon" - }, + "weapon": null, "armor": null, - "helmet": { - "id": 4, - "name": "\u5e03\u7532\u5934\u76d4", - "type": "armor", - "description": "\u63d0\u4f9b\u5c11\u91cf\u9632\u5fa1\u3002", - "value": 30, - "effects": [], - "stats": [], - "slot": "helmet" - } + "helmet": null }, "activeQuests": [], - "completedQuests": [ - "KILL_GOBLIN" - ] + "completedQuests": [] }, "world": { - "currentTileId": "FOREST_01" + "currentTileId": "CAVE_01" } } \ No newline at end of file diff --git a/src/Database/MapRepository.php b/src/Database/MapRepository.php index b170384..6867fd8 100644 --- a/src/Database/MapRepository.php +++ b/src/Database/MapRepository.php @@ -18,11 +18,11 @@ class MapRepository 'id' => $id, 'name' => $tileData['name'], 'description' => $tileData['description'], - 'encounter_chance' => $tileData['encounter_chance'] ?? 0, + 'encounter_chance' => $tileData['encounterChance'] ?? 0, 'connections' => $tileData['connections'] ?? [], - 'encounter_pool' => $tileData['encounter_pool'] ?? [], - 'npc_ids' => $tileData['npc_ids'] ?? [], - 'loot_ids' => $tileData['loot_ids'] ?? [] + 'encounter_pool' => $tileData['encounterPool'] ?? [], + 'npc_ids' => $tileData['npcIds'] ?? [], + 'loot_ids' => $tileData['lootIds'] ?? [] ]; } diff --git a/src/Database/QuestRepository.php b/src/Database/QuestRepository.php index d3334d8..186294a 100644 --- a/src/Database/QuestRepository.php +++ b/src/Database/QuestRepository.php @@ -21,14 +21,17 @@ class QuestRepository implements RepositoryInterface { */ private function buildNpcQuestIndex(): void { foreach ($this->data as $questId => $questData) { - // 优先使用 triggerType = NPC 的 triggerValue - if (isset($questData['triggerType']) && $questData['triggerType'] === 'NPC' && isset($questData['triggerValue'])) { + // 优先使用新的 npcId 字段 + if (isset($questData['npcId'])) { + $npcId = $questData['npcId']; + $this->questsByNpc[$npcId][] = $questId; + } + // 兼容旧数据:使用 triggerType = NPC 的 triggerValue + elseif (isset($questData['triggerType']) && $questData['triggerType'] === 'NPC' && isset($questData['triggerValue'])) { $npcId = $questData['triggerValue']; $this->questsByNpc[$npcId][] = $questId; } - // 兼容旧数据:检查 target_npc_id (如果是 talk 类型或者仅仅是关联) - // 但如果不作为触发条件,可能不应该在这里索引? - // 保持兼容性:如果还没索引过,且有 target_npc_id + // 兼容旧数据:检查 target_npc_id elseif (isset($questData['target_npc_id'])) { $targetId = $questData['target_npc_id']; // 避免重复? diff --git a/src/Model/Player.php b/src/Model/Player.php index a4456db..95e03b6 100644 --- a/src/Model/Player.php +++ b/src/Model/Player.php @@ -16,6 +16,9 @@ class Player extends Character { 'helmet' => null, // 可根据需要添加 'ring', 'amulet' 等 ]; + // ⭐ 新增:任务节点状态 + protected array $activeQuestNodes = []; + public function __construct(string $name, int $maxHealth, int $attack, int $defense,int $maxMana = 50) { // 调用父类 (Character) 的构造函数来初始化核心属性 parent::__construct($name, $maxHealth, $attack, $defense,$maxMana); @@ -126,6 +129,31 @@ class Player extends Character { public function isQuestCompleted(string $questId): bool { return in_array($questId, $this->completedQuests); } + + // ⭐ 新增方法:添加任务节点 + public function addActiveQuestNode(string $questId, string $nodeId): void { + $this->activeQuestNodes[$questId] = $nodeId; + } + + // ⭐ 新增方法:获取任务节点 + public function getQuestNode(string $questId): ?string { + return $this->activeQuestNodes[$questId] ?? null; + } + + // ⭐ 新增方法:更新任务节点 + public function updateQuestNode(string $questId, string $nodeId): void { + $this->activeQuestNodes[$questId] = $nodeId; + } + + // ⭐ 新增方法:获取所有任务节点 + public function getActiveQuestNodes(): array { + return $this->activeQuestNodes; + } + + // ⭐ 新增方法:设置任务节点(用于存档加载) + public function setActiveQuestNodes(array $nodes): void { + $this->activeQuestNodes = $nodes; + } // ⭐ 新增:玩家背包 (存储 Item 实例) protected array $inventory = []; diff --git a/src/Model/QuestNode.php b/src/Model/QuestNode.php new file mode 100644 index 0000000..6f2cf0e --- /dev/null +++ b/src/Model/QuestNode.php @@ -0,0 +1,39 @@ +id = $id; + $this->type = $type; + $this->text = $text; + $this->options = $options; + $this->goal = $goal; + $this->nextNode = $nextNode; + $this->onComplete = $onComplete; + $this->preConditions = $preConditions; + $this->actions = $actions; + } +} \ No newline at end of file diff --git a/src/System/ActionExecutor.php b/src/System/ActionExecutor.php new file mode 100644 index 0000000..ef3ce0c --- /dev/null +++ b/src/System/ActionExecutor.php @@ -0,0 +1,75 @@ +dispatcher = $dispatcher; + } + + public function executeActions(Player $player, array $actions): void { + foreach ($actions as $action) { + $this->executeAction($player, $action); + } + } + + private function executeAction(Player $player, array $action): void { + $type = $action['type'] ?? ''; + + switch ($type) { + case 'give_item': + $this->giveItem($player, $action); + break; + case 'give_xp': + $this->giveXp($player, $action); + break; + case 'give_gold': + $this->giveGold($player, $action); + break; + case 'set_variable': + $this->setVariable($player, $action); + break; + case 'complete_quest': + $this->completeQuest($player, $action); + break; + default: + // 未知动作类型,跳过 + break; + } + } + + private function giveItem(Player $player, array $action): void { + $itemId = $action['itemId'] ?? ''; + $quantity = $action['quantity'] ?? 1; + // 这里可以调用LootService给予物品 + } + + private function giveXp(Player $player, array $action): void { + $amount = $action['amount'] ?? 0; + $player->gainXp($amount); + } + + private function giveGold(Player $player, array $action): void { + $amount = $action['amount'] ?? 0; + $player->gainGold($amount); + } + + private function setVariable(Player $player, array $action): void { + // 设置玩家变量 + $name = $action['name'] ?? ''; + $value = $action['value'] ?? null; + // 这里可以扩展玩家变量系统 + } + + private function completeQuest(Player $player, array $action): void { + $questId = $action['questId'] ?? ''; + $player->markQuestCompleted($questId); + } +} \ No newline at end of file diff --git a/src/System/ConditionEngine.php b/src/System/ConditionEngine.php new file mode 100644 index 0000000..0c24a37 --- /dev/null +++ b/src/System/ConditionEngine.php @@ -0,0 +1,44 @@ +checkSingleCondition($player, $condition)) { + return false; + } + } + return true; + } + + private function checkSingleCondition(Player $player, array $condition): bool { + $type = $condition['type'] ?? ''; + + switch ($type) { + case 'level': + return $player->getLevel() >= ($condition['value'] ?? 0); + case 'completed_quest': + return $player->isQuestCompleted($condition['questId'] ?? ''); + case 'variable': + // 检查玩家变量 + $varName = $condition['name'] ?? ''; + $expectedValue = $condition['value'] ?? null; + // 这里可以扩展玩家变量检查逻辑 + return true; // 简化实现 + case 'item': + // 检查玩家是否拥有特定物品 + $itemId = $condition['itemId'] ?? ''; + $minCount = $condition['count'] ?? 1; + // 这里可以扩展物品检查逻辑 + return true; // 简化实现 + default: + return true; + } + } +} \ No newline at end of file diff --git a/src/System/DialogueService.php b/src/System/DialogueService.php index e130d45..55e6058 100644 --- a/src/System/DialogueService.php +++ b/src/System/DialogueService.php @@ -24,6 +24,7 @@ class DialogueService implements EventListenerInterface { private array $currentDialogueTree = []; private string $currentNodeId = ''; + private ?string $currentQuestId = null; // ⭐ 添加任务ID public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, InputInterface $input, OutputInterface $output, QuestionHelper $helper) { $this->dispatcher = $dispatcher; @@ -37,11 +38,13 @@ class DialogueService implements EventListenerInterface { switch ($event->getType()) { case 'StartDialogueEvent': $dialogue = $event->getPayload()['dialogue']; - $this->startDialogue($dialogue); + $questId = $event->getPayload()['questId'] ?? null; // ⭐ 获取任务ID + $this->startDialogue($dialogue, $questId); // ⭐ 传递任务ID break; case 'DialogueChoice': // ⭐ Listen for choice event - $choice = $event->getPayload()['choice']; - $this->handleChoice($choice); + $choiceIndex = $event->getPayload()['choiceIndex']; // ⭐ 修正键名 + $questId = $event->getPayload()['questId'] ?? null; // ⭐ 获取任务ID + $this->handleChoice($choiceIndex, $questId); // ⭐ 传递任务ID break; } } @@ -49,9 +52,10 @@ class DialogueService implements EventListenerInterface { /** * 开始一段对话 (非阻塞,初始化状态) */ - public function startDialogue(array $dialogueTree): void { + public function startDialogue(array $dialogueTree, ?string $questId = null): void { $this->currentDialogueTree = $dialogueTree; - $this->currentNodeId = 'root'; + $this->currentNodeId = 'root'; // ⭐ 使用'root'作为根节点 + $this->currentQuestId = $questId; // ⭐ 保存任务ID // 切换游戏模式 $this->stateManager->setMode(StateManager::MODE_DIALOGUE); @@ -89,7 +93,7 @@ class DialogueService implements EventListenerInterface { /** * 处理玩家选择 */ - private function handleChoice(string $input): void { + private function handleChoice(string $input, ?string $questId = null): void { // ⭐ 添加任务ID参数 if (!isset($this->currentDialogueTree[$this->currentNodeId])) { $this->endDialogue(); return; @@ -101,7 +105,7 @@ class DialogueService implements EventListenerInterface { // 如果没有选项,任意输入都结束对话 (或者跳转 next) if (empty($node->options)) { $this->endDialogue(); - return; + return; } if (!is_numeric($input)) { @@ -131,11 +135,20 @@ class DialogueService implements EventListenerInterface { $this->currentNodeId = $selectedOption['next']; $this->displayCurrentNode(); } + + // 3. 如果是任务对话,分发选择事件到InteractionSystem + if ($questId) { + $this->dispatcher->dispatch(new Event('DialogueChoice', [ + 'choiceIndex' => (int)$input, + 'questId' => $questId + ])); + } } private function endDialogue(): void { $this->currentDialogueTree = []; $this->currentNodeId = ''; + $this->currentQuestId = null; // ⭐ 重置任务ID $this->output->writeln("\n[对话结束]"); // 恢复地图模式 (或者之前的模式,稍微简化处理) @@ -160,4 +173,4 @@ class DialogueService implements EventListenerInterface { break; } } -} +} \ No newline at end of file diff --git a/src/System/Evaluator.php b/src/System/Evaluator.php new file mode 100644 index 0000000..8cfae0e --- /dev/null +++ b/src/System/Evaluator.php @@ -0,0 +1,71 @@ +evaluateKillGoal($player, $goal); + case 'collect': + return $this->evaluateCollectGoal($player, $goal); + case 'level': + return $this->evaluateLevelGoal($player, $goal); + case 'explore': + return $this->evaluateExploreGoal($player, $goal); + case 'talk': + return $this->evaluateTalkGoal($player, $goal); + default: + return false; + } + } + + private function evaluateKillGoal(Player $player, array $goal): bool { + // 这里需要跟踪玩家击杀记录,简化实现 + // 在实际实现中,需要在BattleService中记录击杀 + $requiredEntityId = $goal['entityId'] ?? ''; + $requiredCount = $goal['count'] ?? 1; + + // 这里需要检查玩家的击杀记录,暂时返回false + return false; + } + + private function evaluateCollectGoal(Player $player, array $goal): bool { + // 检查玩家背包中是否拥有足够数量的物品 + $requiredItemId = $goal['entityId'] ?? ''; + $requiredCount = $goal['count'] ?? 1; + + // 计算玩家背包中指定物品的数量 + $itemCount = 0; + foreach ($player->getInventory() as $item) { + if ($item->id === $requiredItemId) { + $itemCount++; + } + } + + return $itemCount >= $requiredCount; + } + + private function evaluateLevelGoal(Player $player, array $goal): bool { + $requiredLevel = $goal['value'] ?? 1; + return $player->getLevel() >= $requiredLevel; + } + + private function evaluateExploreGoal(Player $player, array $goal): bool { + // 这里需要跟踪玩家探索记录,简化实现 + return false; + } + + private function evaluateTalkGoal(Player $player, array $goal): bool { + // 这里需要跟踪玩家对话记录,简化实现 + return false; + } +} \ No newline at end of file diff --git a/src/System/InteractionSystem.php b/src/System/InteractionSystem.php index 8372b58..f321530 100644 --- a/src/System/InteractionSystem.php +++ b/src/System/InteractionSystem.php @@ -59,6 +59,12 @@ class InteractionSystem implements EventListenerInterface { $npcId = $event->getPayload()['npcId']; $this->startInteraction($npcId); break; + // ⭐ 处理StartDialogueEvent + case 'StartDialogueEvent': + $dialogue = $event->getPayload()['dialogue']; + $questId = $event->getPayload()['questId'] ?? null; + $this->dialogueService->startDialogue($dialogue, $questId); + break; } } @@ -77,7 +83,7 @@ class InteractionSystem implements EventListenerInterface { } $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "👤 你走近了 {$npc->getName()}。" + 'message' => "👤 你走近了 {$npc->getName()}" ])); // ⭐ 直接尝试触发任务对话 @@ -108,7 +114,7 @@ class InteractionSystem implements EventListenerInterface { $questData = $this->questRepository->find($questId); if ($questData && !empty($questData['dialogue'])) { // ⭐ 使用新版对话系统 - $this->dialogueService->startDialogue($questData['dialogue']); + $this->dialogueService->startDialogue($questData['dialogue'], $questId); $foundQuest = true; break; } @@ -120,7 +126,7 @@ class InteractionSystem implements EventListenerInterface { // 如果没有新任务,显示 NPC 默认闲聊 $defaultMsg = is_array($npc->dialogue) ? ($npc->dialogue['greeting'] ?? '...') : '...'; $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "{$npc->getName()}{$defaultMsg}" + 'message' => "{$npc->getName()}:{$defaultMsg}" ])); // ⭐ 检查NPC是否有商店 diff --git a/src/System/LootService.php b/src/System/LootService.php index 9602866..36eb68a 100644 --- a/src/System/LootService.php +++ b/src/System/LootService.php @@ -42,8 +42,8 @@ class LootService implements EventListenerInterface { $data['health'], $data['attack'], $data['defense'], - $data['xp_reward'], - $data['loot_table'] ?? [] // 传递 loot_table + $data['xp'], + $data['loot'] ?? [] // 传递 loot_table ); } @@ -150,20 +150,14 @@ class LootService implements EventListenerInterface { /** * 处理探索时发现的宝箱/固定物品 (保持不变) */ - private function handleLootFound(int $lootId): void { - // ... - if ($lootId === 5) { - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "🗝️ 你打开了宝箱!" - ])); - $this->giveItemToPlayer(2); - } + private function handleLootFound($lootId): void { + } /** * 核心逻辑:创建物品实例并添加到玩家背包 (保持不变) */ - private function giveItemToPlayer(int $itemId): void { + private function giveItemToPlayer($itemId): void { // ... $item = $this->itemRepository->createItem($itemId); diff --git a/src/System/MapSystem.php b/src/System/MapSystem.php index c8b1f55..17612cc 100644 --- a/src/System/MapSystem.php +++ b/src/System/MapSystem.php @@ -120,7 +120,6 @@ class MapSystem implements EventListenerInterface { */ private function checkRandomEncounter(float $chance): bool { $currentTile = $this->stateManager->getCurrentTile(); - dd($currentTile); if (!$currentTile->encounterPool || rand(1, 100) / 100 > $chance) { return false; } @@ -142,7 +141,7 @@ class MapSystem implements EventListenerInterface { $currentWeight = 0; foreach ($pool as $item) { $currentWeight += $item['weight']; - if ($randValue <= $currentWeight) return $item['enemyId']; + if ($randValue <= $currentWeight) return $item['id']; } return null; } diff --git a/src/System/QuestService.php b/src/System/QuestService.php index d1ae97a..701497e 100644 --- a/src/System/QuestService.php +++ b/src/System/QuestService.php @@ -5,247 +5,290 @@ use Game\Database\QuestRepository; use Game\Event\Event; use Game\Event\EventListenerInterface; use Game\Event\EventDispatcher; -use Game\Model\Quest; +use Game\Model\QuestNode; use Game\Model\Player; /** - * QuestService: 管理任务的接受、追踪和奖励发放。 + * 重构后的任务服务 - 基于事件驱动和有向图节点 */ class QuestService implements EventListenerInterface { - private EventDispatcher $dispatcher; private StateManager $stateManager; - private QuestRepository $questRepository; // ⭐ 新增属性 + private QuestRepository $questRepository; + private ConditionEngine $conditionEngine; + private Evaluator $evaluator; + private ActionExecutor $actionExecutor; - // ⭐ 修正构造函数:注入 QuestRepository - private $questData = []; - - public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, QuestRepository $questRepository) { + public function __construct( + EventDispatcher $dispatcher, + StateManager $stateManager, + QuestRepository $questRepository + ) { $this->dispatcher = $dispatcher; $this->stateManager = $stateManager; - $this->questRepository = $questRepository; // ⭐ 赋值 + $this->questRepository = $questRepository; + $this->conditionEngine = new ConditionEngine(); + $this->evaluator = new Evaluator(); + $this->actionExecutor = new ActionExecutor($dispatcher); } - public function handleEvent(Event $event): void { switch ($event->getType()) { case 'GameStartEvent': - $this->initializeQuests(); // 游戏开始时检查是否有初始任务 + $this->initializeQuests(); break; - case 'BattleEndEvent': // 响应战斗结束,检查击杀目标 - $payload = $event->getPayload(); - if (isset($payload['enemyId'])) { - $this->checkKillQuests($payload['enemyId']); - } + case 'QuestAcceptRequest': + $this->acceptQuest($event->getPayload()['questId']); break; - case 'MapMoveEvent': // ⭐ 响应移动,检查地点触发任务 - $this->checkSystemTriggers('MAP_MOVE', $event->getPayload()['targetId'] ?? ''); // 假设 MapMoveEvent 携带 targetId (MapTile ID) + case 'QuestProgressEvent': + $this->updateQuestProgress($event->getPayload()); break; - case 'LevelUpEvent': // ⭐ 响应升级 - $this->checkSystemTriggers('LEVEL_UP', (string)$event->getPayload()['newLevel']); + case 'DialogueChoice': + $this->handleDialogueChoice($event->getPayload()); break; - case 'QuestAcceptRequest': // ⭐ 响应对话中的接受任务请求 - $this->startQuest($event->getPayload()['questId']); + case 'BattleEndEvent': + $this->checkKillQuests($event->getPayload()); break; - case 'QuestTurnInConfirm': // ⭐ 交付任务确认 - $this->turnInQuest($event->getPayload()['questId']); + case 'LevelUpEvent': + $this->checkLevelQuests($event->getPayload()); break; } } /** - * 3. 检查击杀类任务进度 + * 接受任务 */ - private function checkKillQuests(string $killedEnemyId): void { + private function acceptQuest(string $questId): void { $player = $this->stateManager->getPlayer(); - $activeQuests = $player->getActiveQuests(); - - foreach ($activeQuests as $questId => $progress) { - $questConfig = $this->questRepository->find($questId); - if ($questConfig['type'] === 'kill' && $questConfig['target']['entityId'] == $killedEnemyId) { - // 更新玩家任务进度 - $player->updateQuestProgress($questId, 1); - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "任务进度更新:[{$questConfig['name']}] {$player->getActiveQuests()[$questId]->getCurrentCount()}/{$questConfig['target']['count']}" - ])); - - // 如果任务完成,触发 QuestCompletedEventRequest - if ($player->getActiveQuests()[$questId]->isCompleted()) { - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "任务 [{$questConfig['name']}] 已完成!请回去找NPC提交。" - ])); - } - } - } - } - - /** - * 4. 检查任务是否可以提交并给予奖励 - */ - private function checkQuestCompletion(string $questId): void { - $player = $this->stateManager->getPlayer(); - $progress = $player->getActiveQuests()[$questId]; - $questConfig = $this->questData[$questId]; - - if ($progress['isCompleted']) { - // 发放奖励 - $rewards = $questConfig['rewards']; - - // 经验值奖励 (交给 CharacterService) - if (isset($rewards['xp'])) { - $this->dispatcher->dispatch(new Event('XpGainedEvent', ['xp' => $rewards['xp']])); - } - - // 物品奖励 (交给 LootService) - if (isset($rewards['itemId'])) { - $this->dispatcher->dispatch(new Event('LootFoundEvent', ['lootId' => $rewards['itemId']])); - } - - // 标记玩家任务完成 - $player->markQuestCompleted($questId); - - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "🎉 任务提交成功! 获得了奖励。" - ])); - } else { - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "(村长)任务 [{$questConfig['name']}] 还没有完成。目标:{$progress['currentCount']}/{$progress['targetCount']}" - ])); - } - } - - /** - * 模拟:根据 NPC ID 获取对应的任务 ID - */ - private function getQuestIdForNpc(string $npcId): ?string { - 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' => "📝 接受任务: {$quest->getName()} - {$quest->getDescription()}" - ])); - } - - /** - * 游戏开始时尝试加载初始任务 (或通过 NPC 交互触发) - */ - public function initializeQuests(): void { - $player = $this->stateManager->getPlayer(); - -// if (empty($player->getActiveQuests()) && empty($player->getCompletedQuests())) { -// 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; + // 检查前置条件 + $startNode = $questData['nodes']['START'] ?? null; + if ($startNode && isset($startNode['preConditions'])) { + if (!$this->conditionEngine->checkConditions($player, $startNode['preConditions'])) { + $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "前置条件不满足,无法接受此任务。"])); + return; + } } + + // 初始化任务状态 + $player->addActiveQuestNode($questId, 'START'); - $quest = $activeQuests[$questId]; - if (!$quest->isCompleted()) { - $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '任务还未完成,无法交付。'])); + $this->dispatcher->dispatch(new Event('SystemMessage', [ + 'message' => "📝 接受任务: {$questData['name']} - {$questData['description']}" + ])); + + // 显示起始对话 + if (isset($startNode['type']) && $startNode['type'] === 'dialogue') { + $this->dispatcher->dispatch(new Event('StartDialogueEvent', [ + 'dialogue' => [ + 'root' => [ + 'text' => $startNode['text'], + 'options' => $startNode['options'] + ] + ], + 'questId' => $questId // 添加任务ID以便对话系统处理选择 + ])); + } + } + + /** + * 更新任务进度 + */ + private function updateQuestProgress(array $payload): void { + $questId = $payload['questId']; + $player = $this->stateManager->getPlayer(); + $currentNode = $player->getQuestNode($questId); + + if (!$currentNode) { 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' => " ✅ 获得 {$rewards['xp']} 经验值" - ])); + $nodeData = $questData['nodes'][$currentNode] ?? null; + if (!$nodeData || $nodeData['type'] !== 'objective') { + return; } - // 金币奖励 - if (isset($rewards['gold'])) { - $player->gainGold($rewards['gold']); - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => " ✅ 获得 {$rewards['gold']} 金币" - ])); + // 检查目标是否完成 + if ($this->evaluator->evaluateObjective($player, $nodeData['goal'])) { + $this->completeObjective($questId, $nodeData); } - - // 物品奖励 - if (isset($rewards['itemId'])) { - $this->dispatcher->dispatch(new Event('LootFoundEvent', ['lootId' => $rewards['itemId']])); - } - - // 标记任务完成 - $player->markQuestCompleted($questId); - - $this->dispatcher->dispatch(new Event('SystemMessage', [ - 'message' => "\n🎉 任务已交付! 感谢你的帮助!" - ])); } /** - * 7. 检查系统触发的任务 + * 完成目标节点 */ - private function checkSystemTriggers(string $triggerType, string $triggerValue): void { + private function completeObjective(string $questId, array $nodeData): void { $player = $this->stateManager->getPlayer(); - $allQuests = $this->questRepository->findAll(); // 假设 we can get all quests + + // 执行完成动作 + if (isset($nodeData['actions'])) { + $this->actionExecutor->executeActions($player, $nodeData['actions']); + } + + // 跳转到下一个节点 + $nextNode = $nodeData['onComplete'] ?? $nodeData['nextNode'] ?? null; + if ($nextNode) { + $player->updateQuestNode($questId, $nextNode); + + // 处理下一个节点 + $questData = $this->questRepository->find($questId); + $nextNodeData = $questData['nodes'][$nextNode] ?? null; + + if ($nextNodeData) { + $this->processNode($questId, $nextNode, $nextNodeData); + } + } else { + // 没有下一个节点,任务完成 + $player->markQuestCompleted($questId); + } + } - foreach ($allQuests as $questData) { - $questId = $questData['id']; + /** + * 处理对话选择 + */ + private function handleDialogueChoice(array $payload): void { + $questId = $payload['questId'] ?? null; + $choiceIndex = $payload['choiceIndex']; + $player = $this->stateManager->getPlayer(); + + if (!$questId) { + return; + } + + $currentNode = $player->getQuestNode($questId); + if (!$currentNode) { + return; + } + + $questData = $this->questRepository->find($questId); + if (!$questData) { + return; + } + + $nodeData = $questData['nodes'][$currentNode] ?? null; + if (!$nodeData || $nodeData['type'] !== 'dialogue') { + return; + } + + $selectedOption = $nodeData['options'][$choiceIndex] ?? null; + if (!$selectedOption) { + return; + } + + // 跳转到下一个节点 + $nextNode = $selectedOption['next_node'] ?? null; + if ($nextNode) { + $player->updateQuestNode($questId, $nextNode); + + // 处理下一个节点 + $nextNodeData = $questData['nodes'][$nextNode] ?? null; + if ($nextNodeData) { + $this->processNode($questId, $nextNode, $nextNodeData); + } + } else { + // 没有下一个节点,任务完成 + $player->markQuestCompleted($questId); + } + } - // 检查触发条件 - if (isset($questData['triggerType']) && $questData['triggerType'] === 'SYSTEM' && - isset($questData['triggerValue']) && $questData['triggerValue'] === $triggerValue) { + /** + * 处理节点 + */ + private function processNode(string $questId, string $nodeId, array $nodeData): void { + switch ($nodeData['type']) { + case 'dialogue': + $this->dispatcher->dispatch(new Event('StartDialogueEvent', [ + 'dialogue' => [ + 'root' => [ + 'text' => $nodeData['text'], + 'options' => $nodeData['options'] + ] + ], + 'questId' => $questId // 添加任务ID以便对话系统处理选择 + ])); + break; + case 'objective': + // 目标节点,等待相应事件触发 + break; + case 'branch': + // 分支节点,可能需要特殊处理 + break; + } + } - // 检查是否已完成或已接取 - if (!$player->isQuestCompleted($questId) && !isset($player->getActiveQuests()[$questId])) { - // 触发对话 - if (!empty($questData['dialogue'])) { - $this->dispatcher->dispatch(new Event('StartDialogueEvent', ['dialogue' => $questData['dialogue']])); - } else { - // 无对话直接接取? 或者弹窗提示 - // 简单起见,无对话也强制开始(可能会有默认提示) - $this->startQuest($questId); - } + /** + * 检查击杀类任务 + */ + private function checkKillQuests(array $payload): void { + $killedEnemyId = $payload['enemyId'] ?? ''; + $player = $this->stateManager->getPlayer(); + + // 遍历所有进行中的任务 + foreach ($player->getActiveQuestNodes() as $questId => $currentNode) { + $questData = $this->questRepository->find($questId); + if (!$questData) { + continue; + } + + $nodeData = $questData['nodes'][$currentNode] ?? null; + if (!$nodeData || $nodeData['type'] !== 'objective') { + continue; + } + + // 检查是否为击杀目标 + if (isset($nodeData['goal']['type']) && $nodeData['goal']['type'] === 'kill' && + isset($nodeData['goal']['entityId']) && $nodeData['goal']['entityId'] === $killedEnemyId) { + + // 更新任务进度 + $this->dispatcher->dispatch(new Event('QuestProgressEvent', [ + 'questId' => $questId + ])); + } + } + } + + /** + * 检查等级类任务 + */ + private function checkLevelQuests(array $payload): void { + $player = $this->stateManager->getPlayer(); + + // 遍历所有进行中的任务 + foreach ($player->getActiveQuestNodes() as $questId => $currentNode) { + $questData = $this->questRepository->find($questId); + if (!$questData) { + continue; + } + + $nodeData = $questData['nodes'][$currentNode] ?? null; + if (!$nodeData || $nodeData['type'] !== 'objective') { + continue; + } + + // 检查是否为等级目标 + if (isset($nodeData['goal']['type']) && $nodeData['goal']['type'] === 'level') { + if ($this->evaluator->evaluateObjective($player, $nodeData['goal'])) { + $this->completeObjective($questId, $nodeData); } } } } + + /** + * 初始化任务 + */ + private function initializeQuests(): void { + // 初始化逻辑 + } } \ No newline at end of file diff --git a/src/System/StateManager.php b/src/System/StateManager.php index e6d27cd..e68de62 100644 --- a/src/System/StateManager.php +++ b/src/System/StateManager.php @@ -107,7 +107,8 @@ class StateManager { public function getCurrentTile(): MapTile { if (!isset($this->currentTile)) { - throw new \RuntimeException("Current MapTile not set in StateManager."); + $this->currentTile = $this->mapRepository->createTile('TOWN_01'); +// throw new \RuntimeException("Current MapTile not set in StateManager."); } return $this->currentTile; } diff --git a/tests/test_dialogue_tree.php b/tests/test_dialogue_tree.php new file mode 100644 index 0000000..0fdd4f1 --- /dev/null +++ b/tests/test_dialogue_tree.php @@ -0,0 +1,77 @@ + new QuestionHelper()]); + +$container = new ServiceContainer($input, $output, $helperSet); +$dispatcher = $container->registerServices(); +$stateManager = $container->getStateManager(); + +// 创建测试玩家 +$player = new \Game\Model\Player("HanLi", 100, 10, 5); +$player->gainGold(50); // 给玩家一些初始金币 +$stateManager->setPlayer($player); +$stateManager->setCurrentTileId('TOWN_01'); + +echo "=== 《凡人修仙传》任务对话树测试 ===\n\n"; + +// 获取任务对话数据 +$questRepo = $container->getQuestRepository(); +$questData = $questRepo->find('QUEST_001'); + +echo "1. 任务对话数据测试:\n"; +echo " - 任务: {$questData['name']}\n"; +echo " - 对话结构类型: " . (isset($questData['dialogue']['root']) ? '对话树格式' : '简单格式') . "\n"; + +if (isset($questData['dialogue']['root'])) { + echo " - 根节点文本: " . substr($questData['dialogue']['root']['text'], 0, 30) . "...\n"; + echo " - 选项数量: " . count($questData['dialogue']['root']['options']) . "\n"; + + foreach ($questData['dialogue']['root']['options'] as $index => $option) { + echo " - 选项 {$index}: {$option['text']} -> {$option['next']}\n"; + } +} + +// 检查DialogueService +echo "\n2. 对话服务测试:\n"; +$dialogueService = new \Game\System\DialogueService($dispatcher, $stateManager, $input, $output, $helperSet); +echo " - DialogueService 创建成功\n"; + +// 检查对话树是否能正确启动 +echo "\n3. 对话树启动测试:\n"; +try { + $dialogueService->startDialogue($questData['dialogue']); + echo " - 对话树启动成功\n"; + echo " - 当前节点: {$dialogueService->currentNodeId}\n"; +} catch (Exception $e) { + echo " - 对话树启动失败: " . $e->getMessage() . "\n"; +} + +// 测试对话处理 +echo "\n4. 对话处理测试:\n"; +$initialActiveQuests = count($player->getActiveQuests()); +echo " - 初始进行中任务数: {$initialActiveQuests}\n"; + +// 模拟选择接受任务 +$dispatcher->dispatch(new Event('StartDialogueEvent', ['dialogue' => $questData['dialogue']])); + +// 这里我们直接测试任务接受事件 +$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'QUEST_001'])); + +$finalActiveQuests = count($player->getActiveQuests()); +echo " - 最终进行中任务数: {$finalActiveQuests}\n"; +echo " - 任务接受成功: " . ($finalActiveQuests > $initialActiveQuests ? '是' : '否') . "\n"; + +echo "\n输出消息:\n"; +echo $output->fetch() . "\n"; + +echo "=== 对话树测试完成 ===\n"; \ No newline at end of file diff --git a/tests/test_map_display.php b/tests/test_map_display.php index 3c01e4a..847e564 100644 --- a/tests/test_map_display.php +++ b/tests/test_map_display.php @@ -26,8 +26,8 @@ echo "=== 《凡人修仙传》第一阶段地图显示测试 ===\n\n"; // 测试地图显示 $mapRepo = $container->getMapRepository(); -$stateManager->setCurrentTileId('VILLAGE_01'); -$dispatcher->dispatch(new Event('MapExploreRequest')); +$stateManager->setCurrentTileId('CAVE_01'); +$dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => 'MODOCTOR'])); dd(2); // 手动调用UIService显示地图信息 $uiService = new \Game\System\UIService($output, $stateManager, $container->getQuestRepository()); diff --git a/tests/test_new_quest_core.php b/tests/test_new_quest_core.php new file mode 100644 index 0000000..7087f70 --- /dev/null +++ b/tests/test_new_quest_core.php @@ -0,0 +1,129 @@ + new QuestionHelper()]); + +$container = new ServiceContainer($input, $output, $helperSet); +$dispatcher = $container->registerServices(); +$stateManager = $container->getStateManager(); + +// 创建测试玩家 +$player = new \Game\Model\Player("HanLi", 100, 10, 5); +$player->setLevel(5); // 设置等级为5,满足所有任务条件 +$player->gainGold(50); // 给玩家一些初始金币 +$stateManager->setPlayer($player); +$stateManager->setCurrentTileId('TOWN_01'); + +echo "=== 《凡人修仙传》新任务系统核心功能测试 ===\n\n"; + +// 测试1: 检查任务数据结构 +echo "1. 任务数据结构测试:\n"; +$questRepo = $container->getQuestRepository(); +$questData = $questRepo->find('QUEST_001'); + +if (isset($questData['nodes'])) { + echo " - 任务节点结构: ✅\n"; + echo " - 节点数量: " . count($questData['nodes']) . "\n"; + foreach ($questData['nodes'] as $nodeId => $node) { + echo " - 节点 {$nodeId}: 类型={$node['type']}\n"; + } +} else { + echo " - 任务节点结构: ❌\n"; +} + +// 测试2: 检查ConditionEngine +echo "\n2. 条件引擎测试:\n"; +$conditionEngine = new \Game\System\ConditionEngine(); +$conditions = $questData['nodes']['START']['preConditions'] ?? []; +$playerSatisfies = $conditionEngine->checkConditions($player, $conditions); +echo " - 玩家满足前置条件: " . ($playerSatisfies ? '✅' : '❌') . "\n"; + +// 测试3: 检查Evaluator +echo "\n3. 目标评估器测试:\n"; +$evaluator = new \Game\System\Evaluator(); +$objectiveNode = $questData['nodes']['ACCEPT']; +$objectiveResult = $evaluator->evaluateObjective($player, $objectiveNode['goal']); +echo " - 目标评估结果: " . ($objectiveResult ? '完成' : '未完成') . "\n"; + +// 测试4: 检查ActionExecutor +echo "\n4. 动作执行器测试:\n"; +$actionExecutor = new \Game\System\ActionExecutor($dispatcher); +$initialGold = $player->getGold(); +$initialXp = $player->getCurrentXp(); + +$rewardNode = $questData['nodes']['REWARD']; +if (isset($rewardNode['actions'])) { + $actionExecutor->executeActions($player, $rewardNode['actions']); + $finalGold = $player->getGold(); + $finalXp = $player->getCurrentXp(); + + echo " - 金币变化: {$initialGold} -> {$finalGold}\n"; + echo " - 经验变化: {$initialXp} -> {$finalXp}\n"; + echo " - 动作执行: ✅\n"; +} + +// 测试5: 任务接受流程 +echo "\n5. 任务接受流程测试:\n"; +$initialActiveNodes = count($player->getActiveQuestNodes()); +echo " - 初始活跃节点: {$initialActiveNodes}\n"; + +$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'QUEST_001'])); +$currentNodes = $player->getActiveQuestNodes(); +echo " - 接受后活跃节点: " . count($currentNodes) . "\n"; +if (isset($currentNodes['QUEST_001'])) { + echo " - QUEST_001当前节点: {$currentNodes['QUEST_001']}\n"; +} + +// 测试6: 任务完成流程 +echo "\n6. 任务完成流程测试:\n"; +// 将任务节点更新到目标节点 +$player->updateQuestNode('QUEST_001', 'ACCEPT'); + +// 模拟目标完成(这里我们直接跳过实际的收集过程) +$dispatcher->dispatch(new Event('QuestProgressEvent', [ + 'questId' => 'QUEST_001' +])); + +$afterProgressNodes = $player->getActiveQuestNodes(); +echo " - 完成目标后节点: " . ($afterProgressNodes['QUEST_001'] ?? '未找到') . "\n"; + +// 检查是否已完成 +$completedQuests = $player->getCompletedQuests(); +echo " - 已完成任务数: " . count($completedQuests) . "\n"; +if (in_array('QUEST_001', $completedQuests)) { + echo " - QUEST_001已完成: ✅\n"; +} else { + echo " - QUEST_001已完成: ❌\n"; +} + +// 测试7: 对话选择功能 +echo "\n7. 对话选择功能测试:\n"; +// 重新接受一个任务 +$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'QUEST_002'])); +$currentNodes = $player->getActiveQuestNodes(); +if (isset($currentNodes['QUEST_002'])) { + echo " - QUEST_002接受成功,当前节点: {$currentNodes['QUEST_002']}\n"; + + // 模拟对话选择 + $dispatcher->dispatch(new Event('DialogueChoice', [ + 'choiceIndex' => 1, // 选择"太危险了,我不去。" + 'questId' => 'QUEST_002' + ])); + + $afterChoiceNodes = $player->getActiveQuestNodes(); + echo " - 选择后节点: " . ($afterChoiceNodes['QUEST_002'] ?? '未找到') . "\n"; +} + +echo "\n输出消息:\n"; +echo $output->fetch() . "\n"; + +echo "=== 核心功能测试完成 ===\n"; \ No newline at end of file diff --git a/tests/test_new_quest_system.php b/tests/test_new_quest_system.php new file mode 100644 index 0000000..bc682a0 --- /dev/null +++ b/tests/test_new_quest_system.php @@ -0,0 +1,106 @@ + new QuestionHelper()]); + +$container = new ServiceContainer($input, $output, $helperSet); +$dispatcher = $container->registerServices(); +$stateManager = $container->getStateManager(); + +// 创建测试玩家 +$player = new \Game\Model\Player("HanLi", 100, 10, 5); +$player->setLevel(5); // 设置等级为5,满足所有任务条件 +$player->gainGold(50); // 给玩家一些初始金币 +$stateManager->setPlayer($player); +$stateManager->setCurrentTileId('TOWN_01'); + +echo "=== 《凡人修仙传》新任务系统测试 ===\n\n"; + +// 测试1: 检查任务数据结构 +echo "1. 任务数据结构测试:\n"; +$questRepo = $container->getQuestRepository(); +$questData = $questRepo->find('QUEST_001'); + +if (isset($questData['nodes'])) { + echo " - 任务节点结构: ✅\n"; + echo " - 节点数量: " . count($questData['nodes']) . "\n"; + foreach ($questData['nodes'] as $nodeId => $node) { + echo " - 节点 {$nodeId}: 类型={$node['type']}\n"; + } +} else { + echo " - 任务节点结构: ❌\n"; +} + +// 测试2: 检查ConditionEngine +echo "\n2. 条件引擎测试:\n"; +$conditionEngine = new \Game\System\ConditionEngine(); +$conditions = $questData['nodes']['START']['preConditions'] ?? []; +$playerSatisfies = $conditionEngine->checkConditions($player, $conditions); +echo " - 玩家满足前置条件: " . ($playerSatisfies ? '✅' : '❌') . "\n"; + +// 测试3: 检查Evaluator +echo "\n3. 目标评估器测试:\n"; +$evaluator = new \Game\System\Evaluator(); +$objectiveNode = $questData['nodes']['ACCEPT']; +$objectiveResult = $evaluator->evaluateObjective($player, $objectiveNode['goal']); +echo " - 目标评估结果: " . ($objectiveResult ? '完成' : '未完成') . "\n"; + +// 测试4: 检查ActionExecutor +echo "\n4. 动作执行器测试:\n"; +$actionExecutor = new \Game\System\ActionExecutor($dispatcher); +$initialGold = $player->getGold(); +$initialXp = $player->getCurrentXp(); + +$rewardNode = $questData['nodes']['REWARD']; +if (isset($rewardNode['actions'])) { + $actionExecutor->executeActions($player, $rewardNode['actions']); + $finalGold = $player->getGold(); + $finalXp = $player->getCurrentXp(); + + echo " - 金币变化: {$initialGold} -> {$finalGold}\n"; + echo " - 经验变化: {$initialXp} -> {$finalXp}\n"; + echo " - 动作执行: ✅\n"; +} + +// 测试5: 任务服务交互 +echo "\n5. 任务服务交互测试:\n"; + +// 测试接受任务 +$initialActiveNodes = count($player->getActiveQuestNodes()); +$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'QUEST_001'])); +$finalActiveNodes = count($player->getActiveQuestNodes()); + +echo " - 任务节点状态: {$initialActiveNodes} -> {$finalActiveNodes}\n"; +if ($finalActiveNodes > $initialActiveNodes) { + echo " - 任务接受: ✅\n"; +} else { + echo " - 任务接受: ❌\n"; +} + +// 测试6: 检查完成状态 +echo "\n6. 任务完成状态测试:\n"; +$currentNode = $player->getQuestNode('QUEST_001'); +echo " - 当前节点: {$currentNode}\n"; + +// 模拟完成目标 +$dispatcher->dispatch(new Event('QuestProgressEvent', [ + 'questId' => 'QUEST_001' +])); + +// 检查是否跳转到奖励节点 +$updatedNode = $player->getQuestNode('QUEST_001'); +echo " - 更新后节点: {$updatedNode}\n"; + +echo "\n输出消息:\n"; +echo $output->fetch() . "\n"; + +echo "=== 新任务系统测试完成 ===\n"; \ No newline at end of file diff --git a/tests/test_new_quest_system_full.php b/tests/test_new_quest_system_full.php new file mode 100644 index 0000000..72d2852 --- /dev/null +++ b/tests/test_new_quest_system_full.php @@ -0,0 +1,93 @@ + new QuestionHelper()]); + +$container = new ServiceContainer($input, $output, $helperSet); +$dispatcher = $container->registerServices(); +$stateManager = $container->getStateManager(); + +// 创建测试玩家 +$player = new \Game\Model\Player("HanLi", 100, 10, 5); +$player->setLevel(5); // 设置等级为5,满足所有任务条件 +$player->gainGold(50); // 给玩家一些初始金币 +$stateManager->setPlayer($player); +$stateManager->setCurrentTileId('TOWN_01'); + +echo "=== 《凡人修仙传》新任务系统完整流程测试 ===\n\n"; + +// 检查初始状态 +echo "1. 初始状态:\n"; +echo " - 活跃任务节点数: " . count($player->getActiveQuestNodes()) . "\n"; +echo " - 金币: " . $player->getGold() . "\n"; +echo " - 经验: " . $player->getCurrentXp() . "\n"; + +// 接受任务 +echo "\n2. 接受任务:\n"; +$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'QUEST_001'])); + +$initialNodes = $player->getActiveQuestNodes(); +echo " - 接受后活跃节点: " . count($initialNodes) . "\n"; +if (isset($initialNodes['QUEST_001'])) { + echo " - 任务QUEST_001节点: " . $initialNodes['QUEST_001'] . "\n"; +} + +// 模拟完成目标 +echo "\n3. 完成任务目标:\n"; +// 更新任务节点到目标节点 +$player->updateQuestNode('QUEST_001', 'ACCEPT'); + +// 模拟目标完成事件 +$dispatcher->dispatch(new Event('QuestProgressEvent', [ + 'questId' => 'QUEST_001' +])); + +$afterProgressNodes = $player->getActiveQuestNodes(); +echo " - 完成目标后节点: " . ($afterProgressNodes['QUEST_001'] ?? '未找到') . "\n"; + +// 检查奖励是否发放 +echo "\n4. 奖励发放检查:\n"; +echo " - 金币: " . $player->getGold() . "\n"; +echo " - 经验: " . $player->getCurrentXp() . "\n"; + +// 检查任务是否完成 +echo "\n5. 任务完成检查:\n"; +$completedQuests = $player->getCompletedQuests(); +echo " - 已完成任务数: " . count($completedQuests) . "\n"; +if (in_array('QUEST_001', $completedQuests)) { + echo " - QUEST_001 已完成: ✅\n"; +} else { + echo " - QUEST_001 已完成: ❌\n"; +} + +// 测试分支任务 +echo "\n6. 分支任务测试 (QUEST_004):\n"; +$dispatcher->dispatch(new Event('QuestAcceptRequest', ['questId' => 'QUEST_004'])); + +$quest4Nodes = $player->getActiveQuestNodes(); +if (isset($quest4Nodes['QUEST_004'])) { + echo " - 任务QUEST_004节点: " . $quest4Nodes['QUEST_004'] . "\n"; + + // 模拟选择接受路径 + $dispatcher->dispatch(new Event('DialogueChoice', [ + 'choiceIndex' => 0, // 选择"请墨大夫传授功法" + 'questId' => 'QUEST_004' + ])); + + $afterChoiceNodes = $player->getActiveQuestNodes(); + echo " - 选择后节点: " . ($afterChoiceNodes['QUEST_004'] ?? '未找到') . "\n"; +} + +echo "\n输出消息:\n"; +echo $output->fetch() . "\n"; + +echo "=== 完整流程测试完成 ===\n"; \ No newline at end of file diff --git a/tests/test_npc_quest_assoc.php b/tests/test_npc_quest_assoc.php new file mode 100644 index 0000000..2555876 --- /dev/null +++ b/tests/test_npc_quest_assoc.php @@ -0,0 +1,93 @@ + new QuestionHelper()]); + +$container = new ServiceContainer($input, $output, $helperSet); +$dispatcher = $container->registerServices(); +$stateManager = $container->getStateManager(); + +// 创建测试玩家 +$player = new \Game\Model\Player("HanLi", 100, 10, 5); +$player->setLevel(5); // 设置等级为5,满足所有任务条件 +$player->gainGold(50); // 给玩家一些初始金币 +$stateManager->setPlayer($player); +$stateManager->setCurrentTileId('TOWN_01'); + +echo "=== NPC与任务关联测试 ===\n\n"; + +// 测试1: 检查任务与NPC的关联 +echo "1. 检查任务与NPC关联:\n"; +$questRepo = $container->getQuestRepository(); +$npcRepo = $container->getNpcRepository(); + +// 获取VILLAGER_1相关的任务 +$villagerQuests = $questRepo->getQuestsByNpc('VILLAGER_1'); +echo " - VILLAGER_1关联的任务: " . implode(', ', $villagerQuests) . "\n"; + +// 获取MODOCTOR相关的任务 +$doctorQuests = $questRepo->getQuestsByNpc('MODOCTOR'); +echo " - MODOCTOR关联的任务: " . implode(', ', $doctorQuests) . "\n"; + +// 检查任务数据中的NPC ID +$quest1 = $questRepo->find('QUEST_001'); +if (isset($quest1['npcId'])) { + echo " - QUEST_001的NPC: {$quest1['npcId']}\n"; +} else { + echo " - QUEST_001没有NPC关联\n"; +} + +$quest3 = $questRepo->find('QUEST_003'); +if (isset($quest3['npcId'])) { + echo " - QUEST_003的NPC: {$quest3['npcId']}\n"; +} else { + echo " - QUEST_003没有NPC关联\n"; +} + +// 测试2: 检查NPC是否存在 +echo "\n2. 检查NPC数据:\n"; +$villager = $npcRepo->createNPC('VILLAGER_1'); +if ($villager) { + echo " - VILLAGER_1存在: {$villager->getName()}\n"; +} else { + echo " - VILLAGER_1不存在\n"; +} + +$doctor = $npcRepo->createNPC('MODOCTOR'); +if ($doctor) { + echo " - MODOCTOR存在: {$doctor->getName()}\n"; +} else { + echo " - MODOCTOR不存在\n"; +} + +// 测试3: 模拟NPC交互 +echo "\n3. 模拟NPC交互测试:\n"; +$initialActiveQuests = count($player->getActiveQuests()); +echo " - 初始活跃任务数: {$initialActiveQuests}\n"; + +// 模拟与VILLAGER_1的交互 +$dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => 'VILLAGER_1'])); + +$afterInteractionQuests = count($player->getActiveQuests()); +echo " - 与VILLAGER_1交互后任务数: {$afterInteractionQuests}\n"; + +// 检查是否接受了任务 +$activeQuestNodes = $player->getActiveQuestNodes(); +echo " - 活跃任务节点: " . count($activeQuestNodes) . "\n"; +foreach ($activeQuestNodes as $questId => $nodeId) { + echo " - 任务{$questId}节点: {$nodeId}\n"; +} + +echo "\n输出消息:\n"; +echo $output->fetch() . "\n"; + +echo "=== 关联测试完成 ===\n"; \ No newline at end of file diff --git a/tests/test_quest_interaction.php b/tests/test_quest_interaction.php new file mode 100644 index 0000000..29f005e --- /dev/null +++ b/tests/test_quest_interaction.php @@ -0,0 +1,87 @@ + new QuestionHelper()]); + +$container = new ServiceContainer($input, $output, $helperSet); +$dispatcher = $container->registerServices(); +$stateManager = $container->getStateManager(); + +// 创建测试玩家 +$player = new \Game\Model\Player("HanLi", 100, 10, 5); +$player->gainGold(50); // 给玩家一些初始金币 +$stateManager->setPlayer($player); +$stateManager->setCurrentTileId('TOWN_01'); + +echo "=== 《凡人修仙传》任务系统交互测试 ===\n\n"; + +// 测试任务接受 +echo "1. 任务接受测试:\n"; +$questService = new \Game\System\QuestService($dispatcher, $stateManager, $container->getQuestRepository()); + +// 手动添加一个任务到玩家 +$questData = $container->getQuestRepository()->find('QUEST_001'); +$quest = new Quest( + $questData['id'], + $questData['name'], + $questData['description'], + $questData['type'], + $questData['target'], + $questData['rewards'], + $questData['triggerType'] ?? null, + $questData['triggerValue'] ?? null, + $questData['dialogue'] ?? [] +); + +$player->addActiveQuest($quest); +echo " - 添加任务: {$quest->getTitle()}\n"; +echo " - 任务类型: {$quest->getType()}\n"; +echo " - 目标: " . ($quest->getTarget()['count'] ?? '未知') . "\n\n"; + +// 测试任务进度更新 +echo "2. 任务进度更新测试:\n"; +$player->updateQuestProgress('QUEST_001', 5); +$activeQuests = $player->getActiveQuests(); +$currentQuest = $activeQuests['QUEST_001']; +echo " - 当前进度: {$currentQuest->getCurrentCount()}/{$quest->getTarget()['count']}\n\n"; + +// 测试任务完成 +echo "3. 任务完成测试:\n"; +// 完成任务 +for ($i = 0; $i < 5; $i++) { + $player->updateQuestProgress('QUEST_001', 1); +} +$currentQuest = $player->getActiveQuests()['QUEST_001']; +echo " - 任务是否完成: " . ($currentQuest->isCompleted() ? '是' : '否') . "\n"; +echo " - 最终进度: {$currentQuest->getCurrentCount()}/{$quest->getTarget()['count']}\n\n"; + +// 测试任务交付 +echo "4. 任务交付测试:\n"; +$initialGold = $player->getGold(); +$initialXp = $player->getCurrentXp(); +echo " - 交付前金币: {$initialGold}, 经验: {$initialXp}\n"; + +// 触发任务交付 +$dispatcher->dispatch(new Event('QuestTurnInConfirm', ['questId' => 'QUEST_001'])); + +$finalGold = $player->getGold(); +$finalXp = $player->getCurrentXp(); +echo " - 交付后金币: {$finalGold}, 经验: {$finalXp}\n"; +echo " - 金币增加: " . ($finalGold - $initialGold) . "\n"; +echo " - 经验增加: " . ($finalXp - $initialXp) . "\n"; +echo " - 是否在完成列表: " . (in_array('QUEST_001', $player->getCompletedQuests()) ? '是' : '否') . "\n\n"; + +echo "输出消息:\n"; +echo $output->fetch() . "\n"; + +echo "=== 任务系统交互测试完成 ===\n"; \ No newline at end of file