This commit is contained in:
hantao 2025-12-24 21:10:08 +08:00
parent 2a8b44765c
commit daef6be5c7
24 changed files with 1319 additions and 462 deletions

View File

@ -1,6 +1,6 @@
{ {
"POTION_01": { "POTION_01": {
"id": 1, "id": "POTION_01",
"name": "疗伤药", "name": "疗伤药",
"type": "potion", "type": "potion",
"description": "普通的疗伤药,可以恢复少量生命值。", "description": "普通的疗伤药,可以恢复少量生命值。",
@ -10,7 +10,7 @@
} }
}, },
"POTION_02": { "POTION_02": {
"id": 2, "id": "POTION_02",
"name": "回气丹", "name": "回气丹",
"type": "potion", "type": "potion",
"description": "可以恢复一定量的真气。", "description": "可以恢复一定量的真气。",
@ -20,7 +20,7 @@
} }
}, },
"POTION_03": { "POTION_03": {
"id": 3, "id": "POTION_03",
"name": "长春丹", "name": "长春丹",
"type": "potion", "type": "potion",
"description": "传说中的长春丹,据说有延年益寿之效。", "description": "传说中的长春丹,据说有延年益寿之效。",
@ -31,7 +31,7 @@
} }
}, },
"WEAPON_01": { "WEAPON_01": {
"id": 4, "id": "WEAPON_01",
"name": "精钢剑", "name": "精钢剑",
"type": "weapon", "type": "weapon",
"description": "一柄精钢打造的长剑,锋利坚韧。", "description": "一柄精钢打造的长剑,锋利坚韧。",
@ -42,7 +42,7 @@
} }
}, },
"ARMOR_01": { "ARMOR_01": {
"id": 5, "id": "ARMOR_01",
"name": "铁布衫", "name": "铁布衫",
"type": "armor", "type": "armor",
"description": "一件铁布衫,可以有效防护。", "description": "一件铁布衫,可以有效防护。",
@ -53,21 +53,21 @@
} }
}, },
"CAOYAO": { "CAOYAO": {
"id": 6, "id": "CAOYAO",
"name": "灵草药", "name": "灵草药",
"type": "material", "type": "material",
"description": "一株灵草药,可用于炼制丹药。", "description": "一株灵草药,可用于炼制丹药。",
"value": 50 "value": 50
}, },
"ZHENGBI": { "ZHENGBI": {
"id": 7, "id": "CAOYAO",
"name": "银两", "name": "银两",
"type": "currency", "type": "currency",
"description": "七玄门通行的货币,可用于交易。", "description": "七玄门通行的货币,可用于交易。",
"value": 1 "value": 1
}, },
"ZHUTIANPING": { "ZHUTIANPING": {
"id": 8, "id": "ZHUTIANPING",
"name": "掌天瓶", "name": "掌天瓶",
"type": "treasure", "type": "treasure",
"description": "传说中的至宝掌天瓶,具有神秘力量。", "description": "传说中的至宝掌天瓶,具有神秘力量。",

View File

@ -7,7 +7,13 @@
"E": "VILLAGE_01", "E": "VILLAGE_01",
"W": "FOREST_01" "W": "FOREST_01"
}, },
"encounterPool": ["GOBLIN", "WOLF"], "encounterPool": [{
"id": "GOBLIN",
"weight": 60
}, {
"id": "WOLF",
"weight": 40
}],
"encounterChance": 0.5, "encounterChance": 0.5,
"npcIds": ["VILLAGER_1"], "npcIds": ["VILLAGER_1"],
"lootIds": ["POTION_01"] "lootIds": ["POTION_01"]
@ -20,7 +26,15 @@
"W": "TOWN_01", "W": "TOWN_01",
"N": "CAVE_01" "N": "CAVE_01"
}, },
"encounterPool": ["WOLF", "BEAR"], "encounterPool": [
{
"id": "WOLF",
"weight": 60
},
{
"id": "BEAR",
"weight": 40
}],
"encounterChance": 0.5, "encounterChance": 0.5,
"npcIds": [], "npcIds": [],
"lootIds": ["HERB_BASIC"] "lootIds": ["HERB_BASIC"]
@ -32,7 +46,12 @@
"connections": { "connections": {
"E": "TOWN_01" "E": "TOWN_01"
}, },
"encounterPool": ["STRANGE_CULTIVATOR"], "encounterPool": [
{
"id": "STRANGE_CULTIVATOR",
"weight": 60
}
],
"encounterChance": 0.5, "encounterChance": 0.5,
"npcIds": ["BLACKSMITH"], "npcIds": ["BLACKSMITH"],
"lootIds": ["CAOYAO"] "lootIds": ["CAOYAO"]
@ -44,7 +63,12 @@
"connections": { "connections": {
"S": "VILLAGE_01" "S": "VILLAGE_01"
}, },
"encounterPool": ["GUARD"], "encounterPool": [
{
"id": "GUARD",
"weight": 60
}
],
"encounterChance": 0.5, "encounterChance": 0.5,
"npcIds": ["MODOCTOR"], "npcIds": ["MODOCTOR"],
"lootIds": ["ZHENGBI", "ZHUTIANPING"] "lootIds": ["ZHENGBI", "ZHUTIANPING"]

