优化副本闯关模式系统

修改内容:
* 使用unordered_map替代vector提升查询性能
* 修复SummonedCreatureDies中的逻辑错误
* 添加线程安全保护(mutex)
* 修复关卡验证错误,调整逻辑顺序
* 完成所有关卡后改为离开副本选项

技术改进:
- Stage类数据结构优化
- 完善错误处理和日志记录
- 修复状态管理bug

解决问题:
- 修复完成所有关卡后的状态处理
- 提升系统稳定性和性能
This commit is contained in:
尚美 2025-07-17 12:49:36 +08:00
parent d72e531f5b
commit 74774554f7
2 changed files with 314 additions and 149 deletions

View File

@ -1,5 +1,4 @@
#include "Player.h"
#include "stage.h"
#include <ScriptedGossip.h>
#include "CreatureScript.h"
#include "PlayerScript.h"
@ -7,217 +6,365 @@
#include "ScriptedCreature.h"
#include "Chat.h"
#include "stage.h"
#include "../mod_Switch/Switch.h"
#include <mod_CustomCreature.h>
std::vector<StageTemplate> StageVec;
// 使用unordered_map替代vector提升查询性能
std::unordered_map<uint32, StageTemplate> StageMap;
std::mutex StageMutex; // 添加线程安全保护
void Stage::Load()
{
uint32 oldMSTime = getMSTime();
StageVec.clear();
QueryResult result = WorldDatabase.Query(
// 0 1 2 3
"SELECT 关卡ID,召唤生物组ID,通关召唤物体ID,菜单文本 FROM acore_custom._副本_闯关模式");
std::lock_guard<std::mutex> lock(StageMutex);
StageMap.clear();
QueryResult result = WorldDatabase.Query("SELECT 关卡ID,召唤生物组ID,通关召唤物体ID,菜单文本 FROM acore_custom._副本_闯关模式");
if (result)
{
do
{
Field* fields = result->Fetch();
StageTemplate Temp;
Temp.stageRank = fields[0].Get<uint32>();
Temp.summonsGroupId = fields[1].Get<uint32>();
Temp.rewardGameobjectId = fields[2].Get<uint32>();
Temp.description = fields[3].Get<std::string>();
StageVec.push_back(Temp);
StageTemplate temp;
temp.stageRank = fields[0].Get<uint32>();
temp.summonsGroupId = fields[1].Get<uint32>();
temp.rewardGameobjectId = fields[2].Get<uint32>();
temp.description = fields[3].Get<std::string>();
// 验证召唤组ID和GameObject模板是否存在
if (!ValidateSummonGroup(temp.summonsGroupId))
{
LOG_ERROR("module", "关卡 {} 有无效的召唤组ID: {}", temp.stageRank, temp.summonsGroupId);
continue;
}
if (temp.rewardGameobjectId != 0 && !sObjectMgr->GetGameObjectTemplate(temp.rewardGameobjectId))
{
LOG_ERROR("module", "关卡 {} 有无效的游戏物体ID: {}", temp.stageRank, temp.rewardGameobjectId);
continue;
}
StageMap[temp.stageRank] = temp;
} while (result->NextRow());
sLog->outMessage("server", LogLevel::LOG_LEVEL_INFO, ">> 读取自定义功能数据表【_副本_闯关模式-----------】,共{}条数据读取加载,用时{}毫秒", StageVec.size(), GetMSTimeDiffToNow(oldMSTime));
sLog->outMessage("server", LogLevel::LOG_LEVEL_INFO,
">> 读取自定义功能数据表【_副本_闯关模式-----------】,共{}条数据读取加载,用时{}毫秒",
StageMap.size(), GetMSTimeDiffToNow(oldMSTime));
}
}
std::string Stage::GetDescription(uint32 stage)
{
uint32 len = StageVec.size();
for (size_t i = 0; i < len; i++)
{
if (stage == StageVec[i].stageRank)
return StageVec[i].description;
}
return "";
std::lock_guard<std::mutex> lock(StageMutex);
auto it = StageMap.find(stage);
return (it != StageMap.end()) ? it->second.description : "";
}
uint32 Stage::GetSumId(uint32 stage)
{
uint32 len = StageVec.size();
for (size_t i = 0; i < len; i++)
{
if (stage == StageVec[i].stageRank)
return StageVec[i].summonsGroupId;
}
return 1;
std::lock_guard<std::mutex> lock(StageMutex);
auto it = StageMap.find(stage);
return (it != StageMap.end()) ? it->second.summonsGroupId : 0; // 改为返回0表示无效
}
uint32 Stage::GetGobId(uint32 stage)
{
uint32 len = StageVec.size();
std::lock_guard<std::mutex> lock(StageMutex);
auto it = StageMap.find(stage);
return (it != StageMap.end()) ? it->second.rewardGameobjectId : 0;
}
for (size_t i = 0; i < len; i++)
{
if (stage == StageVec[i].stageRank)
return StageVec[i].rewardGameobjectId;
}
bool Stage::IsValidStage(uint32 stage)
{
std::lock_guard<std::mutex> lock(StageMutex);
return StageMap.find(stage) != StageMap.end();
}
return 0;
uint32 Stage::GetMaxStage()
{
std::lock_guard<std::mutex> lock(StageMutex);
return StageMap.size();
}
// 验证召唤组是否存在的辅助函数
bool Stage::ValidateSummonGroup(uint32 groupId)
{
// 这里需要检查creature_summon_groups表中是否存在该组ID
QueryResult result = WorldDatabase.Query("SELECT COUNT(*) FROM creature_summon_groups WHERE groupId = {}", groupId);
if (result)
{
Field* field = result->Fetch();
return field[0].Get<uint32>() > 0;
}
return false;
}
class StageNPC : public CreatureScript
{
public:
StageNPC() : CreatureScript("StageNPC") { }
bool OnGossipHello(Player* player, Creature* creature) override
{
// 获取该creature的mod_CustomCreature实例
StageNPC() : CreatureScript("StageNPC") {}
bool OnGossipHello(Player* player, Creature* creature) override
{
mod_CustomCreature* customCreature = mod_CustomCreature::GetForCreature(creature);
if (!customCreature)
{
LOG_ERROR("module", "闯关NPC: 未能获取生物 {} 的自定义生物数据", creature->GetGUID().ToString());
return false;
}
player->PlayerTalkClass->ClearMenus();
player->PlayerTalkClass->ClearMenus();
uint32 eventid = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 1).c_str());
// 检查时间限制
uint32 eventid = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 1).c_str());
if (eventid != 0 && !sGameEventMgr->IsActiveEvent(eventid))
{
ChatHandler(player->GetSession()).PSendSysMessage("当前时间不允许挑战!");
return false;
}
if (eventid != 0 && !sGameEventMgr->IsActiveEvent(eventid))
{
ChatHandler(player->GetSession()).PSendSysMessage("当前时间不允许挑战!");
return false;
}
// 先检查是否完成所有关卡,再进行有效性验证
if (customCreature->stage > sStage->GetMaxStage())
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, "你已经完成了所有挑战!", 0, GOSSIP_ACTION_INFO_DEF + 3);
AddGossipItemFor(player, GOSSIP_ICON_CHAT, "离开副本", 0, GOSSIP_ACTION_INFO_DEF + 4);
}
else
{
// 只有在正常关卡范围内才进行有效性验证
if (!sStage->IsValidStage(customCreature->stage))
{
LOG_ERROR("module", "生物 {} 的关卡 {} 无效", customCreature->stage, creature->GetGUID().ToString());
ChatHandler(player->GetSession()).PSendSysMessage("关卡配置错误,请联系管理员!");
return false;
}
if (customCreature->summonsClear && customCreature->stage <= StageVec.size())
{
std::ostringstream oss;
oss << "挑战等级:" << customCreature->stage;
if (customCreature->summonsClear)
{
std::ostringstream oss;
oss << "挑战等级:" << customCreature->stage;
AddGossipItemFor(player, GOSSIP_ICON_CHAT, oss.str(), customCreature->stage, GOSSIP_ACTION_INFO_DEF + 1);
AddGossipItemFor(player, GOSSIP_ICON_CHAT, sStage->GetDescription(customCreature->stage), customCreature->stage, GOSSIP_ACTION_INFO_DEF);
}
AddGossipItemFor(player, GOSSIP_ICON_CHAT, oss.str(), customCreature->stage, GOSSIP_ACTION_INFO_DEF + 1);
std::string description = sStage->GetDescription(customCreature->stage);
if (!description.empty())
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, description, customCreature->stage, GOSSIP_ACTION_INFO_DEF);
}
}
else
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, "当前关卡进行中...", 0, GOSSIP_ACTION_INFO_DEF + 2);
}
}
SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID());
return true;
}
return true;
}
bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override
{
// 获取该creature的mod_CustomCreature实例
bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override
{
mod_CustomCreature* customCreature = mod_CustomCreature::GetForCreature(creature);
if (!customCreature)
{
LOG_ERROR("module", "关卡NPC错误无法获取生物GUID[{}]的自定义生物数据", creature->GetGUID().ToString());
return false;
}
if (action == GOSSIP_ACTION_INFO_DEF + 1)
{
OnGossipHello(player, creature);
return true;
}
creature->SummonCreatureGroup(sStage->GetSumId(customCreature->stage));
if (action == GOSSIP_ACTION_INFO_DEF + 1)
{
OnGossipHello(player, creature);
return true;
}
else if (action == GOSSIP_ACTION_INFO_DEF + 2)
{
CloseGossipMenuFor(player);
return true;
}
else if (action == GOSSIP_ACTION_INFO_DEF + 4) // 离开副本
{
CloseGossipMenuFor(player);
// 传送玩家离开副本
player->TeleportToEntryPoint();
return true;
}
// 开始挑战
uint32 summonGroupId = sStage->GetSumId(customCreature->stage);
LOG_INFO("module", "正在尝试为生物Entry[{}]召唤生物组[{}]", summonGroupId, creature->GetEntry());
if (summonGroupId == 0) // 0表示无效
{
ChatHandler(player->GetSession()).PSendSysMessage("关卡召唤配置错误!");
CloseGossipMenuFor(player);
return false;
}
creature->SummonCreatureGroup(summonGroupId);
customCreature->summonsClear = false;
CloseGossipMenuFor(player);
return true;
}
CloseGossipMenuFor(player);
return true;
}
struct StageNPCAI : public ScriptedAI
{
StageNPCAI(Creature* creature) : ScriptedAI(creature), Summons(me){}
struct StageNPCAI : public ScriptedAI
{
StageNPCAI(Creature* creature) : ScriptedAI(creature), Summons(me) {}
SummonList Summons;
void JustSummoned(Creature* summon) override
{
summon->GetMotionMaster()->MoveRandom(5.0f);
Summons.Summon(summon);
}
SummonList Summons;
void Reset() override
{
// 获取该creature的mod_CustomCreature实例
void JustSummoned(Creature* summon) override
{
summon->GetMotionMaster()->MoveRandom(5.0f);
Summons.Summon(summon);
LOG_INFO("module", "已召唤生物Entry[{}] 位置(X:{}, Y:{}, Z:{}) 当前总召唤数:{}",
summon->GetEntry(), summon->GetPositionX(), summon->GetPositionY(),
summon->GetPositionZ(), Summons.size());
}
void Reset() override
{
mod_CustomCreature* customCreature = mod_CustomCreature::GetForCreature(me);
if (!customCreature)
{
LOG_ERROR("module", "关卡NPC AI重置错误无法获取生物GUID[{}]的自定义生物数据", me->GetGUID().ToString());
return;
}
customCreature->summonsClear = true;
customCreature->stage = 1;
Summons.DespawnAll();
}
Summons.DespawnAll();
void MoveInLineOfSight(Unit* who)
{
}
LOG_INFO("module", "重置完成,已清除召唤生物:{},当前关卡:{}", customCreature->summonsClear, customCreature->stage);
}
void SummonedCreatureDies(Creature* summon, Unit* killer) override
{
// 获取该creature的mod_CustomCreature实例
mod_CustomCreature* customCreature = mod_CustomCreature::GetForCreature(summon);
void MoveInLineOfSight(Unit* /*who*/) override
{
// 空实现,避免不必要的处理
}
void SummonedCreatureDies(Creature* summon, Unit* killer) override
{
// 修复应该从me获取customCreature而不是从summon
mod_CustomCreature* customCreature = mod_CustomCreature::GetForCreature(me);
if (!customCreature)
{
LOG_ERROR("module", "召唤生物死亡错误无法获取生物GUID[{}]的自定义生物数据", me->GetGUID().ToString());
return;
}
std::ostringstream oss;
oss << "挑战等级:" << customCreature->stage;
me->Say(oss.str().c_str(), LANG_UNIVERSAL, 0);
LOG_INFO("module", "生物死亡,当前关卡:{},之前召唤数量:{}", customCreature->stage, Summons.size());
Summons.Despawn(summon);
std::ostringstream oss;
oss << "挑战等级:" << customCreature->stage;
me->Say(oss.str().c_str(), LANG_UNIVERSAL, 0);
if (Summons.empty())
{
uint32 gobEntry = sStage->GetGobId(customCreature->stage);
Summons.Despawn(summon);
if (sObjectMgr->GetGameObjectTemplate(gobEntry))
me->SummonGameObject(gobEntry, killer->GetPositionX(), killer->GetPositionY(), killer->GetPositionZ(), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0);
LOG_INFO("module", "消失后召唤数量:{},是否为空:{}", Summons.size(), Summons.empty());
customCreature->stage++;
customCreature->summonsClear = true;
}
}
};
if (Summons.empty())
{
uint32 gobEntry = sStage->GetGobId(customCreature->stage);
CreatureAI* GetAI(Creature* creature) const override
{
return new StageNPCAI(creature);
}
// 验证GameObject模板存在性
if (gobEntry != 0 && sObjectMgr->GetGameObjectTemplate(gobEntry))
{
me->SummonGameObject(gobEntry, killer->GetPositionX(), killer->GetPositionY(),
killer->GetPositionZ(), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300); // 5分钟后消失
}
// 检查是否超出最大关卡
if (customCreature->stage < sStage->GetMaxStage())
{
customCreature->stage++;
customCreature->summonsClear = true;
LOG_INFO("module", "已进阶到关卡:{},召唤清除标记设为:{}", customCreature->stage, customCreature->summonsClear);
// 通知玩家进入下一关
std::ostringstream nextStageMsg;
nextStageMsg << "恭喜通过第" << (customCreature->stage - 1) << "关!准备挑战第" << customCreature->stage << "关!";
me->Say(nextStageMsg.str().c_str(), LANG_UNIVERSAL, 0);
}
else
{
// 所有关卡完成
me->Say("恭喜完成所有关卡挑战!", LANG_UNIVERSAL, 0);
customCreature->summonsClear = true;
customCreature->stage = sStage->GetMaxStage() + 1; // 设置为超出最大关卡
// 可以在这里添加特殊奖励逻辑
}
}
else
{
LOG_INFO("module", "仍有{}个召唤生物存在", Summons.size());
}
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new StageNPCAI(creature);
}
};
class StagePlayerScript : public PlayerScript
{
public:
StagePlayerScript() : PlayerScript("StagePlayerScript") {}
StagePlayerScript() : PlayerScript("StagePlayerScript") {}
void OnPlayerMapChanged(Player* player) override
{
uint32 mapId = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 2).c_str());
void OnPlayerMapChanged(Player* player) override
{
if (!player)
return;
if (player->GetMapId() != mapId)
return;
uint32 mapId = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 2).c_str());
if (mapId == 0 || player->GetMapId() != mapId)
return;
uint32 size = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 4).c_str());
uint32 maxPlayers = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 4).c_str());
if (maxPlayers == 0)
{
LOG_ERROR("module", "关卡系统玩家最大数量配置无效");
return;
}
if (Map* map = player->GetMap())
{
uint32 count = map->GetPlayersCountExceptGMs();
Map* map = player->GetMap();
if (!map)
return;
if (count > size)
{
player->RepopAtGraveyard();
return;
}
uint32 currentPlayers = map->GetPlayersCountExceptGMs();
if (count == 1)
{
uint32 npcId = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 3).c_str());
// 人数超限处理
if (currentPlayers > maxPlayers)
{
ChatHandler(player->GetSession()).PSendSysMessage("当前地图人数已满,正在传送您离开...");
player->RepopAtGraveyard();
return;
}
if (Creature* creature = player->FindNearestCreature(npcId, 150))
creature->AI()->Reset();
}
}
}
// 单人重置逻辑
if (currentPlayers == 1)
{
uint32 npcId = atoi(sSwitch->GetFlagByIndex(ST_STAGE, 3).c_str());
if (npcId == 0)
{
LOG_ERROR("module", "关卡系统NPC ID配置无效");
return;
}
if (Creature* creature = player->FindNearestCreature(npcId, 150.0f))
{
if (creature->AI())
{
creature->AI()->Reset();
ChatHandler(player->GetSession()).PSendSysMessage("关卡已重置,可以开始新的挑战!");
}
}
}
}
};
void AddSC_Stage()

