Core/Instances: Delete InstanceSaveMgr and replace most of its uses with new InstanceLockMgr

Port From (https://github.com/TrinityCore/TrinityCore/commit/9b924522d0549dd67b10e2cbdfc20297dd21e182)
This commit is contained in:
hondacrx
2022-10-04 10:57:04 -04:00
parent 793cf91a24
commit 66c7047a29
27 changed files with 186 additions and 1545 deletions
@@ -315,16 +315,20 @@ namespace Game.Maps
Log.outDebug(LogFilter.Instance, $"Deleting instance {instanceId} as it is no longer referenced by any player");
}
public void UpdateInstanceLockExtensionForPlayer(ObjectGuid playerGuid, MapDb2Entries entries, bool extended)
public Tuple<DateTime, DateTime> UpdateInstanceLockExtensionForPlayer(ObjectGuid playerGuid, MapDb2Entries entries, bool extended)
{
InstanceLock instanceLock = FindActiveInstanceLock(playerGuid, entries, true, false);
if (instanceLock != null)
{
DateTime oldExpiryTime = instanceLock.GetEffectiveExpiryTime();
instanceLock.SetExtended(extended);
DB.Characters.Execute($"UPDATE character_instance_lock SET extended = {(extended ? 1 : 0)} WHERE guid = {playerGuid.GetCounter()} AND mapId = {entries.MapDifficulty.MapID} AND lockId = {entries.MapDifficulty.LockID}");
Log.outDebug(LogFilter.Instance, $"[{entries.Map.Id}-{entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()]} | " +
$"{entries.MapDifficulty.DifficultyID}-{CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name}] Instance lock for {playerGuid} is {(extended ? "now" : "no longer")} extended");
return Tuple.Create(oldExpiryTime, instanceLock.GetEffectiveExpiryTime());
}
return Tuple.Create(DateTime.MinValue, DateTime.MinValue);
}
public DateTime GetNextResetTime(MapDb2Entries entries)
@@ -1,778 +0,0 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.Database;
using Game.DataStorage;
using Game.Entities;
using Game.Groups;
using Game.Scenarios;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Game.Maps
{
public class InstanceSaveManager : Singleton<InstanceSaveManager>
{
InstanceSaveManager() { }
public InstanceSave AddInstanceSave(uint mapId, uint instanceId, Difficulty difficulty, long resetTime, uint entranceId, bool canReset, bool load = false)
{
InstanceSave old_save = GetInstanceSave(instanceId);
if (old_save != null)
return old_save;
MapRecord entry = CliDB.MapStorage.LookupByKey(mapId);
if (entry == null)
{
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: wrong mapid = {0}, instanceid = {1}!", mapId, instanceId);
return null;
}
if (instanceId == 0)
{
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, wrong instanceid = {1}!", mapId, instanceId);
return null;
}
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
if (difficultyEntry == null || difficultyEntry.InstanceType != entry.InstanceType)
{
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}, wrong dificalty {2}!", mapId, instanceId, difficulty);
return null;
}
if (entranceId != 0 && Global.ObjectMgr.GetWorldSafeLoc(entranceId) == null)
{
Log.outWarn(LogFilter.Misc, "InstanceSaveManager.AddInstanceSave: invalid entranceId = {0} defined for instance save with mapid = {1}, instanceid = {2}!", entranceId, mapId, instanceId);
entranceId = 0;
}
if (resetTime == 0)
{
// initialize reset time
// for normal instances if no creatures are killed the instance will reset in two hours
if (entry.InstanceType == MapTypes.Raid || difficulty > Difficulty.Normal)
resetTime = GetResetTimeFor(mapId, difficulty);
else
{
resetTime = GameTime.GetGameTime() + 2 * Time.Hour;
// normally this will be removed soon after in InstanceMap.Add, prevent error
ScheduleReset(true, resetTime, new InstResetEvent(0, mapId, difficulty, instanceId));
}
}
Log.outDebug(LogFilter.Maps, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}", mapId, instanceId);
InstanceSave save = new(mapId, instanceId, difficulty, entranceId, resetTime, canReset);
if (!load)
save.SaveToDB();
m_instanceSaveById[instanceId] = save;
return save;
}
public InstanceSave GetInstanceSave(uint InstanceId)
{
return m_instanceSaveById.LookupByKey(InstanceId);
}
public void DeleteInstanceFromDB(uint instanceid)
{
SQLTransaction trans = new();
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INSTANCE_BY_INSTANCE);
stmt.AddValue(0, instanceid);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE);
stmt.AddValue(0, instanceid);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE);
stmt.AddValue(0, instanceid);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE);
stmt.AddValue(0, instanceid);
trans.Append(stmt);
DB.Characters.CommitTransaction(trans);
// Respawn times should be deleted only when the map gets unloaded
}
public void RemoveInstanceSave(uint InstanceId)
{
var instanceSave = m_instanceSaveById.LookupByKey(InstanceId);
if (instanceSave != null)
{
// save the resettime for normal instances only when they get unloaded
long resettime = instanceSave.GetResetTimeForDB();
if (resettime != 0)
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_RESETTIME);
stmt.AddValue(0, resettime);
stmt.AddValue(1, InstanceId);
DB.Characters.Execute(stmt);
}
instanceSave.SetToDelete(true);
m_instanceSaveById.Remove(InstanceId);
}
}
public void UnloadInstanceSave(uint InstanceId)
{
InstanceSave save = GetInstanceSave(InstanceId);
if (save != null)
save.UnloadIfEmpty();
}
public void LoadInstances()
{
uint oldMSTime = Time.GetMSTime();
// Delete expired instances (Instance related spawns are removed in the following cleanup queries)
DB.Characters.DirectExecute("DELETE i FROM instance i LEFT JOIN instance_reset ir ON mapid = map AND i.difficulty = ir.difficulty " +
"WHERE (i.resettime > 0 AND i.resettime < UNIX_TIMESTAMP()) OR (ir.resettime IS NOT NULL AND ir.resettime < UNIX_TIMESTAMP())");
// Delete invalid character_instance and group_instance references
DB.Characters.DirectExecute("DELETE ci.* FROM character_instance AS ci LEFT JOIN characters AS c ON ci.guid = c.guid WHERE c.guid IS NULL");
DB.Characters.DirectExecute("DELETE gi.* FROM group_instance AS gi LEFT JOIN `groups` AS g ON gi.guid = g.guid WHERE g.guid IS NULL");
// Delete invalid instance references
DB.Characters.DirectExecute("DELETE i.* FROM instance AS i LEFT JOIN character_instance AS ci ON i.id = ci.instance LEFT JOIN group_instance AS gi ON i.id = gi.instance WHERE ci.guid IS NULL AND gi.guid IS NULL");
// Delete invalid references to instance
DB.Characters.DirectExecute("DELETE FROM respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
DB.Characters.DirectExecute("DELETE tmp.* FROM character_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
DB.Characters.DirectExecute("DELETE tmp.* FROM group_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
// Clean invalid references to instance
DB.Characters.DirectExecute("UPDATE corpse SET instanceId = 0 WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
DB.Characters.DirectExecute("UPDATE characters AS tmp LEFT JOIN instance ON tmp.instance_id = instance.id SET tmp.instance_id = 0 WHERE tmp.instance_id > 0 AND instance.id IS NULL");
// Initialize instance id storage (Needs to be done after the trash has been clean out)
Global.MapMgr.InitInstanceIds();
// Load reset times and clean expired instances
LoadResetTimes();
Log.outInfo(LogFilter.ServerLoading, "Loaded instances in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
}
void LoadResetTimes()
{
long now = GameTime.GetGameTime();
long today = (now / Time.Day) * Time.Day;
// NOTE: Use DirectPExecute for tables that will be queried later
// get the current reset times for normal instances (these may need to be updated)
// these are only kept in memory for InstanceSaves that are loaded later
// resettime = 0 in the DB for raid/heroic instances so those are skipped
Dictionary<uint, Tuple<uint, long>> instResetTime = new();
// index instance ids by map/difficulty pairs for fast reset warning send
MultiMap<uint, uint> mapDiffResetInstances = new();
SQLResult result = DB.Characters.Query("SELECT id, map, difficulty, resettime FROM instance ORDER BY id ASC");
if (!result.IsEmpty())
{
do
{
uint instanceId = result.Read<uint>(0);
// Mark instance id as being used
Global.MapMgr.RegisterInstanceId(instanceId);
long resettime = result.Read<long>(3);
if (resettime != 0)
{
uint mapid = result.Read<ushort>(1);
uint difficulty = result.Read<byte>(2);
instResetTime[instanceId] = Tuple.Create(MathFunctions.MakePair32(mapid, difficulty), resettime);
mapDiffResetInstances.Add(MathFunctions.MakePair32(mapid, difficulty), instanceId);
}
}
while (result.NextRow());
// schedule the reset times
foreach (var pair in instResetTime)
if (pair.Value.Item2 > now)
ScheduleReset(true, pair.Value.Item2, new InstResetEvent(0, MathFunctions.Pair32_LoPart(pair.Value.Item1), (Difficulty)MathFunctions.Pair32_HiPart(pair.Value.Item1), pair.Key));
}
// load the global respawn times for raid/heroic instances
uint resetHour = WorldConfig.GetUIntValue(WorldCfg.InstanceResetTimeHour);
result = DB.Characters.Query("SELECT mapid, difficulty, resettime FROM instance_reset");
if (!result.IsEmpty())
{
do
{
uint mapid = result.Read<ushort>(0);
Difficulty difficulty = (Difficulty)result.Read<byte>(1);
long oldresettime = result.Read<long>(2);
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty);
if (mapDiff == null)
{
Log.outError(LogFilter.Server, "InstanceSaveManager.LoadResetTimes: invalid mapid({0})/difficulty({1}) pair in instance_reset!", mapid, difficulty);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GLOBAL_INSTANCE_RESETTIME);
stmt.AddValue(0, mapid);
stmt.AddValue(1, (byte)difficulty);
DB.Characters.DirectExecute(stmt);
continue;
}
// update the reset time if the hour in the configs changes
long newresettime = Time.GetLocalHourTimestamp(oldresettime, resetHour, false);
if (oldresettime != newresettime)
{
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME);
stmt.AddValue(0, newresettime);
stmt.AddValue(1, mapid);
stmt.AddValue(2, (byte)difficulty);
DB.Characters.DirectExecute(stmt);
}
InitializeResetTimeFor(mapid, difficulty, newresettime);
} while (result.NextRow());
}
// calculate new global reset times for expired instances and those that have never been reset yet
// add the global reset times to the priority queue
foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties())
{
uint mapid = mapDifficultyPair.Key;
foreach (var difficultyPair in mapDifficultyPair.Value)
{
Difficulty difficulty = (Difficulty)difficultyPair.Key;
MapDifficultyRecord mapDiff = difficultyPair.Value;
if (mapDiff.GetRaidDuration() == 0)
continue;
// the reset_delay must be at least one day
uint period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day);
if (period < Time.Day)
period = Time.Day;
long t = GetResetTimeFor(mapid, difficulty);
if (t == 0)
{
// initialize the reset time
t = Time.GetLocalHourTimestamp(today + period, resetHour);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_GLOBAL_INSTANCE_RESETTIME);
stmt.AddValue(0, mapid);
stmt.AddValue(1, (byte)difficulty);
stmt.AddValue(2, t);
DB.Characters.DirectExecute(stmt);
}
if (t < now)
{
// assume that expired instances have already been cleaned
// calculate the next reset time
long day = (t / Time.Day) * Time.Day;
t = Time.GetLocalHourTimestamp(day + ((today - day) / period + 1) * period, resetHour);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME);
stmt.AddValue(0, t);
stmt.AddValue(1, mapid);
stmt.AddValue(2, (byte)difficulty);
DB.Characters.DirectExecute(stmt);
}
InitializeResetTimeFor(mapid, difficulty, t);
// schedule the global reset/warning
byte type;
for (type = 1; type < 4; ++type)
if (t - ResetTimeDelay[type - 1] > now)
break;
ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, 0));
var range = mapDiffResetInstances.LookupByKey(MathFunctions.MakePair32(mapid, (uint)difficulty));
foreach (var id in range)
ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, id));
}
}
}
public long GetSubsequentResetTime(uint mapid, Difficulty difficulty, long resetTime)
{
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty);
if (mapDiff == null || mapDiff.GetRaidDuration() == 0)
{
Log.outError(LogFilter.Misc, "InstanceSaveManager.GetSubsequentResetTime: not valid difficulty or no reset delay for map {0}", mapid);
return 0;
}
long resetHour = WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour);
long period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day);
if (period < Time.Day)
period = Time.Day;
return Time.GetLocalHourTimestamp(((resetTime + Time.Minute) / Time.Day * Time.Day) + period, (uint)resetHour);
}
public void ScheduleReset(bool add, long time, InstResetEvent Event)
{
if (!add)
{
// find the event in the queue and remove it
var range = m_resetTimeQueue.LookupByKey(time);
foreach (var instResetEvent in range)
{
if (instResetEvent == Event)
{
m_resetTimeQueue.Remove(time, instResetEvent);
return;
}
}
// in case the reset time changed (should happen very rarely), we search the whole queue
foreach (var pair in m_resetTimeQueue)
{
if (pair.Value == Event)
{
m_resetTimeQueue.Remove(pair);
return;
}
}
Log.outError(LogFilter.Server, "InstanceSaveManager.ScheduleReset: cannot cancel the reset, the event({0}, {1}, {2}) was not found!", Event.type, Event.mapid, Event.instanceId);
}
else
m_resetTimeQueue.Add(time, Event);
}
public void ForceGlobalReset(uint mapId, Difficulty difficulty)
{
if (Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty) == null)
return;
// remove currently scheduled reset times
ScheduleReset(false, 0, new InstResetEvent(1, mapId, difficulty, 0));
ScheduleReset(false, 0, new InstResetEvent(4, mapId, difficulty, 0));
// force global reset on the instance
_ResetOrWarnAll(mapId, difficulty, false, GameTime.GetGameTime());
}
public void Update()
{
long now = GameTime.GetGameTime();
while (!m_resetTimeQueue.Empty())
{
var pair = m_resetTimeQueue.First();
long time = pair.Key;
if (time >= now)
break;
InstResetEvent Event = pair.Value;
if (Event.type == 0)
{
// for individual normal instances, max creature respawn + X hours
_ResetInstance(Event.mapid, Event.instanceId);
m_resetTimeQueue.Remove(pair);
}
else
{
// global reset/warning for a certain map
long resetTime = GetResetTimeFor(Event.mapid, Event.difficulty);
_ResetOrWarnAll(Event.mapid, Event.difficulty, Event.type != 4, resetTime);
if (Event.type != 4)
{
// schedule the next warning/reset
++Event.type;
ScheduleReset(true, resetTime - ResetTimeDelay[Event.type - 1], Event);
}
m_resetTimeQueue.Remove(pair);
}
}
}
void _ResetSave(KeyValuePair<uint, InstanceSave> pair)
{
// unbind all players bound to the instance
// do not allow UnbindInstance to automatically unload the InstanceSaves
lock_instLists = true;
bool shouldDelete = true;
var pList = pair.Value.m_playerList;
List<Player> temp = new(); // list of expired binds that should be unbound
foreach (var player in pList)
{
InstanceBind bind = player.GetBoundInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID());
if (bind != null)
{
Cypher.Assert(bind.save == pair.Value);
if (bind.perm && bind.extendState != 0) // permanent and not already expired
{
// actual promotion in DB already happened in caller
bind.extendState = bind.extendState == BindExtensionState.Extended ? BindExtensionState.Normal : BindExtensionState.Expired;
shouldDelete = false;
continue;
}
}
temp.Add(player);
}
var gList = pair.Value.m_groupList;
while (!gList.Empty())
{
Group group = gList.First();
group.UnbindInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID(), true);
}
if (shouldDelete)
m_instanceSaveById.Remove(pair.Key);
lock_instLists = false;
}
void _ResetInstance(uint mapid, uint instanceId)
{
Log.outDebug(LogFilter.Maps, "InstanceSaveMgr._ResetInstance {0}, {1}", mapid, instanceId);
var map = CliDB.MapStorage.LookupByKey(mapid);
if (!map.IsDungeon())
return;
var pair = m_instanceSaveById.Find(instanceId);
if (pair.Value != null)
_ResetSave(pair);
DeleteInstanceFromDB(instanceId); // even if save not loaded
Map iMap = Global.MapMgr.FindMap(mapid, instanceId);
if (iMap != null)
{
((InstanceMap)iMap).Reset(InstanceResetMethod.RespawnDelay);
iMap.DeleteRespawnTimes();
iMap.DeleteCorpseData();
}
else
Map.DeleteRespawnTimesInDB(mapid, instanceId);
// Free up the instance id and allow it to be reused
Global.MapMgr.FreeInstanceId(instanceId);
}
void _ResetOrWarnAll(uint mapid, Difficulty difficulty, bool warn, long resetTime)
{
// global reset for all instances of the given map
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid);
if (!mapEntry.Instanceable())
return;
Log.outDebug(LogFilter.Misc, "InstanceSaveManager.ResetOrWarnAll: Processing map {0} ({1}) on difficulty {2} (warn? {3})", mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()], mapid, difficulty, warn);
long now = GameTime.GetGameTime();
if (!warn)
{
// calculate the next reset time
long next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime);
if (next_reset == 0)
return;
// delete them from the DB, even if not loaded
SQLTransaction trans = new();
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF);
stmt.AddValue(0, mapid);
stmt.AddValue(1, (byte)difficulty);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_MAP_DIFF);
stmt.AddValue(0, mapid);
stmt.AddValue(1, (byte)difficulty);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_INSTANCE_BY_MAP_DIFF);
stmt.AddValue(0, mapid);
stmt.AddValue(1, (byte)difficulty);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF);
stmt.AddValue(0, mapid);
stmt.AddValue(1, (byte)difficulty);
trans.Append(stmt);
DB.Characters.CommitTransaction(trans);
// promote loaded binds to instances of the given map
foreach (var pair in m_instanceSaveById.ToList())
{
if (pair.Value.GetMapId() == mapid && pair.Value.GetDifficultyID() == difficulty)
_ResetSave(pair);
}
SetResetTimeFor(mapid, difficulty, next_reset);
ScheduleReset(true, next_reset - 3600, new InstResetEvent(1, mapid, difficulty, 0));
// Update it in the DB
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME);
stmt.AddValue(0, (uint)next_reset);
stmt.AddValue(1, (ushort)mapid);
stmt.AddValue(2, (byte)difficulty);
DB.Characters.Execute(stmt);
}
// note: this isn't fast but it's meant to be executed very rarely
if (mapEntry.IsDungeon())
{
Global.MapMgr.DoForAllMapsWithMapId(mapid, map =>
{
if (warn)
{
uint timeLeft;
if (now >= resetTime)
timeLeft = 0;
else
timeLeft = (uint)(resetTime - now);
((InstanceMap)map).SendResetWarnings(timeLeft);
}
else
((InstanceMap)map).Reset(InstanceResetMethod.Global);
});
}
// @todo delete creature/gameobject respawn times even if the maps are not loaded
}
public uint GetNumBoundPlayersTotal()
{
uint ret = 0;
foreach (var pair in m_instanceSaveById)
ret += pair.Value.GetPlayerCount();
return ret;
}
public uint GetNumBoundGroupsTotal()
{
uint ret = 0;
foreach (var pair in m_instanceSaveById)
ret += pair.Value.GetGroupCount();
return ret;
}
public long GetResetTimeFor(uint mapid, Difficulty d)
{
return m_resetTimeByMapDifficulty.LookupByKey(MathFunctions.MakePair64(mapid, (uint)d));
}
// Use this on startup when initializing reset times
void InitializeResetTimeFor(uint mapid, Difficulty d, long t)
{
m_resetTimeByMapDifficulty[MathFunctions.MakePair64(mapid, (uint)d)] = t;
}
// Use this only when updating existing reset times
void SetResetTimeFor(uint mapid, Difficulty d, long t)
{
var key = MathFunctions.MakePair64(mapid, (uint)d);
Cypher.Assert(m_resetTimeByMapDifficulty.ContainsKey(key));
m_resetTimeByMapDifficulty[key] = t;
}
public Dictionary<ulong, long> GetResetTimeMap()
{
return m_resetTimeByMapDifficulty;
}
public int GetNumInstanceSaves() { return m_instanceSaveById.Count; }
public class InstResetEvent
{
public InstResetEvent(byte t = 0, uint _mapid = 0, Difficulty d = Difficulty.Normal, uint _instanceid = 0)
{
type = t;
difficulty = d;
mapid = _mapid;
instanceId = _instanceid;
}
public byte type;
public Difficulty difficulty;
public uint mapid;
public uint instanceId;
}
static ushort[] ResetTimeDelay = { 3600, 900, 300, 60 };
// used during global instance resets
public bool lock_instLists;
// fast lookup by instance id
Dictionary<uint, InstanceSave> m_instanceSaveById = new();
// fast lookup for reset times (always use existed functions for access/set)
Dictionary<ulong, long> m_resetTimeByMapDifficulty = new();
MultiMap<long, InstResetEvent> m_resetTimeQueue = new();
}
public class InstanceSave
{
public InstanceSave(uint MapId, uint InstanceId, Difficulty difficulty, uint entranceId, long resetTime, bool canReset)
{
m_resetTime = resetTime;
m_instanceid = InstanceId;
m_mapid = MapId;
m_difficulty = difficulty;
m_entranceId = entranceId;
m_canReset = canReset;
m_toDelete = false;
}
public void SaveToDB()
{
// save instance data too
string data = "";
uint completedEncounters = 0;
Map map = Global.MapMgr.FindMap(GetMapId(), m_instanceid);
if (map != null)
{
Cypher.Assert(map.IsDungeon());
InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript();
if (instanceScript != null)
{
data = instanceScript.GetSaveData();
completedEncounters = instanceScript.GetCompletedEncounterMask();
m_entranceId = instanceScript.GetEntranceLocation();
}
InstanceScenario scenario = map.ToInstanceMap().GetInstanceScenario();
if (scenario != null)
scenario.SaveToDB();
}
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_INSTANCE_SAVE);
stmt.AddValue(0, m_instanceid);
stmt.AddValue(1, GetMapId());
stmt.AddValue(2, GetResetTimeForDB());
stmt.AddValue(3, (uint)GetDifficultyID());
stmt.AddValue(4, completedEncounters);
stmt.AddValue(5, data);
stmt.AddValue(6, m_entranceId);
DB.Characters.Execute(stmt);
}
public long GetResetTimeForDB()
{
// only save the reset time for normal instances
MapRecord entry = CliDB.MapStorage.LookupByKey(GetMapId());
if (entry == null || entry.InstanceType == MapTypes.Raid || GetDifficultyID() == Difficulty.Heroic)
return 0;
else
return GetResetTime();
}
MapRecord GetMapEntry()
{
return CliDB.MapStorage.LookupByKey(m_mapid);
}
public void DeleteFromDB()
{
Global.InstanceSaveMgr.DeleteInstanceFromDB(GetInstanceId());
}
public bool UnloadIfEmpty()
{
if (m_playerList.Empty() && m_groupList.Empty())
{
if (!Global.InstanceSaveMgr.lock_instLists)
Global.InstanceSaveMgr.RemoveInstanceSave(GetInstanceId());
return false;
}
else
return true;
}
public uint GetPlayerCount() { return (uint)m_playerList.Count; }
public uint GetGroupCount() { return (uint)m_groupList.Count; }
public uint GetInstanceId() { return m_instanceid; }
public uint GetMapId() { return m_mapid; }
public long GetResetTime() { return m_resetTime; }
public void SetResetTime(long resetTime) { m_resetTime = resetTime; }
public uint GetEntranceLocation() { return m_entranceId; }
void SetEntranceLocation(uint entranceId) { m_entranceId = entranceId; }
public void AddPlayer(Player player)
{
m_playerList.Add(player);
}
public bool RemovePlayer(Player player)
{
m_playerList.Remove(player);
return UnloadIfEmpty();
}
public void AddGroup(Group group) { m_groupList.Add(group); }
public bool RemoveGroup(Group group)
{
m_groupList.Remove(group);
return UnloadIfEmpty();
}
public bool CanReset() { return m_canReset; }
public void SetCanReset(bool canReset) { m_canReset = canReset; }
public Difficulty GetDifficultyID() { return m_difficulty; }
public void SetToDelete(bool toDelete)
{
m_toDelete = toDelete;
}
public List<Player> m_playerList = new();
public List<Group> m_groupList = new();
long m_resetTime;
uint m_instanceid;
uint m_mapid;
Difficulty m_difficulty;
uint m_entranceId;
bool m_canReset;
bool m_toDelete;
}
public class InstanceTemplate
{
public uint Parent;
public uint ScriptId;
}
public class InstanceBind
{
public InstanceSave save;
public bool perm;
public BindExtensionState extendState;
}
}
+12 -3
View File
@@ -430,7 +430,7 @@ namespace Game.Maps
bossInfo.state = state;
SaveToDB();
if (state == EncounterState.Done)
if (state == EncounterState.Done && dungeonEncounter != null)
instance.UpdateInstanceLock(dungeonEncounter, new(id, state));
}
@@ -754,11 +754,20 @@ namespace Game.Maps
return false;
}
bool IsEncounterCompleted(uint dungeonEncounterId)
{
for (uint i = 0; i < bosses.Count; ++i)
for (var j = 0; j < bosses[i].DungeonEncounters.Length; ++j)
if (bosses[i].DungeonEncounters[j] != null && bosses[i].DungeonEncounters[j].Id == dungeonEncounterId)
return bosses[i].state == EncounterState.Done;
return false;
}
public void SetEntranceLocation(uint worldSafeLocationId)
{
_entranceId = worldSafeLocationId;
if (_temporaryEntranceId != 0)
_temporaryEntranceId = 0;
_temporaryEntranceId = 0;
}
public void SendEncounterUnit(EncounterFrameType type, Unit unit = null, byte priority = 0)
+31 -61
View File
@@ -1713,20 +1713,17 @@ namespace Game.Maps
{
//Get instance where player's group is bound & its map
uint instanceIdToCheck = Global.MapMgr.FindInstanceIdForPlayer(mapid, player);
if (instanceIdToCheck != 0)
Map boundMap = Global.MapMgr.FindMap(mapid, instanceIdToCheck);
if (boundMap != null)
{
Map boundMap = Global.MapMgr.FindMap(mapid, instanceIdToCheck);
if (boundMap != null)
{
EnterState denyReason = boundMap.CannotEnter(player);
if (denyReason != 0)
return denyReason;
}
// players are only allowed to enter 10 instances per hour
if (entry.IsDungeon() && !player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead())
return EnterState.CannotEnterTooManyInstances;
EnterState denyReason = boundMap.CannotEnter(player);
if (denyReason != 0)
return denyReason;
}
// players are only allowed to enter 10 instances per hour
if (entry.IsDungeon() && !player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead())
return EnterState.CannotEnterTooManyInstances;
}
return EnterState.CanEnter;
@@ -2090,6 +2087,9 @@ namespace Game.Maps
void DeleteRespawnInfoFromDB(SpawnObjectType type, ulong spawnId, SQLTransaction dbTrans = null)
{
if (Instanceable())
return;
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_RESPAWN);
stmt.AddValue(0, (ushort)type);
stmt.AddValue(1, spawnId);
@@ -2727,6 +2727,9 @@ namespace Game.Maps
public void SaveRespawnInfoDB(RespawnInfo info, SQLTransaction dbTrans = null)
{
if (Instanceable())
return;
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.REP_RESPAWN);
stmt.AddValue(0, (ushort)info.type);
stmt.AddValue(1, info.spawnId);
@@ -2738,6 +2741,9 @@ namespace Game.Maps
public void LoadRespawnTimes()
{
if (Instanceable())
return;
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_RESPAWNS);
stmt.AddValue(0, GetId());
stmt.AddValue(1, GetInstanceId());
@@ -2768,14 +2774,17 @@ namespace Game.Maps
public void DeleteRespawnTimes()
{
UnloadAllRespawnInfos();
DeleteRespawnTimesInDB(GetId(), GetInstanceId());
DeleteRespawnTimesInDB();
}
public static void DeleteRespawnTimesInDB(uint mapId, uint instanceId)
public void DeleteRespawnTimesInDB()
{
if (Instanceable())
return;
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ALL_RESPAWNS);
stmt.AddValue(0, mapId);
stmt.AddValue(1, instanceId);
stmt.AddValue(0, GetId());
stmt.AddValue(1, GetInstanceId());
DB.Characters.Execute(stmt);
}
@@ -3284,11 +3293,6 @@ namespace Game.Maps
return i_mapRecord != null && i_mapRecord.IsRaid();
}
public bool IsRaidOrHeroicDungeon()
{
return IsRaid() || IsHeroic();
}
public bool IsHeroic()
{
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(i_spawnMode);
@@ -4893,7 +4897,6 @@ namespace Game.Maps
// for normal instances schedule the reset after all players have left
SetResetSchedule(true);
Global.InstanceSaveMgr.UnloadInstanceSave(GetInstanceId());
}
public void CreateInstanceData()
@@ -4942,10 +4945,11 @@ namespace Game.Maps
public bool Reset(InstanceResetMethod method)
{
// note: since the map may not be loaded when the instance needs to be reset
// the instance must be deleted from the DB by InstanceSaveManager
// the instance must be deleted from the DB
if (HavePlayers())
{
// on manual reset, fail
if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty)
{
// notify the players to leave the instance so it can be reset
@@ -4954,24 +4958,15 @@ namespace Game.Maps
}
else
{
bool doUnload = true;
// on lock expiration boot players (do we also care about extension state?)
if (method == InstanceResetMethod.Global)
{
// set the homebind timer for players inside (1 minute)
foreach (Player player in GetPlayers())
{
InstanceBind bind = player.GetBoundInstance(GetId(), GetDifficultyID());
if (bind != null && bind.extendState != 0 && bind.save.GetInstanceId() == GetInstanceId())
doUnload = false;
else
player.m_InstanceValid = false;
}
if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this
doUnload = false;
player.m_InstanceValid = false;
}
if (doUnload)
if (!HasPermBoundPlayers())
{
// the unload timer is not started
// instead the map will unload immediately after the players have left
@@ -5075,28 +5070,9 @@ namespace Game.Maps
base.UnloadAll();
}
public void SendResetWarnings(uint timeLeft)
{
foreach (Player player in GetPlayers())
player.SendInstanceResetWarning(GetId(), player.GetDifficultyID(GetEntry()), timeLeft, true);
}
public void SetResetSchedule(bool on)
{
// only for normal instances
// the reset time is only scheduled when there are no payers inside
// it is assumed that the reset time will rarely (if ever) change while the reset is scheduled
if (IsDungeon() && !HavePlayers() && !IsRaidOrHeroicDungeon())
{
InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId());
if (save != null)
Global.InstanceSaveMgr.ScheduleReset(on, save.GetResetTime(),
new InstanceSaveManager.InstResetEvent(0, GetId(), GetDifficultyID(), GetInstanceId()));
else
Log.outError(LogFilter.Maps,
"InstanceMap.SetResetSchedule: cannot turn schedule {0}, there is no save information for instance (map [id: {1}, name: {2}], instance id: {3}, difficulty: {4})",
on ? "on" : "off", GetId(), GetMapName(), GetInstanceId(), GetDifficultyID());
}
}
bool HasPermBoundPlayers()
@@ -5115,12 +5091,6 @@ namespace Game.Maps
return GetEntry().MaxPlayers;
}
public uint GetMaxResetDelay()
{
MapDifficultyRecord mapDiff = GetMapDifficulty();
return mapDiff != null ? mapDiff.GetRaidDuration() : 0;
}
public int GetTeamIdInInstance()
{
if (Global.WorldStateMgr.GetValue(WorldStates.TeamInInstanceAlliance, this) != 0)
+44
View File
@@ -242,6 +242,50 @@ namespace Game.Entities
return FindMap_i(mapId, instanceId);
}
public uint FindInstanceIdForPlayer(uint mapId, Player player)
{
MapRecord entry = CliDB.MapStorage.LookupByKey(mapId);
if (entry == null)
return 0;
if (entry.IsBattlegroundOrArena())
return player.GetBattlegroundId();
else if (entry.IsDungeon())
{
Group group = player.GetGroup();
Difficulty difficulty = group != null ? group.GetDifficultyID(entry) : player.GetDifficultyID(entry);
MapDb2Entries entries = new(entry, Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty));
ObjectGuid instanceOwnerGuid = group ? group.GetRecentInstanceOwner(mapId) : player.GetGUID();
InstanceLock instanceLock = Global.InstanceLockMgr.FindActiveInstanceLock(instanceOwnerGuid, entries);
uint newInstanceId = 0;
if (instanceLock != null)
newInstanceId = instanceLock.GetInstanceId();
else if (!entries.MapDifficulty.HasResetSchedule()) // Try finding instance id for normal dungeon
newInstanceId = group ? group.GetRecentInstanceId(mapId) : player.GetRecentInstanceId(mapId);
if (newInstanceId == 0)
return 0;
Map map = FindMap(mapId, newInstanceId);
// is is possible that instance id is already in use by another group for boss-based locks
if (!entries.IsInstanceIdBound() && instanceLock != null && map != null && map.ToInstanceMap().GetInstanceLock() != instanceLock)
return 0;
return newInstanceId;
}
else if (entry.IsGarrison())
return (uint)player.GetGUID().GetCounter();
else
{
if (entry.IsSplitByFaction())
return (uint)player.GetTeamId();
return 0;
}
}
public void Update(uint diff)
{
i_timer.Update(diff);