View File

@ -3,88 +3,171 @@
"id": "QUEST_001", "id": "QUEST_001",
"name": "七玄门初入江湖", "name": "七玄门初入江湖",
"description": "作为新入门的弟子,需要熟悉门派环境,完成基础任务。", "description": "作为新入门的弟子,需要熟悉门派环境,完成基础任务。",
"type": "collect", "npcId": "VILLAGER_1",
"target": { "nodes": {
"entityId": "ZHENGBI", "START": {
"count": 10 "type": "dialogue",
}, "text": "师弟门中最近需要一些银两你能帮我去后山收集10两银子吗",
"rewards": { "options": [
"xp": 50, { "text": "好,我去收集银两。", "next_node": "ACCEPT" },
"gold": 20, { "text": "现在没时间,下次再说。", "next_node": "END" }
"itemId": "POTION_01" ],
}, "preConditions": [
"triggerType": "NPC", { "type": "level", "value": 1 }
"triggerValue": "VILLAGER_1", ]
"dialogue": { },
"start": "师弟门中最近需要一些银两你能帮我去后山收集10两银子吗", "ACCEPT": {
"progress": "还差一些银两,继续努力。", "type": "objective",
"complete": "很好,师弟表现不错,这是你的奖励。" "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": { "QUEST_002": {
"id": "QUEST_002", "id": "QUEST_002",
"name": "后山除害", "name": "后山除害",
"description": "后山出现了一些野兽,威胁门派安全,需要清除。", "description": "后山出现了一些野狼,威胁门派安全,需要清除。",
"type": "kill", "npcId": "VILLAGER_1",
"target": { "nodes": {
"entityId": "WOLF", "START": {
"count": 3 "type": "dialogue",
}, "text": "师弟,后山有三只野狼威胁弟子安全,你能帮我们解决吗?",
"rewards": { "options": [
"xp": 100, { "text": "野狼?我可以帮忙处理。", "next_node": "ACCEPT" },
"gold": 30, { "text": "太危险了,我不去。", "next_node": "END" }
"itemId": "WEAPON_01" ],
}, "preConditions": [
"triggerType": "NPC", { "type": "level", "value": 2 }
"triggerValue": "VILLAGER_1", ]
"dialogue": { },
"start": "师弟,后山有三只野狼威胁弟子安全,你能帮我们解决吗?", "ACCEPT": {
"progress": "野狼还未全部清除,小心应对。", "type": "objective",
"complete": "太好了,后山安全了,这是给你的奖励。" "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": { "QUEST_003": {
"id": "QUEST_003", "id": "QUEST_003",
"name": "意外救墨大夫", "name": "意外救墨大夫",
"description": "在地牢中发现被困的神秘人物,需要帮助他脱困。", "description": "在地牢中发现被困的神秘人物,需要帮助他脱困。",
"type": "explore", "npcId": "MODOCTOR",
"target": { "nodes": {
"entityId": "CAVE_01", "START": {
"count": 1 "type": "dialogue",
}, "text": "小友,老夫被困在此地多日,若能助我脱困,必有重谢。",
"rewards": { "options": [
"xp": 200, { "text": "墨大夫,我来救你。", "next_node": "ACCEPT" },
"gold": 100, { "text": "我还有事,改日再来。", "next_node": "END" }
"itemId": "POTION_03" ],
}, "preConditions": [
"triggerType": "SYSTEM", { "type": "level", "value": 3 }
"triggerValue": "EXPLORE_CAVE", ]
"dialogue": { },
"start": "小友,老夫被困在此地多日,若能助我脱困,必有重谢。", "ACCEPT": {
"progress": "需要找到方法帮助墨大夫。", "type": "objective",
"complete": "多谢小友相助,这是老夫的一点心意。" "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": { "QUEST_004": {
"id": "QUEST_004", "id": "QUEST_004",
"name": "发现长春功", "name": "发现长春功",
"description": "从墨大夫处学习到神秘功法,需要修炼至小成。", "description": "从墨大夫处学习到神秘功法,需要修炼至小成。",
"type": "level", "npcId": "MODOCTOR",
"target": { "nodes": {
"entityId": "PLAYER", "START": {
"count": 2 "type": "dialogue",
}, "text": "小友,老夫观你骨骼精奇,可传授你长春功,但需谨慎修炼。",
"rewards": { "options": [
"xp": 300, { "text": "请墨大夫传授功法。", "next_node": "PATH_ACCEPT" },
"gold": 200, { "text": "我现在还不想学。", "next_node": "PATH_DECLINE" }
"itemId": "ZHUTIANPING" ],
}, "preConditions": [
"triggerType": "NPC", { "type": "completed_quest", "questId": "QUEST_003" }
"triggerValue": "MODOCTOR", ]
"dialogue": { },
"start": "小友,老夫观你骨骼精奇,可传授你长春功,但需谨慎修炼。", "PATH_ACCEPT": {
"progress": "修炼需要时间,循序渐进。", "type": "objective",
"complete": "恭喜小友,长春功已初见成效,这是掌天瓶,望善用之。" "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" }
]
}
} }
} }
} }

View File

@ -1,174 +1,23 @@
{ {
"player": { "player": {
"name": "AutoSaveTest", "name": "hant",
"health": 160, "health": 58,
"maxHealth": 160, "maxHealth": 100,
"base_attack": 22, "base_attack": 15,
"base_defense": 13, "base_defense": 5,
"level": 5, "level": 1,
"currentXp": 175, "currentXp": 40,
"gold": 241, "gold": 21,
"inventory": [ "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"
}
],
"equipment": { "equipment": {
"weapon": { "weapon": null,
"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": { "helmet": null
"id": 4,
"name": "\u5e03\u7532\u5934\u76d4",
"type": "armor",
"description": "\u63d0\u4f9b\u5c11\u91cf\u9632\u5fa1\u3002",
"value": 30,
"effects": [],
"stats": [],
"slot": "helmet"
}
}, },
"activeQuests": [], "activeQuests": [],
"completedQuests": [ "completedQuests": []
"KILL_GOBLIN"
]
}, },
"world": { "world": {
"currentTileId": "FOREST_01" "currentTileId": "CAVE_01"
} }
} }

View File

@ -18,11 +18,11 @@ class MapRepository
'id' => $id, 'id' => $id,
'name' => $tileData['name'], 'name' => $tileData['name'],
'description' => $tileData['description'], 'description' => $tileData['description'],
'encounter_chance' => $tileData['encounter_chance'] ?? 0, 'encounter_chance' => $tileData['encounterChance'] ?? 0,
'connections' => $tileData['connections'] ?? [], 'connections' => $tileData['connections'] ?? [],
'encounter_pool' => $tileData['encounter_pool'] ?? [], 'encounter_pool' => $tileData['encounterPool'] ?? [],
'npc_ids' => $tileData['npc_ids'] ?? [], 'npc_ids' => $tileData['npcIds'] ?? [],
'loot_ids' => $tileData['loot_ids'] ?? [] 'loot_ids' => $tileData['lootIds'] ?? []
]; ];
} }

View File

@ -21,14 +21,17 @@ class QuestRepository implements RepositoryInterface {
*/ */
private function buildNpcQuestIndex(): void { private function buildNpcQuestIndex(): void {
foreach ($this->data as $questId => $questData) { foreach ($this->data as $questId => $questData) {
// 优先使用 triggerType = NPC 的 triggerValue // 优先使用新的 npcId 字段
if (isset($questData['triggerType']) && $questData['triggerType'] === 'NPC' && isset($questData['triggerValue'])) { 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']; $npcId = $questData['triggerValue'];
$this->questsByNpc[$npcId][] = $questId; $this->questsByNpc[$npcId][] = $questId;
} }
// 兼容旧数据:检查 target_npc_id (如果是 talk 类型或者仅仅是关联) // 兼容旧数据:检查 target_npc_id
// 但如果不作为触发条件,可能不应该在这里索引?
// 保持兼容性:如果还没索引过,且有 target_npc_id
elseif (isset($questData['target_npc_id'])) { elseif (isset($questData['target_npc_id'])) {
$targetId = $questData['target_npc_id']; $targetId = $questData['target_npc_id'];
// 避免重复? // 避免重复?

View File

@ -16,6 +16,9 @@ class Player extends Character {
'helmet' => null, 'helmet' => null,
// 可根据需要添加 'ring', 'amulet' 等 // 可根据需要添加 'ring', 'amulet' 等
]; ];
// ⭐ 新增:任务节点状态
protected array $activeQuestNodes = [];
public function __construct(string $name, int $maxHealth, int $attack, int $defense,int $maxMana = 50) { public function __construct(string $name, int $maxHealth, int $attack, int $defense,int $maxMana = 50) {
// 调用父类 (Character) 的构造函数来初始化核心属性 // 调用父类 (Character) 的构造函数来初始化核心属性
parent::__construct($name, $maxHealth, $attack, $defense,$maxMana); parent::__construct($name, $maxHealth, $attack, $defense,$maxMana);
@ -126,6 +129,31 @@ class Player extends Character {
public function isQuestCompleted(string $questId): bool { public function isQuestCompleted(string $questId): bool {
return in_array($questId, $this->completedQuests); 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 实例) // ⭐ 新增:玩家背包 (存储 Item 实例)
protected array $inventory = []; protected array $inventory = [];

39
src/Model/QuestNode.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace Game\Model;
/**
* 任务节点模型
*/
class QuestNode {
public string $id;
public string $type; // dialogue, objective, branch
public string $text; // 用于dialogue类型
public array $options; // 用于dialogue类型
public array $goal; // 用于objective类型
public ?string $nextNode; // 默认跳转节点
public ?string $onComplete; // 完成后跳转节点
public array $preConditions; // 前置条件
public array $actions; // 完成后执行的动作
public function __construct(
string $id,
string $type,
string $text = '',
array $options = [],
array $goal = [],
?string $nextNode = null,
?string $onComplete = null,
array $preConditions = [],
array $actions = []
) {
$this->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;
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Game\System;
use Game\Event\EventDispatcher;
use Game\Model\Player;
/**
* 动作执行器 - 统一处理 JSON 中的指令
*/
class ActionExecutor {
private EventDispatcher $dispatcher;
public function __construct(EventDispatcher $dispatcher) {
$this->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);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Game\System;
use Game\Model\Player;
/**
* 条件引擎 - 统一判定玩家是否满足某个配置条件
*/
class ConditionEngine {
public function checkConditions(Player $player, array $conditions): bool {
foreach ($conditions as $condition) {
if (!$this->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;
}
}
}

View File

@ -24,6 +24,7 @@ class DialogueService implements EventListenerInterface {
private array $currentDialogueTree = []; private array $currentDialogueTree = [];
private string $currentNodeId = ''; private string $currentNodeId = '';
private ?string $currentQuestId = null; // ⭐ 添加任务ID
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) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
@ -37,11 +38,13 @@ class DialogueService implements EventListenerInterface {
switch ($event->getType()) { switch ($event->getType()) {
case 'StartDialogueEvent': case 'StartDialogueEvent':
$dialogue = $event->getPayload()['dialogue']; $dialogue = $event->getPayload()['dialogue'];
$this->startDialogue($dialogue); $questId = $event->getPayload()['questId'] ?? null; // ⭐ 获取任务ID
$this->startDialogue($dialogue, $questId); // ⭐ 传递任务ID
break; break;
case 'DialogueChoice': // ⭐ Listen for choice event case 'DialogueChoice': // ⭐ Listen for choice event
$choice = $event->getPayload()['choice']; $choiceIndex = $event->getPayload()['choiceIndex']; // ⭐ 修正键名
$this->handleChoice($choice); $questId = $event->getPayload()['questId'] ?? null; // ⭐ 获取任务ID
$this->handleChoice($choiceIndex, $questId); // ⭐ 传递任务ID
break; 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->currentDialogueTree = $dialogueTree;
$this->currentNodeId = 'root'; $this->currentNodeId = 'root'; // ⭐ 使用'root'作为根节点
$this->currentQuestId = $questId; // ⭐ 保存任务ID
// 切换游戏模式 // 切换游戏模式
$this->stateManager->setMode(StateManager::MODE_DIALOGUE); $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])) { if (!isset($this->currentDialogueTree[$this->currentNodeId])) {
$this->endDialogue(); $this->endDialogue();
return; return;
@ -101,7 +105,7 @@ class DialogueService implements EventListenerInterface {
// 如果没有选项,任意输入都结束对话 (或者跳转 next) // 如果没有选项,任意输入都结束对话 (或者跳转 next)
if (empty($node->options)) { if (empty($node->options)) {
$this->endDialogue(); $this->endDialogue();
return; return;
} }
if (!is_numeric($input)) { if (!is_numeric($input)) {
@ -131,11 +135,20 @@ class DialogueService implements EventListenerInterface {
$this->currentNodeId = $selectedOption['next']; $this->currentNodeId = $selectedOption['next'];
$this->displayCurrentNode(); $this->displayCurrentNode();
} }
// 3. 如果是任务对话分发选择事件到InteractionSystem
if ($questId) {
$this->dispatcher->dispatch(new Event('DialogueChoice', [
'choiceIndex' => (int)$input,
'questId' => $questId
]));
}
} }
private function endDialogue(): void { private function endDialogue(): void {
$this->currentDialogueTree = []; $this->currentDialogueTree = [];
$this->currentNodeId = ''; $this->currentNodeId = '';
$this->currentQuestId = null; // ⭐ 重置任务ID
$this->output->writeln("\n[对话结束]"); $this->output->writeln("\n[对话结束]");
// 恢复地图模式 (或者之前的模式,稍微简化处理) // 恢复地图模式 (或者之前的模式,稍微简化处理)
@ -160,4 +173,4 @@ class DialogueService implements EventListenerInterface {
break; break;
} }
} }
} }

71
src/System/Evaluator.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace Game\System;
use Game\Model\Player;
use Game\Model\QuestNode;
/**
* 目标评估器 - 使用策略模式处理不同类型的任务目标
*/
class Evaluator {
public function evaluateObjective(Player $player, array $goal): bool {
$type = $goal['type'] ?? '';
switch ($type) {
case 'kill':
return $this->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;
}
}

View File

@ -59,6 +59,12 @@ class InteractionSystem implements EventListenerInterface {
$npcId = $event->getPayload()['npcId']; $npcId = $event->getPayload()['npcId'];
$this->startInteraction($npcId); $this->startInteraction($npcId);
break; 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', [ $this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "👤 你走近了 <fg=cyan;options=bold>{$npc->getName()}</>" 'message' => "👤 你走近了 <fg=cyan;options=bold>{$npc->getName()}</>"
])); ]));
// ⭐ 直接尝试触发任务对话 // ⭐ 直接尝试触发任务对话
@ -108,7 +114,7 @@ class InteractionSystem implements EventListenerInterface {
$questData = $this->questRepository->find($questId); $questData = $this->questRepository->find($questId);
if ($questData && !empty($questData['dialogue'])) { if ($questData && !empty($questData['dialogue'])) {
// ⭐ 使用新版对话系统 // ⭐ 使用新版对话系统
$this->dialogueService->startDialogue($questData['dialogue']); $this->dialogueService->startDialogue($questData['dialogue'], $questId);
$foundQuest = true; $foundQuest = true;
break; break;
} }
@ -120,7 +126,7 @@ 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是否有商店 // ⭐ 检查NPC是否有商店

View File

@ -42,8 +42,8 @@ class LootService implements EventListenerInterface {
$data['health'], $data['health'],
$data['attack'], $data['attack'],
$data['defense'], $data['defense'],
$data['xp_reward'], $data['xp'],
$data['loot_table'] ?? [] // 传递 loot_table $data['loot'] ?? [] // 传递 loot_table
); );
} }
@ -150,20 +150,14 @@ class LootService implements EventListenerInterface {
/** /**
* 处理探索时发现的宝箱/固定物品 (保持不变) * 处理探索时发现的宝箱/固定物品 (保持不变)
*/ */
private function handleLootFound(int $lootId): void { private function handleLootFound($lootId): void {
// ...
if ($lootId === 5) {
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => "🗝️ 你打开了宝箱!"
]));
$this->giveItemToPlayer(2);
}
} }
/** /**
* 核心逻辑:创建物品实例并添加到玩家背包 (保持不变) * 核心逻辑:创建物品实例并添加到玩家背包 (保持不变)
*/ */
private function giveItemToPlayer(int $itemId): void { private function giveItemToPlayer($itemId): void {
// ... // ...
$item = $this->itemRepository->createItem($itemId); $item = $this->itemRepository->createItem($itemId);

View File

@ -120,7 +120,6 @@ class MapSystem implements EventListenerInterface {
*/ */
private function checkRandomEncounter(float $chance): bool { private function checkRandomEncounter(float $chance): bool {
$currentTile = $this->stateManager->getCurrentTile(); $currentTile = $this->stateManager->getCurrentTile();
dd($currentTile);
if (!$currentTile->encounterPool || rand(1, 100) / 100 > $chance) { if (!$currentTile->encounterPool || rand(1, 100) / 100 > $chance) {
return false; return false;
} }
@ -142,7 +141,7 @@ class MapSystem implements EventListenerInterface {
$currentWeight = 0; $currentWeight = 0;
foreach ($pool as $item) { foreach ($pool as $item) {
$currentWeight += $item['weight']; $currentWeight += $item['weight'];
if ($randValue <= $currentWeight) return $item['enemyId']; if ($randValue <= $currentWeight) return $item['id'];
} }
return null; return null;
} }

View File

@ -5,247 +5,290 @@ use Game\Database\QuestRepository;
use Game\Event\Event; use Game\Event\Event;
use Game\Event\EventListenerInterface; use Game\Event\EventListenerInterface;
use Game\Event\EventDispatcher; use Game\Event\EventDispatcher;
use Game\Model\Quest; use Game\Model\QuestNode;
use Game\Model\Player; use Game\Model\Player;
/** /**
* QuestService: 管理任务的接受、追踪和奖励发放。 * 重构后的任务服务 - 基于事件驱动和有向图节点
*/ */
class QuestService implements EventListenerInterface { class QuestService implements EventListenerInterface {
private EventDispatcher $dispatcher; private EventDispatcher $dispatcher;
private StateManager $stateManager; private StateManager $stateManager;
private QuestRepository $questRepository; // ⭐ 新增属性 private QuestRepository $questRepository;
private ConditionEngine $conditionEngine;
private Evaluator $evaluator;
private ActionExecutor $actionExecutor;
// ⭐ 修正构造函数:注入 QuestRepository public function __construct(
private $questData = []; EventDispatcher $dispatcher,
StateManager $stateManager,
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, QuestRepository $questRepository) { QuestRepository $questRepository
) {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->stateManager = $stateManager; $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 { public function handleEvent(Event $event): void {
switch ($event->getType()) { switch ($event->getType()) {
case 'GameStartEvent': case 'GameStartEvent':
$this->initializeQuests(); // 游戏开始时检查是否有初始任务 $this->initializeQuests();
break; break;
case 'BattleEndEvent': // 响应战斗结束,检查击杀目标 case 'QuestAcceptRequest':
$payload = $event->getPayload(); $this->acceptQuest($event->getPayload()['questId']);
if (isset($payload['enemyId'])) {
$this->checkKillQuests($payload['enemyId']);
}
break; break;
case 'MapMoveEvent': // ⭐ 响应移动,检查地点触发任务 case 'QuestProgressEvent':
$this->checkSystemTriggers('MAP_MOVE', $event->getPayload()['targetId'] ?? ''); // 假设 MapMoveEvent 携带 targetId (MapTile ID) $this->updateQuestProgress($event->getPayload());
break; break;
case 'LevelUpEvent': // ⭐ 响应升级 case 'DialogueChoice':
$this->checkSystemTriggers('LEVEL_UP', (string)$event->getPayload()['newLevel']); $this->handleDialogueChoice($event->getPayload());
break; break;
case 'QuestAcceptRequest': // ⭐ 响应对话中的接受任务请求 case 'BattleEndEvent':
$this->startQuest($event->getPayload()['questId']); $this->checkKillQuests($event->getPayload());
break; break;
case 'QuestTurnInConfirm': // ⭐ 交付任务确认 case 'LevelUpEvent':
$this->turnInQuest($event->getPayload()['questId']); $this->checkLevelQuests($event->getPayload());
break; break;
} }
} }
/** /**
* 3. 检查击杀类任务进度 * 接受任务
*/ */
private function checkKillQuests(string $killedEnemyId): void { private function acceptQuest(string $questId): void {
$player = $this->stateManager->getPlayer(); $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']}] <fg=green;options=bold>已完成!</>请回去找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' => "🎉 <fg=yellow>任务提交成功!</> 获得了奖励。"
]));
} 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); $questData = $this->questRepository->find($questId);
if (!$questData) { if (!$questData) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 错误任务ID '{$questId}' 不存在。"])); $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "❌ 错误任务ID '{$questId}' 不存在。"]));
return; 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', [ $startNode = $questData['nodes']['START'] ?? null;
'message' => "📝 接受任务: <fg=yellow>{$quest->getName()}</> - {$quest->getDescription()}" if ($startNode && isset($startNode['preConditions'])) {
])); if (!$this->conditionEngine->checkConditions($player, $startNode['preConditions'])) {
} $this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "前置条件不满足,无法接受此任务。"]));
return;
/** }
* 游戏开始时尝试加载初始任务 (或通过 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;
} }
// 初始化任务状态
$player->addActiveQuestNode($questId, 'START');
$quest = $activeQuests[$questId]; $this->dispatcher->dispatch(new Event('SystemMessage', [
if (!$quest->isCompleted()) { 'message' => "📝 接受任务: <fg=yellow>{$questData['name']}</> - {$questData['description']}"
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '任务还未完成,无法交付。'])); ]));
// 显示起始对话
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; return;
} }
$questData = $this->questRepository->find($questId); $questData = $this->questRepository->find($questId);
if (!$questData) { if (!$questData) {
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '任务数据出错。']));
return; return;
} }
// 发放奖励 $nodeData = $questData['nodes'][$currentNode] ?? null;
$rewards = $questData['rewards'] ?? []; if (!$nodeData || $nodeData['type'] !== 'objective') {
return;
// 经验值奖励
if (isset($rewards['xp'])) {
$player->gainXp($rewards['xp']);
$this->dispatcher->dispatch(new Event('SystemMessage', [
'message' => " ✅ 获得 <fg=yellow>{$rewards['xp']}</> 经验值"
]));
} }
// 金币奖励 // 检查目标是否完成
if (isset($rewards['gold'])) { if ($this->evaluator->evaluateObjective($player, $nodeData['goal'])) {
$player->gainGold($rewards['gold']); $this->completeObjective($questId, $nodeData);
$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>任务已交付!</> 感谢你的帮助!"
]));
} }
/** /**
* 7. 检查系统触发的任务 * 完成目标节点
*/ */
private function checkSystemTriggers(string $triggerType, string $triggerValue): void { private function completeObjective(string $questId, array $nodeData): void {
$player = $this->stateManager->getPlayer(); $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'])) { private function checkKillQuests(array $payload): void {
$this->dispatcher->dispatch(new Event('StartDialogueEvent', ['dialogue' => $questData['dialogue']])); $killedEnemyId = $payload['enemyId'] ?? '';
} else { $player = $this->stateManager->getPlayer();
// 无对话直接接取? 或者弹窗提示
// 简单起见,无对话也强制开始(可能会有默认提示) // 遍历所有进行中的任务
$this->startQuest($questId); 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 {
// 初始化逻辑
}
} }

View File

@ -107,7 +107,8 @@ class StateManager {
public function getCurrentTile(): MapTile { public function getCurrentTile(): MapTile {
if (!isset($this->currentTile)) { 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; return $this->currentTile;
} }

View File

@ -0,0 +1,77 @@
<?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("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";

View File

@ -26,8 +26,8 @@ echo "=== 《凡人修仙传》第一阶段地图显示测试 ===\n\n";
// 测试地图显示 // 测试地图显示
$mapRepo = $container->getMapRepository(); $mapRepo = $container->getMapRepository();
$stateManager->setCurrentTileId('VILLAGE_01'); $stateManager->setCurrentTileId('CAVE_01');
$dispatcher->dispatch(new Event('MapExploreRequest')); $dispatcher->dispatch(new Event('StartInteractionEvent', ['npcId' => 'MODOCTOR']));
dd(2); dd(2);
// 手动调用UIService显示地图信息 // 手动调用UIService显示地图信息
$uiService = new \Game\System\UIService($output, $stateManager, $container->getQuestRepository()); $uiService = new \Game\System\UIService($output, $stateManager, $container->getQuestRepository());

View File

@ -0,0 +1,129 @@
<?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("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";

View File

@ -0,0 +1,106 @@
<?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("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";

View File

@ -0,0 +1,93 @@
<?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("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";

View File

@ -0,0 +1,93 @@
<?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("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";

View File

@ -0,0 +1,87 @@
<?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("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";