玩家进入副本的时候 会生成一个持久性针对当前地图的副本数据 MapPersistentState 这个副本数据会保存在这个持久化类里面 等玩家重置副本时候 下一帧会卸载掉这个数据 重新进入又会生成
调用方法如下
DungeonPersistentState* state = (DungeonPersistentState*)sMapPersistentStateMgr.AddPersistentState(mapEntry, instanceId, Difficulty(difficulty), resetTime, !perm, true);
生成方法如下
MapPersistentState* MapPersistentStateManager::AddPersistentState(MapEntry const* mapEntry, uint32 instanceId, Difficulty difficulty, time_t resetTime, bool canReset, bool load /*=false*/, uint32 completedEncountersMask /*= 0*/)
{
if (MapPersistentState* old_save = GetPersistentState(mapEntry->MapID, instanceId))
return old_save;
if (mapEntry->IsDungeon())
{
if (!resetTime)
{
// initialize reset time
// for normal instances if no creatures are killed the instance will reset in 30 minutes
if (mapEntry->map_type == MAP_RAID || difficulty > DUNGEON_DIFFICULTY_NORMAL)
resetTime = m_Scheduler.GetResetTimeFor(mapEntry->MapID);
else
{
resetTime = time(nullptr) + NORMAL_INSTANCE_RESET_TIME;
// normally this will be removed soon after in DungeonMap::Add, prevent error
m_Scheduler.ScheduleReset(true, resetTime, DungeonResetEvent(RESET_EVENT_NORMAL_DUNGEON, mapEntry->MapID, instanceId));
}
}
}
DEBUG_LOG("MapPersistentStateManager::AddPersistentState: mapid = %d, instanceid = %d, reset time = '" UI64FMTD "', canRset = %u", mapEntry->MapID, instanceId, uint64(resetTime), canReset ? 1 : 0);
MapPersistentState* state;
if (mapEntry->IsDungeon())
{
DungeonPersistentState* dungeonState = new DungeonPersistentState(mapEntry->MapID, instanceId, difficulty, resetTime, canReset, completedEncountersMask);
if (!load)
dungeonState->SaveToDB();
state = dungeonState;
}
else if (mapEntry->IsBattleGroundOrArena())
state = new BattleGroundPersistentState(mapEntry->MapID, instanceId, difficulty);
else
state = new WorldPersistentState(mapEntry->MapID);
if (instanceId)
m_instanceSaveByInstanceId[instanceId] = state;
else
m_instanceSaveByMapId[mapEntry->MapID] = state;
return state;
}
特殊分组怪物数据从void ObjectMgr::LoadSpawnGroups()函数读取后 会从世界怪物里面删除
for (auto& guidData : entry.DbGuids)
{
if (entry.Type == SPAWN_GROUP_CREATURE)
{
CreatureData const* data = GetCreatureData(guidData.DbGuid);
RemoveCreatureFromGrid(guidData.DbGuid, data);
newContainer->spawnGroupByGuidMap.emplace(std::make_pair(guidData.DbGuid, uint32(TYPEID_UNIT)), &entry);
if (sWorld.getConfig(CONFIG_BOOL_AUTOLOAD_ACTIVE))
{
for (auto& data : entry.RandomEntries)
{
if (CreatureInfo const* cinfo = GetCreatureTemplate(data.Entry))
{
if ((cinfo->ExtraFlags & CREATURE_EXTRA_FLAG_ACTIVE) != 0)
{
entry.Active = true;
break;
}
}
}
}
if (data->spawnMask == 0)
entry.EnabledByDefault = false;
if (data->id)
guidData.OwnEntry = data->id;
if (GetAllRandomCreatureEntries(guidData.DbGuid))
guidData.RandomEntry = true;
}
else
{
GameObjectData const* data = GetGOData(guidData.DbGuid);
RemoveGameobjectFromGrid(guidData.DbGuid, data);
newContainer->spawnGroupByGuidMap.emplace(std::make_pair(guidData.DbGuid, uint32(TYPEID_GAMEOBJECT)), &entry);
if (sWorld.getConfig(CONFIG_BOOL_AUTOLOAD_ACTIVE))
{
for (auto& data : entry.RandomEntries)
{
if (CreatureInfo const* cinfo = GetCreatureTemplate(data.Entry))
{
if ((cinfo->ExtraFlags & CREATURE_EXTRA_FLAG_ACTIVE) != 0)
{
entry.Active = true;
break;
}
}
}
}
if (data->spawnMask == 0)
entry.EnabledByDefault = false;
if (data->id)
guidData.OwnEntry = data->id;
if (GetAllRandomGameObjectEntries(guidData.DbGuid))
guidData.RandomEntry = true;
}
}
}
下面就是整个副本初始化时候生成怪物的流程 上面已经生成了持久化的切片数据 然后就可以初始化切片内的怪物数据了 void Map::Initialize(bool loadInstanceData /*= true*/ 里面调用了 InitPools()函数
m_persistentState = sMapPersistentStateMgr.AddPersistentState(i_mapEntry, GetInstanceId(), GetDifficulty(), 0, IsDungeon(), false);
m_persistentState->SetUsedByMapState(this);
m_persistentState->InitPools();
下面就是初始化
void MapPersistentState::InitPools()
{
// pool system initialized already for persistent state (can be shared by map states)
if (!GetSpawnedPoolData().IsInitialized())
{
GetSpawnedPoolData().SetInitialized();
sPoolMgr.Initialize(this); // init pool system data for map persistent state
sGameEventMgr.Initialize(this); // init pool system data for map persistent state
}
}
void PoolManager::Initialize(MapPersistentState* state)
{
// spawn pools for expected map or for not initialized shared pools state for non-instanceable maps
for (uint16 pool_entry = 0; pool_entry < mPoolTemplate.size(); ++pool_entry)
if (mPoolTemplate[pool_entry].AutoSpawn)
InitSpawnPool(*state, pool_entry);
}
void PoolManager::InitSpawnPool(MapPersistentState& mapState, uint16 pool_id)
{
// spawn pool for expected map
if (mPoolTemplate[pool_id].CanBeSpawnedAtMap(mapState.GetMapEntry()))
SpawnPool(mapState, pool_id, true);
}
void PoolManager::SpawnPool(MapPersistentState& mapState, uint16 pool_id, bool instantly)
{
SpawnPoolGroup<Pool>(mapState, pool_id, 0, instantly);
SpawnPoolGroup<GameObject>(mapState, pool_id, 0, instantly);
SpawnPoolGroup<Creature>(mapState, pool_id, 0, instantly);
}
上面就会依次生成一个地图切片里不同的数据
void PoolManager::SpawnPoolGroup<Creature>(MapPersistentState& mapState, uint16 pool_id, uint32 db_guid, bool instantly)
{
if (!mPoolCreatureGroups[pool_id].isEmpty())
mPoolCreatureGroups[pool_id].SpawnObject(mapState, mPoolTemplate[pool_id].MaxLimit, db_guid, instantly);
}
template <class T>
void PoolGroup<T>::SpawnObject(MapPersistentState& mapState, uint32 limit, uint32 triggerFrom, bool instantly)
{
SpawnedPoolData& spawns = mapState.GetSpawnedPoolData();
uint32 lastDespawned = 0;
int count = limit - spawns.GetSpawnedObjects(poolId);
// If triggered from some object respawn this object is still marked as spawned
// and also counted into m_SpawnedPoolAmount so we need increase count to be
// spawned by 1
if (triggerFrom)
{
if (spawns.IsSpawnedObject<T>(triggerFrom))
++count;
else
triggerFrom = 0;
}
// This will try to spawn the rest of pool, not guaranteed
for (int i = 0; i < count; ++i)
{
PoolObject* obj = RollOne(spawns, triggerFrom, mapState);
if (!obj)
continue;
if (obj->guid == lastDespawned)
continue;
if (obj->guid == triggerFrom)
{
MANGOS_ASSERT(spawns.IsSpawnedObject<T>(obj->guid));
MANGOS_ASSERT(spawns.GetSpawnedObjects(poolId) > 0);
ReSpawn1Object(mapState, obj);
triggerFrom = 0;
continue;
}
spawns.AddSpawn<T>(obj->guid, poolId);
Spawn1Object(mapState, obj, instantly);
if (triggerFrom)
{
// One spawn one despawn no count increase
DespawnObject(mapState, triggerFrom);
lastDespawned = triggerFrom;
triggerFrom = 0;
}
}
}
void MapPersistentState::InitPools()
{
// pool system initialized already for persistent state (can be shared by map states)
if (!GetSpawnedPoolData().IsInitialized())
{
GetSpawnedPoolData().SetInitialized();
sPoolMgr.Initialize(this); // init pool system data for map persistent state
sGameEventMgr.Initialize(this); // init pool system data for map persistent state
}
}
template <>
void PoolGroup<Creature>::Spawn1Object(MapPersistentState& mapState, PoolObject* obj, bool instantly)
{
if (CreatureData const* data = sObjectMgr.GetCreatureData(obj->guid))
{
mapState.AddCreatureToGrid(obj->guid, data);
Map* dataMap = mapState.GetMap();
// We use spawn coords to spawn
if (dataMap && dataMap->IsLoaded(data->posX, data->posY))
{
Creature* pCreature = new Creature;
if (!pCreature->LoadFromDB(obj->guid, dataMap, obj->guid, 0))
delete pCreature;
else
{
// if new spawn replaces a just despawned creature, not instantly spawn but set respawn timer
if (!instantly)
{
pCreature->SetRespawnDelay(data->GetRandomRespawnTime());
pCreature->SetRespawnTime(pCreature->GetRespawnDelay());
if (sWorld.getConfig(CONFIG_BOOL_SAVE_RESPAWN_TIME_IMMEDIATELY) || pCreature->IsWorldBoss())
pCreature->SaveRespawnTime();
}
}
}
// for not loaded grid just update respawn time (avoid work for instances until implemented support)
else if (!instantly)
mapState.SaveCreatureRespawnTime(obj->guid, time(nullptr) + data->GetRandomRespawnTime());
}
}
下面是怪物死亡后 更新的过程 这里设置了重生时间
void Creature::Update(const uint32 diff)
{
switch (m_deathState)
{
case JUST_ALIVED:
// Don't must be called, see Creature::SetDeathState JUST_ALIVED -> ALIVE promoting.
sLog.outError("Creature (GUIDLow: %u Entry: %u ) in wrong state: JUST_ALIVED (4)", GetGUIDLow(), GetEntry());
break;
case JUST_DIED:
// Don't must be called, see Creature::SetDeathState JUST_DIED -> CORPSE promoting.
sLog.outError("Creature (GUIDLow: %u Entry: %u ) in wrong state: JUST_DEAD (1)", GetGUIDLow(), GetEntry());
break;
case DEAD:
{
if (m_respawnTime <= time(nullptr) && (!m_isSpawningLinked || GetMap()->GetCreatureLinkingHolder()->CanSpawn(this)))
{
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Respawning...");
m_respawnTime = 0;
SetCanAggro(false);
delete m_loot;
m_loot = nullptr;
// Clear possible auras having IsDeathPersistent() attribute
RemoveAllAuras();
SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE);
SetDeathState(JUST_ALIVED);
// Call AI respawn virtual function
if (AI())
AI()->JustRespawned();
// Inform Instance Data
if (InstanceData* mapInstance = GetInstanceData())
mapInstance->OnCreatureRespawn(this);
if (m_isCreatureLinkingTrigger)
GetMap()->GetCreatureLinkingHolder()->DoCreatureLinkingEvent(LINKING_EVENT_RESPAWN, this);
if (GetCreatureGroup())
GetCreatureGroup()->TriggerLinkingEvent(CREATURE_GROUP_EVENT_RESPAWN, this);
GetMap()->Add(this);
if (GetObjectGuid().GetHigh() != HIGHGUID_PET)
if (uint16 poolid = sPoolMgr.IsPartOfAPool<Creature>(GetDbGuid()))
sPoolMgr.UpdatePool<Creature>(*GetMap()->GetPersistentState(), poolid, GetDbGuid());
}
break;
}
case CORPSE:
{
Unit::Update(diff);
if (m_loot)
m_loot->Update();
if (IsCorpseExpired())
RemoveCorpse();
break;
}
case ALIVE:
{
Unit::Update(diff);
// creature can be dead after Unit::Update call
// CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly)
if (!IsAlive())
break;
// Creature can be dead after unit update
if (IsAlive())
RegenerateAll(diff);
break;
}
default:
break;
}
}
上面代码判断生物是死亡后 当重生时间到了 并且能重生 就会调用
if (GetObjectGuid().GetHigh() != HIGHGUID_PET)
if (uint16 poolid = sPoolMgr.IsPartOfAPool<Creature>(GetDbGuid()))
sPoolMgr.UpdatePool<Creature>(*GetMap()->GetPersistentState(), poolid, GetDbGuid());
里面就会调用 上面生成的代码 就会重生怪物
下面代码实现了添加新的怪物组到世界流程