View File

@ -1,27 +1,45 @@
#pragma once
#pragma once
#ifndef STAGE_H
#define STAGE_H
#include "Define.h"
#include <string>
#include <unordered_map>
#include <mutex>
struct StageTemplate
{
uint32 stageRank;
uint32 summonsGroupId;
uint32 rewardGameobjectId;
std::string description;
uint32 stageRank;
uint32 summonsGroupId;
uint32 rewardGameobjectId;
std::string description;
};
extern std::vector<StageTemplate> StageVec;
class Stage
{
public:
static Stage* instance()
{
static Stage instance;
return &instance;
}
static Stage* instance()
{
static Stage instance;
return &instance;
}
void Load();
void Load();
std::string GetDescription(uint32 stage);
uint32 GetSumId(uint32 stage);
uint32 GetGobId(uint32 stage);
bool IsValidStage(uint32 stage);
uint32 GetMaxStage();
uint32 GetSumId(uint32 stage);
uint32 GetGobId(uint32 stage);
std::string GetDescription(uint32 stage);
private:
bool ValidateSummonGroup(uint32 groupId);
Stage() = default;
~Stage() = default;
Stage(const Stage&) = delete;
Stage& operator=(const Stage&) = delete;
};
#define sStage Stage::instance()
#define sStage Stage::instance()
#endif // STAGE_H