玩家进入副本的时候 会生成一个持久性针对当前地图的副本数据 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 函数 里面有具体不从数据库读取数据生成代码