void SpawnManager::Update()
{
auto now = m_map.GetCurrentClockTime();
for (auto itr = m_spawns.begin(); itr != m_spawns.end();)
{
auto& spawnInfo = *itr;
if (spawnInfo.IsUsed() ||
(spawnInfo.GetRespawnTime() <= now && spawnInfo.ConstructForMap(m_map)))
itr = m_spawns.erase(itr);
else
++itr;
}
for (auto& group : m_spawnGroups)
group.second->Update();
}
void SpawnGroup::Update()
{
Spawn(false);
}
void SpawnGroup::Spawn(bool force)
{
if (!m_enabled && !force)
return;
// duplicated code for optimization - way fewer cond fails
if ((m_entry.Flags & SPAWN_GROUP_DESPAWN_ON_COND_FAIL) != 0) // must be before count check
{
if (!m_objects.empty() && !IsWorldstateConditionSatisfied())
{
Despawn(0, 1); // respawn delay 1 to immediately be available for spawn on next condition success
return;
}
}
if (m_objects.size() >= m_entry.MaxCount)
return;
if (!IsWorldstateConditionSatisfied())
return;
if (m_entry.HasChancedSpawns && m_chosenSpawns.size() >= m_entry.MaxCount)
return;
std::vector<SpawnGroupDbGuids const*> eligibleGuids;
std::map<uint32, uint32> validEntries;
std::map<uint32, uint32> minEntries;
for (auto& randomEntry : m_entry.RandomEntries)
{
validEntries[randomEntry.Entry] = randomEntry.MaxCount > 0 ? randomEntry.MaxCount : std::numeric_limits<uint32>::max();
if (randomEntry.MinCount > 0)
minEntries.emplace(randomEntry.Entry, randomEntry.MinCount);
}
for (auto& guid : m_entry.DbGuids)
eligibleGuids.push_back(&guid);
for (auto& data : m_objects)
{
eligibleGuids.erase(std::remove_if(eligibleGuids.begin(), eligibleGuids.end(), [dbGuid = data.first](SpawnGroupDbGuids const* entry) { return entry->DbGuid == dbGuid; }), eligibleGuids.end());
if (validEntries.size() > 0)
{
uint32 curCount = validEntries[data.second];
validEntries[data.second] = curCount > 0 ? curCount - 1 : 0;
}
if (minEntries.size() > 0)
{
auto itr = minEntries.find(data.second);
if (itr != minEntries.end())
{
--(*itr).second;
if ((*itr).second == 0)
minEntries.erase(itr);
}
}
}
time_t now = time(nullptr);
for (auto itr = eligibleGuids.begin(); itr != eligibleGuids.end();)
{
if (m_map.GetPersistentState()->GetObjectRespawnTime(GetObjectTypeId(), (*itr)->DbGuid) > now)
{
if (!force)
{
if (m_entry.MaxCount == 1) // rare mob case - prevent respawn until all are off CD
return;
itr = eligibleGuids.erase(itr);
continue;
}
else
m_map.GetPersistentState()->SaveObjectRespawnTime(GetObjectTypeId(), (*itr)->DbGuid, now);
}
++itr;
}
for (auto itr = eligibleGuids.begin(); itr != eligibleGuids.end();)
{
uint32 spawnMask = 0; // safeguarded on db load
if (GetObjectTypeId() == TYPEID_UNIT)
spawnMask = sObjectMgr.GetCreatureData((*itr)->DbGuid)->spawnMask;
else
spawnMask = sObjectMgr.GetGOData((*itr)->DbGuid)->spawnMask;
if (spawnMask && (spawnMask & (1 << m_map.GetDifficulty())) == 0)
{
itr = eligibleGuids.erase(itr);
continue;
}
++itr;
}
std::shuffle(eligibleGuids.begin(), eligibleGuids.end(), *GetRandomGenerator());
for (auto itr = eligibleGuids.begin(); itr != eligibleGuids.end() && !eligibleGuids.empty() && m_objects.size() < m_entry.MaxCount; ++itr)
{
uint32 dbGuid = (*itr)->DbGuid;
uint32 entry = 0;
if (m_entry.HasChancedSpawns)
{
if ((*itr)->Chance)
{
auto spawnItr = m_chosenSpawns.find(dbGuid);
bool spawn = true;
if (spawnItr == m_chosenSpawns.end())
{
spawn = roll_chance_i((*itr)->Chance);
m_chosenSpawns[dbGuid] = spawn;
}
else
spawn = spawnItr->second;
if (!spawn)
continue;
}
else // filling redundant entries when a group has chanced spawns for optimization so we can stop at start
m_chosenSpawns[dbGuid] = true;
}
// creatures pick random entry on first spawn in dungeons - else always pick random entry
if (GetObjectTypeId() == TYPEID_UNIT)
{
if (m_map.IsDungeon())
{
// only held in memory - implement saving to db if it becomes a major issue
if (m_chosenEntries.find(dbGuid) == m_chosenEntries.end())
{
// some group members can have static entry, or selfcontained random entry
if ((*itr)->RandomEntry)
entry = sObjectMgr.GetRandomCreatureEntry(dbGuid);
else if ((*itr)->OwnEntry)
entry = (*itr)->OwnEntry;
else
entry = GetEligibleEntry(validEntries, minEntries);
m_chosenEntries[dbGuid] = entry;
}
else
entry = m_chosenEntries[dbGuid];
}
else
entry = GetEligibleEntry(validEntries, minEntries);
}
else // GOs always pick random entry
{
if ((*itr)->RandomEntry)
entry = sObjectMgr.GetRandomGameObjectEntry(dbGuid);
else if ((*itr)->OwnEntry)
entry = (*itr)->OwnEntry;
else
entry = GetEligibleEntry(validEntries, minEntries);
}
float x, y;
if (GetObjectTypeId() == TYPEID_UNIT)
{
auto data = sObjectMgr.GetCreatureData(dbGuid);
x = data->posX; y = data->posY;
m_map.GetPersistentState()->AddCreatureToGrid(dbGuid, data);
}
else
{
auto data = sObjectMgr.GetGOData(dbGuid);
x = data->posX; y = data->posY;
m_map.GetPersistentState()->AddGameobjectToGrid(dbGuid, data);
}
AddObject(dbGuid, entry);
if (force || m_entry.Active || m_map.IsLoaded(x, y))
{
if (GetObjectTypeId() == TYPEID_UNIT)
WorldObject::SpawnCreature(dbGuid, &m_map, entry);
else
WorldObject::SpawnGameObject(dbGuid, &m_map, entry);
}
if (entry && validEntries[entry])
--validEntries[entry];
}
}
这样副本就会在玩家进入后的下一帧 生成对应的怪物进入副本
PS 额外一个问题 就是游戏事件触发的时候 生物及物品生成在下面函数内
void GameEventMgr::GameEventSpawn(int16 event_id)
{
int32 internal_event_id = m_gameEvents.size() + event_id - 1;
if (internal_event_id < 0 || (size_t)internal_event_id >= m_gameEventCreatureGuids.size())
{
sLog.outError("GameEventMgr::GameEventSpawn attempt access to out of range mGameEventCreatureGuids element %i (size: " SIZEFMTD ")", internal_event_id, m_gameEventCreatureGuids.size());
return;
}
for (uint32& itr : m_gameEventCreatureGuids[internal_event_id])
{
// Add to correct cell
CreatureData const* data = sObjectMgr.GetCreatureData(itr);
if (data)
{
// negative event id for pool element meaning allow be used in next pool spawn
if (event_id < 0)
{
if (uint16 pool_id = sPoolMgr.IsPartOfAPool<Creature>(itr))
{
// will have chance at next pool update
sPoolMgr.SetExcludeObject<Creature>(pool_id, itr, false);
sPoolMgr.UpdatePoolInMaps<Creature>(pool_id);
continue;
}
}
sObjectMgr.AddCreatureToGrid(itr, data);
Creature::SpawnInMaps(itr, data);
}
}
具体 怪物生成 有ELUNA版本的 可以参考PerformIngameSpawn 函数 里面有具体不从数据库读取数据生成代码
文章讲述了游戏中的副本系统如何在玩家进入时生成并管理持久性的副本数据,包括DungeonPersistentState和MapPersistentStateManager的使用,以及怪物的生成逻辑,如池系统、随机事件触发下的生物生成等。
2810

被折叠的 条评论
为什么被折叠?



