Core/Instances: Instance lock rewrite (WIP)

Port From (https://github.com/TrinityCore/TrinityCore/commit/17665c929c3a9fb7fe75dd680648129bc1c1f874)
This commit is contained in:
hondacrx
2022-10-04 10:00:07 -04:00
parent d21624fd87
commit d3dde761a2
27 changed files with 907 additions and 431 deletions
+13 -2
View File
@@ -1628,9 +1628,20 @@ namespace Framework.Constants
Scenario = 5 Scenario = 5
} }
public enum MapDifficultyFlags : byte public enum MapDifficultyFlags : int
{ {
CannotExtend = 0x10 LimitToPlayersFromOneRealm = 0x01,
UseLootBasedLockInsteadOfInstanceLock = 0x02, // Lock to single encounters
LockedToSoloOwner = 0x04,
ResumeDungeonProgressBasedOnLockout = 0x08, // Mythic dungeons with this flag zone into leaders instance instead of always using a fresh one (Return to Karazhan, Operation: Mechagon)
DisableLockExtension = 0x10,
}
public enum MapDifficultyResetInterval : byte
{
Anytime = 0,
Daily = 1,
Weekly = 2
} }
public enum AreaMountFlags public enum AreaMountFlags
+3
View File
@@ -71,6 +71,8 @@ namespace Framework.Constants
public const string VMapMagic = "VMAP_4.B"; public const string VMapMagic = "VMAP_4.B";
public const float VMAPInvalidHeightValue = -200000.0f; public const float VMAPInvalidHeightValue = -200000.0f;
public const uint MaxDungeonEncountersPerBoss = 4;
} }
public enum NewWorldReason public enum NewWorldReason
@@ -182,6 +184,7 @@ namespace Framework.Constants
CannotEnterNotInRaid, // Target Instance Is A Raid Instance And The Player Is Not In A Raid Group CannotEnterNotInRaid, // Target Instance Is A Raid Instance And The Player Is Not In A Raid Group
CannotEnterCorpseInDifferentInstance, // Player Is Dead And Their Corpse Is Not In Target Instance CannotEnterCorpseInDifferentInstance, // Player Is Dead And Their Corpse Is Not In Target Instance
CannotEnterInstanceBindMismatch, // Player'S Permanent Instance Save Is Not Compatible With Their Group'S Current Instance Bind CannotEnterInstanceBindMismatch, // Player'S Permanent Instance Save Is Not Compatible With Their Group'S Current Instance Bind
CannotEnterAlreadyCompletedEncounter, // Player is locked to encounter that wasn't defeated in the instance yet
CannotEnterTooManyInstances, // Player Has Entered Too Many Instances Recently CannotEnterTooManyInstances, // Player Has Entered Too Many Instances Recently
CannotEnterMaxPlayers, // Target Map Already Has The Maximum Number Of Players Allowed CannotEnterMaxPlayers, // Target Map Already Has The Maximum Number Of Players Allowed
CannotEnterZoneInCombat, // A Boss Encounter Is Currently In Progress On The Target Map CannotEnterZoneInCombat, // A Boss Encounter Is Currently In Progress On The Target Map
+2 -3
View File
@@ -943,9 +943,8 @@ namespace Framework.Constants
public enum MapFlags public enum MapFlags
{ {
CanToggleDifficulty = 0x0100, CanToggleDifficulty = 0x100,
FlexLocking = 0x8000, // All difficulties share completed encounters lock, not bound to a single instance id FlexLocking = 0x8000,
// heroic difficulty flag overrides it and uses instance id bind
Garrison = 0x4000000 Garrison = 0x4000000
} }
+1 -1
View File
@@ -324,7 +324,7 @@ namespace Framework.Database
} }
} }
public void EscapeString(ref string str) public static void EscapeString(ref string str)
{ {
str = MySqlHelper.EscapeString(str); str = MySqlHelper.EscapeString(str);
} }
+1
View File
@@ -417,6 +417,7 @@ public enum LogFilter
Garrison, Garrison,
Gameevent, Gameevent,
Guild, Guild,
Instance,
Lfg, Lfg,
Loot, Loot,
MapsScript, MapsScript,
-16
View File
@@ -103,22 +103,6 @@ namespace Game.Chat
} }
} }
// if the player or the player's group is bound to another instance
// the player will not be bound to another one
InstanceBind bind = _player.GetBoundInstance(target.GetMapId(), target.GetDifficultyID(map.GetEntry()));
if (bind == null)
{
Group group = _player.GetGroup();
// if no bind exists, create a solo bind
InstanceBind gBind = group ? group.GetBoundInstance(target) : null; // if no bind exists, create a solo bind
if (gBind == null)
{
InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(target.GetInstanceId());
if (save != null)
_player.BindToInstance(save, !save.CanReset());
}
}
if (map.IsRaid()) if (map.IsRaid())
{ {
_player.SetRaidDifficultyID(target.GetRaidDifficultyID()); _player.SetRaidDifficultyID(target.GetRaidDifficultyID());
+1 -1
View File
@@ -334,7 +334,7 @@ namespace Game.Chat
if (player == null) if (player == null)
return false; return false;
DB.World.EscapeString(ref normalizedName); WorldDatabase.EscapeString(ref normalizedName);
SQLResult result = DB.World.Query($"SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id = ct.entry WHERE ct.name LIKE '{normalizedName}'"); SQLResult result = DB.World.Query($"SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id = ct.entry WHERE ct.name LIKE '{normalizedName}'");
if (result.IsEmpty()) if (result.IsEmpty())
+10 -4
View File
@@ -105,6 +105,7 @@ namespace Game.DataStorage
} }
public bool IsDynamicDifficultyMap() { return (Flags[0] & MapFlags.CanToggleDifficulty) != 0; } public bool IsDynamicDifficultyMap() { return (Flags[0] & MapFlags.CanToggleDifficulty) != 0; }
public bool IsFlexLocking() { return (Flags[0] & MapFlags.FlexLocking) != 0; }
public bool IsGarrison() { return (Flags[0] & MapFlags.Garrison) != 0; } public bool IsGarrison() { return (Flags[0] & MapFlags.Garrison) != 0; }
public bool IsSplitByFaction() { return Id == 609 || Id == 2175; } public bool IsSplitByFaction() { return Id == 609 || Id == 2175; }
} }
@@ -126,19 +127,24 @@ namespace Game.DataStorage
public LocalizedString Message; // m_message_lang (text showed when transfer to map failed) public LocalizedString Message; // m_message_lang (text showed when transfer to map failed)
public uint DifficultyID; public uint DifficultyID;
public int LockID; public int LockID;
public byte ResetInterval; public MapDifficultyResetInterval ResetInterval;
public uint MaxPlayers; public uint MaxPlayers;
public int ItemContext; public int ItemContext;
public uint ItemContextPickerID; public uint ItemContextPickerID;
public int Flags; public MapDifficultyFlags Flags;
public int ContentTuningID; public int ContentTuningID;
public uint MapID; public uint MapID;
public bool HasResetSchedule() { return ResetInterval != MapDifficultyResetInterval.Anytime; }
public bool IsUsingEncounterLocks() { return Flags.HasFlag(MapDifficultyFlags.UseLootBasedLockInsteadOfInstanceLock); }
public bool IsRestoringDungeonState() { return Flags.HasFlag(MapDifficultyFlags.ResumeDungeonProgressBasedOnLockout); }
public bool IsExtendable() { return !Flags.HasFlag(MapDifficultyFlags.DisableLockExtension); }
public uint GetRaidDuration() public uint GetRaidDuration()
{ {
if (ResetInterval == 1) if (ResetInterval == MapDifficultyResetInterval.Daily)
return 86400; return 86400;
if (ResetInterval == 2) if (ResetInterval == MapDifficultyResetInterval.Weekly)
return 604800; return 604800;
return 0; return 0;
} }
+3 -3
View File
@@ -2863,7 +2863,7 @@ namespace Game.Entities
ObjectGuid transGUID = ObjectGuid.Create(HighGuid.Transport, transguid); ObjectGuid transGUID = ObjectGuid.Create(HighGuid.Transport, transguid);
Transport transport = null; Transport transport = null;
Map transportMap = Global.MapMgr.CreateMap(mapId, this, instanceId); Map transportMap = Global.MapMgr.CreateMap(mapId, this);
if (transportMap != null) if (transportMap != null)
{ {
Transport transportOnMap = transportMap.GetTransport(transGUID); Transport transportOnMap = transportMap.GetTransport(transGUID);
@@ -2873,7 +2873,7 @@ namespace Game.Entities
{ {
mapId = transportOnMap.GetExpectedMapId(); mapId = transportOnMap.GetExpectedMapId();
instanceId = 0; instanceId = 0;
transportMap = Global.MapMgr.CreateMap(mapId, this, instanceId); transportMap = Global.MapMgr.CreateMap(mapId, this);
if (transportMap) if (transportMap)
transport = transportMap.GetTransport(transGUID); transport = transportMap.GetTransport(transGUID);
} }
@@ -2990,7 +2990,7 @@ namespace Game.Entities
// NOW player must have valid map // NOW player must have valid map
// load the player's map here if it's not already loaded // load the player's map here if it's not already loaded
if (!map) if (!map)
map = Global.MapMgr.CreateMap(mapId, this, instance_id); map = Global.MapMgr.CreateMap(mapId, this);
AreaTriggerStruct areaTrigger = null; AreaTriggerStruct areaTrigger = null;
bool check = false; bool check = false;
+1 -1
View File
@@ -79,6 +79,7 @@ namespace Game.Entities
GroupUpdateCounter[] m_groupUpdateSequences = new GroupUpdateCounter[2]; GroupUpdateCounter[] m_groupUpdateSequences = new GroupUpdateCounter[2];
public Dictionary<Difficulty, Dictionary<uint, InstanceBind>> m_boundInstances = new(); public Dictionary<Difficulty, Dictionary<uint, InstanceBind>> m_boundInstances = new();
Dictionary<uint, uint> m_recentInstances = new();
Dictionary<uint, long> _instanceResetTimes = new(); Dictionary<uint, long> _instanceResetTimes = new();
uint _pendingBindId; uint _pendingBindId;
uint _pendingBindTimer; uint _pendingBindTimer;
@@ -87,7 +88,6 @@ namespace Game.Entities
Difficulty m_dungeonDifficulty; Difficulty m_dungeonDifficulty;
Difficulty m_raidDifficulty; Difficulty m_raidDifficulty;
Difficulty m_legacyRaidDifficulty; Difficulty m_legacyRaidDifficulty;
Difficulty m_prevMapDifficulty;
//Movement //Movement
public PlayerTaxi m_taxi = new(); public PlayerTaxi m_taxi = new();
+29 -41
View File
@@ -355,7 +355,7 @@ namespace Game.Entities
} }
if (pair.Value.perm) if (pair.Value.perm)
GetSession().SendCalendarRaidLockout(pair.Value.save, false); GetSession().SendCalendarRaidLockoutRemoved(pair.Value.save);
pair.Value.save.RemovePlayer(this); // save can become invalid pair.Value.save.RemovePlayer(this); // save can become invalid
difficultyDic.Remove(pair.Key); difficultyDic.Remove(pair.Key);
@@ -436,20 +436,14 @@ namespace Game.Entities
return null; return null;
} }
public void BindToInstance() public void ConfirmPendingBind()
{ {
InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(_pendingBindId); InstanceMap map = GetMap().ToInstanceMap();
if (mapSave == null) //it seems sometimes mapSave is NULL, but I did not check why if (map == null || map.GetInstanceId() != _pendingBindId)
return; return;
InstanceSaveCreated data = new();
data.Gm = IsGameMaster();
SendPacket(data);
if (!IsGameMaster()) if (!IsGameMaster())
{ map.CreateInstanceLockForPlayer(this);
BindToInstance(mapSave, true, BindExtensionState.Keep);
GetSession().SendCalendarRaidLockout(mapSave, true);
}
} }
public void SetPendingBind(uint instanceId, uint bindTimer) public void SetPendingBind(uint instanceId, uint bindTimer)
@@ -460,41 +454,25 @@ namespace Game.Entities
public void SendRaidInfo() public void SendRaidInfo()
{ {
DateTime now = GameTime.GetSystemTime();
var instanceLocks = Global.InstanceLockMgr.GetInstanceLocksForPlayer(GetGUID());
InstanceInfoPkt instanceInfo = new(); InstanceInfoPkt instanceInfo = new();
long now = GameTime.GetGameTime(); foreach (InstanceLock instanceLock in instanceLocks)
foreach (var difficultyDic in m_boundInstances.Values)
{ {
foreach (var instanceBind in difficultyDic.Values) InstanceLockPkt lockInfos = new();
{ lockInfos.InstanceID = instanceLock.GetInstanceId();
if (instanceBind.perm) lockInfos.MapID = instanceLock.GetMapId();
{ lockInfos.DifficultyID = (uint)instanceLock.GetDifficultyId();
InstanceSave save = instanceBind.save; lockInfos.TimeRemaining = (int)(instanceLock.GetEffectiveExpiryTime() - now).TotalSeconds;
lockInfos.CompletedMask = instanceLock.GetData().CompletedEncountersMask;
InstanceLock lockInfos; lockInfos.Locked = !instanceLock.IsExpired();
lockInfos.InstanceID = save.GetInstanceId(); lockInfos.Extended = instanceLock.IsExtended();
lockInfos.MapID = save.GetMapId();
lockInfos.DifficultyID = (uint)save.GetDifficultyID();
if (instanceBind.extendState != BindExtensionState.Extended)
lockInfos.TimeRemaining = (int)(save.GetResetTime() - now);
else
lockInfos.TimeRemaining = (int)(Global.InstanceSaveMgr.GetSubsequentResetTime(save.GetMapId(), save.GetDifficultyID(), save.GetResetTime()) - now);
lockInfos.CompletedMask = 0; instanceInfo.LockList.Add(lockInfos);
Map map = Global.MapMgr.FindMap(save.GetMapId(), save.GetInstanceId());
if (map != null)
{
InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript();
if (instanceScript != null)
lockInfos.CompletedMask = instanceScript.GetCompletedEncounterMask();
}
lockInfos.Locked = instanceBind.extendState != BindExtensionState.Expired;
lockInfos.Extended = instanceBind.extendState == BindExtensionState.Extended;
instanceInfo.LockList.Add(lockInfos);
}
}
} }
SendPacket(instanceInfo); SendPacket(instanceInfo);
@@ -820,5 +798,15 @@ namespace Game.Entities
if (HasAuraType(AuraType.ForceBeathBar)) if (HasAuraType(AuraType.ForceBeathBar))
m_MirrorTimerFlags |= PlayerUnderwaterState.InWater; m_MirrorTimerFlags |= PlayerUnderwaterState.InWater;
} }
public uint GetRecentInstanceId(uint mapId)
{
return m_recentInstances.LookupByKey(mapId);
}
public void SetRecentInstance(uint mapId, uint instanceId)
{
m_recentInstances[mapId] = instanceId;
}
} }
} }
+7 -16
View File
@@ -78,7 +78,6 @@ namespace Game.Entities
m_dungeonDifficulty = Difficulty.Normal; m_dungeonDifficulty = Difficulty.Normal;
m_raidDifficulty = Difficulty.NormalRaid; m_raidDifficulty = Difficulty.NormalRaid;
m_legacyRaidDifficulty = Difficulty.Raid10N; m_legacyRaidDifficulty = Difficulty.Raid10N;
m_prevMapDifficulty = Difficulty.NormalRaid;
m_InstanceValid = true; m_InstanceValid = true;
_specializationInfo = new SpecializationInfo(); _specializationInfo = new SpecializationInfo();
@@ -580,7 +579,7 @@ namespace Game.Entities
{ {
// Player left the instance // Player left the instance
if (_pendingBindId == GetInstanceId()) if (_pendingBindId == GetInstanceId())
BindToInstance(); ConfirmPendingBind();
SetPendingBind(0, 0); SetPendingBind(0, 0);
} }
else else
@@ -4051,8 +4050,8 @@ namespace Game.Entities
corpse.UpdatePositionData(); corpse.UpdatePositionData();
corpse.SetZoneScript(); corpse.SetZoneScript();
// we do not need to save corpses for BG/arenas // we do not need to save corpses for instances
if (!GetMap().IsBattlegroundOrArena()) if (!GetMap().Instanceable())
corpse.SaveToDB(); corpse.SaveToDB();
return corpse; return corpse;
@@ -5304,20 +5303,12 @@ namespace Game.Entities
// raid downscaling - send difficulty to player // raid downscaling - send difficulty to player
if (GetMap().IsRaid()) if (GetMap().IsRaid())
{ {
m_prevMapDifficulty = GetMap().GetDifficultyID(); Difficulty mapDifficulty = GetMap().GetDifficultyID();
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(m_prevMapDifficulty); var difficulty = CliDB.DifficultyStorage.LookupByKey(mapDifficulty);
SendRaidDifficulty(difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy), (int)m_prevMapDifficulty); SendRaidDifficulty((difficulty.Flags & DifficultyFlags.Legacy) != 0, (int)mapDifficulty);
} }
else if (GetMap().IsNonRaidDungeon()) else if (GetMap().IsNonRaidDungeon())
{ SendDungeonDifficulty((int)GetMap().GetDifficultyID());
m_prevMapDifficulty = GetMap().GetDifficultyID();
SendDungeonDifficulty((int)m_prevMapDifficulty);
}
else if (!GetMap().Instanceable())
{
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(m_prevMapDifficulty);
SendRaidDifficulty(difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy));
}
PhasingHandler.OnMapChange(this); PhasingHandler.OnMapChange(this);
-26
View File
@@ -917,32 +917,6 @@ namespace Game.Entities
summoner.ToGameObject().GetAI()?.SummonedCreatureDies(creature, attacker); summoner.ToGameObject().GetAI()?.SummonedCreatureDies(creature, attacker);
} }
} }
// Dungeon specific stuff, only applies to players killing creatures
if (creature.GetInstanceId() != 0)
{
Map instanceMap = creature.GetMap();
/// @todo do instance binding anyway if the charmer/owner is offline
if (instanceMap.IsDungeon() && ((attacker != null && attacker.GetCharmerOrOwnerPlayerOrPlayerItself() != null) || attacker == victim))
{
if (instanceMap.IsRaidOrHeroicDungeon())
{
if (creature.GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.InstanceBind))
instanceMap.ToInstanceMap().PermBindAllPlayers();
}
else
{
// the reset time is set but not added to the scheduler
// until the players leave the instance
long resettime = GameTime.GetGameTime() + 2 * Time.Hour;
InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(creature.GetInstanceId());
if (save != null)
if (save.GetResetTime() < resettime)
save.SetResetTime(resettime);
}
}
}
} }
// outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh... // outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh...
+1
View File
@@ -74,6 +74,7 @@ public static class Global
public static WaypointManager WaypointMgr { get { return WaypointManager.Instance; } } public static WaypointManager WaypointMgr { get { return WaypointManager.Instance; } }
public static TransportManager TransportMgr { get { return TransportManager.Instance; } } public static TransportManager TransportMgr { get { return TransportManager.Instance; } }
public static InstanceSaveManager InstanceSaveMgr { get { return InstanceSaveManager.Instance; } } public static InstanceSaveManager InstanceSaveMgr { get { return InstanceSaveManager.Instance; } }
public static InstanceLockManager InstanceLockMgr { get { return InstanceLockManager.Instance; } }
public static ScenarioManager ScenarioMgr { get { return ScenarioManager.Instance; } } public static ScenarioManager ScenarioMgr { get { return ScenarioManager.Instance; } }
//PVP //PVP
+25
View File
@@ -2155,6 +2155,30 @@ namespace Game.Groups
worker(refe.GetSource()); worker(refe.GetSource());
} }
uint GetInstanceId(MapRecord mapEntry) { return 0; }
public ObjectGuid GetRecentInstanceOwner(uint mapId)
{
if (m_recentInstances.TryGetValue(mapId, out Tuple<ObjectGuid, uint> value))
return value.Item1;
return m_leaderGuid;
}
public uint GetRecentInstanceId(uint mapId)
{
if (m_recentInstances.TryGetValue(mapId, out Tuple<ObjectGuid, uint> value))
return value.Item2;
return 0;
}
public void SetRecentInstance(uint mapId, ObjectGuid instanceOwner, uint instanceId)
{
m_recentInstances[mapId] = Tuple.Create(instanceOwner, instanceId);
}
List<MemberSlot> m_memberSlots = new(); List<MemberSlot> m_memberSlots = new();
GroupRefManager m_memberMgr = new(); GroupRefManager m_memberMgr = new();
List<Player> m_invitees = new(); List<Player> m_invitees = new();
@@ -2174,6 +2198,7 @@ namespace Game.Groups
ObjectGuid m_looterGuid; ObjectGuid m_looterGuid;
ObjectGuid m_masterLooterGuid; ObjectGuid m_masterLooterGuid;
Dictionary<Difficulty, Dictionary<uint, InstanceBind>> m_boundInstances = new(); Dictionary<Difficulty, Dictionary<uint, InstanceBind>> m_boundInstances = new();
Dictionary<uint, Tuple<ObjectGuid, uint>> m_recentInstances = new();
byte[] m_subGroupsCounts; byte[] m_subGroupsCounts;
ObjectGuid m_guid; ObjectGuid m_guid;
uint m_dbStoreId; uint m_dbStoreId;
+33 -48
View File
@@ -72,27 +72,16 @@ namespace Game
packet.Events.Add(eventInfo); packet.Events.Add(eventInfo);
} }
foreach (var difficulty in CliDB.DifficultyStorage.Values) foreach (InstanceLock instanceLock in Global.InstanceLockMgr.GetInstanceLocksForPlayer(_player.GetGUID()))
{ {
var boundInstances = _player.GetBoundInstances((Difficulty)difficulty.Id); CalendarSendCalendarRaidLockoutInfo lockoutInfo = new();
if (boundInstances != null)
{
foreach (var boundInstance in boundInstances.Values)
{
if (boundInstance.perm)
{
CalendarSendCalendarRaidLockoutInfo lockoutInfo;
InstanceSave save = boundInstance.save; lockoutInfo.MapID = (int)instanceLock.GetMapId();
lockoutInfo.MapID = (int)save.GetMapId(); lockoutInfo.DifficultyID = (uint)instanceLock.GetDifficultyId();
lockoutInfo.DifficultyID = (uint)save.GetDifficultyID(); lockoutInfo.ExpireTime = (int)(instanceLock.GetEffectiveExpiryTime() - GameTime.GetSystemTime()).TotalSeconds;
lockoutInfo.ExpireTime = save.GetResetTime() - currTime; lockoutInfo.InstanceID = instanceLock.GetInstanceId();
lockoutInfo.InstanceID = save.GetInstanceId(); // instance save id as unique instance copy id
packet.RaidLockouts.Add(lockoutInfo); packet.RaidLockouts.Add(lockoutInfo);
}
}
}
} }
SendPacket(packet); SendPacket(packet);
@@ -564,39 +553,17 @@ namespace Game
player.BindToInstance(instanceBind.save, true, newState, false); player.BindToInstance(instanceBind.save, true, newState, false);
} }
/*
InstancePlayerBind* instanceBind = GetPlayer().GetBoundInstance(setSavedInstanceExtend.MapID, Difficulty(setSavedInstanceExtend.DifficultyID);
if (!instanceBind || !instanceBind.save)
return;
InstanceSave* save = instanceBind.save;
// http://www.wowwiki.com/Instance_Lock_Extension
// SendCalendarRaidLockoutUpdated(save);
*/
} }
public void SendCalendarRaidLockout(InstanceSave save, bool add) public void SendCalendarRaidLockoutAdded(InstanceLock instanceLock)
{ {
long currTime = GameTime.GetGameTime(); CalendarRaidLockoutAdded calendarRaidLockoutAdded = new();
calendarRaidLockoutAdded.InstanceID = instanceLock.GetInstanceId();
if (add) calendarRaidLockoutAdded.ServerTime = (uint)GameTime.GetGameTime();
{ calendarRaidLockoutAdded.MapID = (int)instanceLock.GetMapId();
CalendarRaidLockoutAdded calendarRaidLockoutAdded = new(); calendarRaidLockoutAdded.DifficultyID = instanceLock.GetDifficultyId();
calendarRaidLockoutAdded.InstanceID = save.GetInstanceId(); calendarRaidLockoutAdded.TimeRemaining = (int)(instanceLock.GetExpiryTime() - GameTime.GetSystemTime()).TotalSeconds;
calendarRaidLockoutAdded.ServerTime = (uint)currTime; SendPacket(calendarRaidLockoutAdded);
calendarRaidLockoutAdded.MapID = (int)save.GetMapId();
calendarRaidLockoutAdded.DifficultyID = save.GetDifficultyID();
calendarRaidLockoutAdded.TimeRemaining = (int)(save.GetResetTime() - currTime);
SendPacket(calendarRaidLockoutAdded);
}
else
{
CalendarRaidLockoutRemoved calendarRaidLockoutRemoved = new();
calendarRaidLockoutRemoved.InstanceID = save.GetInstanceId();
calendarRaidLockoutRemoved.MapID = (int)save.GetMapId();
calendarRaidLockoutRemoved.DifficultyID = save.GetDifficultyID();
SendPacket(calendarRaidLockoutRemoved);
}
} }
public void SendCalendarRaidLockoutUpdated(InstanceSave save) public void SendCalendarRaidLockoutUpdated(InstanceSave save)
@@ -614,5 +581,23 @@ namespace Game
SendPacket(packet); SendPacket(packet);
} }
public void SendCalendarRaidLockoutRemoved(InstanceSave save)
{
CalendarRaidLockoutRemoved calendarRaidLockoutRemoved = new();
calendarRaidLockoutRemoved.InstanceID = save.GetInstanceId();
calendarRaidLockoutRemoved.MapID = (int)save.GetMapId();
calendarRaidLockoutRemoved.DifficultyID = save.GetDifficultyID();
SendPacket(calendarRaidLockoutRemoved);
}
void SendCalendarRaidLockoutRemoved(InstanceLock instanceLock)
{
CalendarRaidLockoutRemoved calendarRaidLockoutRemoved = new();
calendarRaidLockoutRemoved.InstanceID = instanceLock.GetInstanceId();
calendarRaidLockoutRemoved.MapID = (int)instanceLock.GetMapId();
calendarRaidLockoutRemoved.DifficultyID = instanceLock.GetDifficultyId();
SendPacket(calendarRaidLockoutRemoved);
}
} }
} }
+1 -1
View File
@@ -1312,7 +1312,7 @@ namespace Game
for (int i = 0; i < SharedConst.MaxDeclinedNameCases; ++i) for (int i = 0; i < SharedConst.MaxDeclinedNameCases; ++i)
{ {
string declinedName = packet.DeclinedNames.name[i]; string declinedName = packet.DeclinedNames.name[i];
DB.Characters.EscapeString(ref declinedName); CharacterDatabase.EscapeString(ref declinedName);
packet.DeclinedNames.name[i] = declinedName; packet.DeclinedNames.name[i] = declinedName;
} }
+9 -13
View File
@@ -304,18 +304,14 @@ namespace Game
Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' does not have a corpse in instance map {1} and cannot enter", player.GetName(), at.target_mapId); Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' does not have a corpse in instance map {1} and cannot enter", player.GetName(), at.target_mapId);
break; break;
case EnterState.CannotEnterInstanceBindMismatch: case EnterState.CannotEnterInstanceBindMismatch:
{ player.SendTransferAborted(at.target_mapId, TransferAbortReason.LockedToDifferentInstance);
MapRecord entry = CliDB.MapStorage.LookupByKey(at.target_mapId); Log.outDebug(LogFilter.Maps, $"MAP: Player '{player.GetName()}' cannot enter instance map {at.target_mapId} because their permanent bind is incompatible with their group's");
if (entry != null) reviveAtTrigger = true;
{ break;
string mapName = entry.MapName[player.GetSession().GetSessionDbcLocale()]; case EnterState.CannotEnterAlreadyCompletedEncounter:
Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' cannot enter instance map '{1}' because their permanent bind is incompatible with their group's", player.GetName(), mapName); player.SendTransferAborted(at.target_mapId, TransferAbortReason.AlreadyCompletedEncounter);
// is there a special opcode for this? Log.outDebug(LogFilter.Maps, $"MAP: Player '{player.GetName()}' cannot enter instance map {at.target_mapId} because their permanent bind is incompatible with their group's");
// @todo figure out how to get player localized difficulty string (e.g. "10 player", "Heroic" etc) reviveAtTrigger = true;
player.SendSysMessage(CypherStrings.InstanceBindMismatch, mapName);
}
reviveAtTrigger = true;
}
break; break;
case EnterState.CannotEnterTooManyInstances: case EnterState.CannotEnterTooManyInstances:
player.SendTransferAborted(at.target_mapId, TransferAbortReason.TooManyInstances); player.SendTransferAborted(at.target_mapId, TransferAbortReason.TooManyInstances);
@@ -783,7 +779,7 @@ namespace Game
} }
if (packet.AcceptLock) if (packet.AcceptLock)
GetPlayer().BindToInstance(); GetPlayer().ConfirmPendingBind();
else else
GetPlayer().RepopAtGraveyard(); GetPlayer().RepopAtGraveyard();
@@ -0,0 +1,513 @@
/*
* 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.Configuration;
using Framework.Constants;
using Framework.Database;
using Game.DataStorage;
using Game.Entities;
using System;
using System.Collections.Generic;
namespace Game.Maps
{
//#define INSTANCE_ID_HIGH_MASK 0x1F440000
//#define INSTANCE_ID_LFG_MASK 0x00000001
//#define INSTANCE_ID_NORMAL_MASK 0x00010000
using InstanceLockKey = Tuple<uint, uint>;
//using PlayerLockMap = std::unordered_map<InstanceLockKey, std::unique_ptr<InstanceLock>>;
//using LockMap = std::unordered_map<ObjectGuid, PlayerLockMap>;
public class InstanceLockManager : Singleton<InstanceLockManager>
{
Dictionary<ObjectGuid, Dictionary<InstanceLockKey, InstanceLock>> _temporaryInstanceLocksByPlayer = new(); // locks stored here before any boss gets killed
Dictionary<ObjectGuid, Dictionary<InstanceLockKey, InstanceLock>> _instanceLocksByPlayer = new();
Dictionary<uint, SharedInstanceLockData> _instanceLockDataById = new();
bool _unloading;
InstanceLockManager() { }
public void Load()
{
Dictionary<uint, SharedInstanceLockData> instanceLockDataById = new();
// 0 1 2
SQLResult result = DB.Characters.Query("SELECT instanceId, data, completedEncountersMask FROM instance");
if (!result.IsEmpty())
{
do
{
uint instanceId = result.Read<uint>(0);
SharedInstanceLockData data = new();
data.Data = result.Read<string>(1);
data.CompletedEncountersMask = result.Read<uint>(2);
data.InstanceId = instanceId;
instanceLockDataById[instanceId] = data;
} while (result.NextRow());
}
// 0 1 2 3 4 5 6 7 8
SQLResult lockResult = DB.Characters.Query("SELECT guid, mapId, lockId, instanceId, difficulty, data, completedEncountersMask, expiryTime, extended FROM character_instance_lock");
if (!result.IsEmpty())
{
do
{
ObjectGuid playerGuid = ObjectGuid.Create(HighGuid.Player, lockResult.Read<ulong>(0));
uint mapId = lockResult.Read<uint>(1);
uint lockId = lockResult.Read<uint>(2);
uint instanceId = lockResult.Read<uint>(3);
Difficulty difficulty = (Difficulty)lockResult.Read<byte>(4);
DateTime expiryTime = Time.UnixTimeToDateTime(lockResult.Read<long>(7));
// Mark instance id as being used
Global.MapMgr.RegisterInstanceId(instanceId);
InstanceLock instanceLock;
if (new MapDb2Entries(mapId, difficulty).IsInstanceIdBound())
{
var sharedData = instanceLockDataById.LookupByKey(instanceId);
if (sharedData == null)
{
Log.outError(LogFilter.Instance, $"Missing instance data for instance id based lock (id {instanceId})");
DB.Characters.Query($"DELETE FROM character_instance_lock WHERE instanceId = {instanceId}");
continue;
}
instanceLock = new SharedInstanceLock(mapId, difficulty, expiryTime, instanceId, sharedData);
_instanceLockDataById[instanceId] = sharedData;
}
else
instanceLock = new InstanceLock(mapId, difficulty, expiryTime, instanceId);
instanceLock.GetData().Data = lockResult.Read<string>(5);
instanceLock.GetData().CompletedEncountersMask = lockResult.Read<uint>(6);
instanceLock.SetExtended(lockResult.Read<bool>(8));
_instanceLocksByPlayer[playerGuid][Tuple.Create(mapId, lockId)] = instanceLock;
} while (result.NextRow());
}
}
public void Unload()
{
_unloading = true;
_instanceLocksByPlayer.Clear();
_instanceLockDataById.Clear();
}
public TransferAbortReason CanJoinInstanceLock(ObjectGuid playerGuid, MapDb2Entries entries, InstanceLock instanceLock)
{
if (!entries.MapDifficulty.HasResetSchedule())
return TransferAbortReason.None;
InstanceLock playerInstanceLock = FindActiveInstanceLock(playerGuid, entries);
if (playerInstanceLock == null)
return TransferAbortReason.None;
if (entries.Map.IsFlexLocking())
{
// compare completed encounters - if instance has any encounters unkilled in players lock then cannot enter
if ((playerInstanceLock.GetData().CompletedEncountersMask & ~instanceLock.GetData().CompletedEncountersMask) != 0)
return TransferAbortReason.AlreadyCompletedEncounter;
return TransferAbortReason.None;
}
if (!entries.MapDifficulty.IsUsingEncounterLocks() && playerInstanceLock.GetInstanceId() != 0 && playerInstanceLock.GetInstanceId() != instanceLock.GetInstanceId())
return TransferAbortReason.LockedToDifferentInstance;
return TransferAbortReason.None;
}
public InstanceLock FindInstanceLock(Dictionary<ObjectGuid, Dictionary<InstanceLockKey, InstanceLock>> locks, ObjectGuid playerGuid, MapDb2Entries entries)
{
var playerLocks = locks.LookupByKey(playerGuid);
if (playerLocks == null)
return null;
return playerLocks.LookupByKey(entries.GetKey());
}
public InstanceLock FindActiveInstanceLock(ObjectGuid playerGuid, MapDb2Entries entries)
{
return FindActiveInstanceLock(playerGuid, entries, false, true);
}
public InstanceLock FindActiveInstanceLock(ObjectGuid playerGuid, MapDb2Entries entries, bool ignoreTemporary, bool ignoreExpired)
{
InstanceLock instanceLock = FindInstanceLock(_instanceLocksByPlayer, playerGuid, entries);
// Ignore expired and not extended locks
if (instanceLock != null && (!instanceLock.IsExpired() || instanceLock.IsExtended() || !ignoreExpired))
return instanceLock;
if (ignoreTemporary)
return null;
return FindInstanceLock(_temporaryInstanceLocksByPlayer, playerGuid, entries);
}
public ICollection<InstanceLock> GetInstanceLocksForPlayer(ObjectGuid playerGuid)
{
return _instanceLocksByPlayer.LookupByKey(playerGuid)?.Values;
}
public InstanceLock CreateInstanceLockForNewInstance(ObjectGuid playerGuid, MapDb2Entries entries, uint instanceId)
{
if (!entries.MapDifficulty.HasResetSchedule())
return null;
InstanceLock instanceLock;
if (entries.IsInstanceIdBound())
{
SharedInstanceLockData sharedData = new();
_instanceLockDataById[instanceId] = sharedData;
instanceLock = new SharedInstanceLock(entries.MapDifficulty.MapID, (Difficulty)entries.MapDifficulty.DifficultyID,
GetNextResetTime(entries), 0, sharedData);
}
else
instanceLock = new InstanceLock(entries.MapDifficulty.MapID, (Difficulty)entries.MapDifficulty.DifficultyID,
GetNextResetTime(entries), 0);
_temporaryInstanceLocksByPlayer[playerGuid][entries.GetKey()] = instanceLock;
Log.outDebug(LogFilter.Instance, $"[{entries.Map.Id}-{entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()]} | " +
$"{entries.MapDifficulty.DifficultyID}-{CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name}] Created new temporary instance lock for {playerGuid} in instance {instanceId}");
return instanceLock;
}
public InstanceLock UpdateInstanceLockForPlayer(SQLTransaction trans, ObjectGuid playerGuid, MapDb2Entries entries, InstanceLockUpdateEvent updateEvent)
{
InstanceLock instanceLock = FindActiveInstanceLock(playerGuid, entries, true, true);
if (instanceLock == null)
{
// Move lock from temporary storage if it exists there
// This is to avoid destroying expired locks before any boss is killed in a fresh lock
// player can still change his mind, exit instance and reactivate old lock
var playerLocks = _temporaryInstanceLocksByPlayer.LookupByKey(playerGuid);
if (playerLocks != null)
{
var playerInstanceLock = playerLocks.LookupByKey(entries.GetKey());
if (playerInstanceLock != null)
{
instanceLock = playerInstanceLock;
_instanceLocksByPlayer[playerGuid][entries.GetKey()] = instanceLock;
playerLocks.Remove(entries.GetKey());
if (playerLocks.Empty())
_temporaryInstanceLocksByPlayer.Remove(playerGuid);
Log.outDebug(LogFilter.Instance, $"[{entries.Map.Id}-{entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()]} | " +
$"{entries.MapDifficulty.DifficultyID}-{CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name}] Promoting temporary lock to permanent for {playerGuid} in instance {updateEvent.InstanceId}");
}
}
}
if (instanceLock == null)
{
if (entries.IsInstanceIdBound())
{
var sharedDataItr = _instanceLockDataById.LookupByKey(updateEvent.InstanceId);
Cypher.Assert(sharedDataItr != null);
instanceLock = new SharedInstanceLock(entries.MapDifficulty.MapID, (Difficulty)entries.MapDifficulty.DifficultyID,
GetNextResetTime(entries), updateEvent.InstanceId, sharedDataItr);
Cypher.Assert((instanceLock as SharedInstanceLock).GetSharedData().InstanceId == updateEvent.InstanceId);
}
else
instanceLock = new InstanceLock(entries.MapDifficulty.MapID, (Difficulty)entries.MapDifficulty.DifficultyID,
GetNextResetTime(entries), updateEvent.InstanceId);
_instanceLocksByPlayer[playerGuid][entries.GetKey()] = instanceLock;
Log.outDebug(LogFilter.Instance, $"[{entries.Map.Id}-{entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()]} | " +
$"{entries.MapDifficulty.DifficultyID}-{CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name}] Created new instance lock for {playerGuid} in instance {updateEvent.InstanceId}");
}
else
{
if (entries.IsInstanceIdBound())
{
Cypher.Assert(instanceLock.GetInstanceId() == 0 || instanceLock.GetInstanceId() == updateEvent.InstanceId);
var sharedDataItr = _instanceLockDataById.LookupByKey(updateEvent.InstanceId);
Cypher.Assert(sharedDataItr != null);
Cypher.Assert(sharedDataItr == (instanceLock as SharedInstanceLock).GetSharedData());
}
instanceLock.SetInstanceId(updateEvent.InstanceId);
}
instanceLock.GetData().Data = updateEvent.NewData;
if (updateEvent.CompletedEncounter != null)
{
instanceLock.GetData().CompletedEncountersMask |= 1u << updateEvent.CompletedEncounter.Bit;
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} in instance {updateEvent.InstanceId} gains completed encounter [{updateEvent.CompletedEncounter.Id}-{updateEvent.CompletedEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]}]");
}
// Synchronize map completed encounters into players completed encounters for UI
if (!entries.MapDifficulty.IsUsingEncounterLocks())
instanceLock.GetData().CompletedEncountersMask |= updateEvent.InstanceCompletedEncountersMask;
if (instanceLock.IsExpired())
{
Cypher.Assert(instanceLock.IsExtended(), "Instance lock must have been extended to create instance map from it");
instanceLock.SetExpiryTime(GetNextResetTime(entries));
instanceLock.SetExtended(false);
Log.outDebug(LogFilter.Instance, $"[{entries.Map.Id}-{entries.Map.MapName[Global.WorldMgr.GetDefaultDbcLocale()]} | " +
$"{entries.MapDifficulty.DifficultyID}-{CliDB.DifficultyStorage.LookupByKey(entries.MapDifficulty.DifficultyID).Name}] Expired instance lock for {playerGuid} in instance {updateEvent.InstanceId} is now active");
}
// TODO: DB SAVE IN TRANSACTION
trans.Append($"DELETE FROM character_instance_lock WHERE guid={playerGuid.GetCounter()} AND mapId={entries.MapDifficulty.MapID} AND lockId={entries.MapDifficulty.LockID}");
string escapedData = instanceLock.GetData().Data;
CharacterDatabase.EscapeString(ref escapedData);
trans.Append($"INSERT INTO character_instance_lock (guid, mapId, lockId, instanceId, difficulty, data, completedEncountersMask, entranceWorldSafeLocId, expiryTime, extended) " +
$"VALUES ({playerGuid.GetCounter()}, {entries.MapDifficulty.MapID}, {entries.MapDifficulty.LockID}, {instanceLock.GetInstanceId()}, {entries.MapDifficulty.DifficultyID}, \"{escapedData}\", " +
$"{instanceLock.GetData().CompletedEncountersMask}, {instanceLock.GetData().EntranceWorldSafeLocId}, {Time.DateTimeToUnixTime(instanceLock.GetExpiryTime())}, {(instanceLock.IsExtended() ? 1 : 0)}");
return instanceLock;
}
public void UpdateSharedInstanceLock(SQLTransaction trans, InstanceLockUpdateEvent updateEvent)
{
var sharedData = _instanceLockDataById.LookupByKey(updateEvent.InstanceId);
Cypher.Assert(sharedData != null);
Cypher.Assert(sharedData.InstanceId == updateEvent.InstanceId);
sharedData.Data = updateEvent.NewData;
if (updateEvent.CompletedEncounter != null)
{
sharedData.CompletedEncountersMask |= 1u << updateEvent.CompletedEncounter.Bit;
Log.outDebug(LogFilter.Instance, $"Instance {updateEvent.InstanceId} gains completed encounter [{updateEvent.CompletedEncounter.Id}-{updateEvent.CompletedEncounter.Name[Global.WorldMgr.GetDefaultDbcLocale()]}]");
}
trans.Append($"DELETE FROM instance2 WHERE instanceId={sharedData.InstanceId}");
string escapedData = sharedData.Data;
CharacterDatabase.EscapeString(ref escapedData);
trans.Append($"INSERT INTO instance2 (instanceId, data, completedEncountersMask, entranceWorldSafeLocId) VALUES ({sharedData.InstanceId}, \"{escapedData}\", {sharedData.CompletedEncountersMask}, {sharedData.EntranceWorldSafeLocId})");
}
public void OnSharedInstanceLockDataDelete(uint instanceId)
{
if (_unloading)
return;
_instanceLockDataById.Remove(instanceId);
DB.Characters.Execute($"DELETE FROM instance2 WHERE instanceId={instanceId}");
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)
{
InstanceLock instanceLock = FindActiveInstanceLock(playerGuid, entries, true, false);
if (instanceLock != null)
{
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");
}
}
public DateTime GetNextResetTime(MapDb2Entries entries)
{
DateTime dateTime = GameTime.GetDateAndTime();
int resetHour = ConfigMgr.GetDefaultValue("ResetSchedule.DailyHour", 9);
int hour = 0;
int day = 0;
switch (entries.MapDifficulty.ResetInterval)
{
case MapDifficultyResetInterval.Daily:
{
if (dateTime.Hour >= resetHour)
day++;
hour = resetHour;
break;
}
case MapDifficultyResetInterval.Weekly:
{
int resetDay = ConfigMgr.GetDefaultValue("ResetSchedule.WeeklyDay", 2);
int daysAdjust = resetDay - dateTime.Day;
if (dateTime.Day > resetDay || (dateTime.Day == resetDay && dateTime.Hour >= resetHour))
daysAdjust += 7; // passed it for current week, grab time from next week
hour = resetHour;
day += daysAdjust;
break;
}
default:
break;
}
return new DateTime(dateTime.Year, dateTime.Month, day, hour, 0, 0);
}
}
public class InstanceLockData
{
public string Data;
public uint CompletedEncountersMask;
public uint EntranceWorldSafeLocId;
}
public class InstanceLock
{
uint _mapId;
Difficulty _difficultyId;
uint _instanceId;
DateTime _expiryTime;
bool _extended;
InstanceLockData _data;
public InstanceLock(uint mapId, Difficulty difficultyId, DateTime expiryTime, uint instanceId)
{
_mapId = mapId;
_difficultyId = difficultyId;
_instanceId = instanceId;
_expiryTime = expiryTime;
_extended = false;
}
public bool IsExpired()
{
return _expiryTime < GameTime.GetSystemTime();
}
public DateTime GetEffectiveExpiryTime()
{
if (!IsExtended())
return GetExpiryTime();
MapDb2Entries entries = new(_mapId, _difficultyId);
// return next reset time
if (IsExpired())
return Global.InstanceLockMgr.GetNextResetTime(entries);
// if not expired, return expiration time + 1 reset period
return GetExpiryTime() + TimeSpan.FromSeconds(entries.MapDifficulty.GetRaidDuration());
}
public uint GetMapId() { return _mapId; }
public Difficulty GetDifficultyId() { return _difficultyId; }
public uint GetInstanceId() { return _instanceId; }
public virtual void SetInstanceId(uint instanceId) { _instanceId = instanceId; }
public DateTime GetExpiryTime() { return _expiryTime; }
public void SetExpiryTime(DateTime expiryTime) { _expiryTime = expiryTime; }
public bool IsExtended() { return _extended; }
public void SetExtended(bool extended) { _extended = extended; }
public InstanceLockData GetData() { return _data; }
public virtual InstanceLockData GetInstanceInitializationData() { return _data; }
}
class SharedInstanceLockData : InstanceLockData
{
public uint InstanceId;
~SharedInstanceLockData()
{
// Cleanup database
if (InstanceId != 0)
Global.InstanceLockMgr.OnSharedInstanceLockDataDelete(InstanceId);
}
}
class SharedInstanceLock : InstanceLock
{
/// <summary>
/// Instance id based locks have two states
/// One shared by everyone, which is the real state used by instance
/// and one for each player that shows in UI that might have less encounters completed
/// </summary>
SharedInstanceLockData _sharedData;
public SharedInstanceLock(uint mapId, Difficulty difficultyId, DateTime expiryTime, uint instanceId, SharedInstanceLockData sharedData) : base(mapId, difficultyId, expiryTime, instanceId)
{
_sharedData = sharedData;
}
public void SetInstanceId(uint instanceId)
{
base.SetInstanceId(instanceId);
_sharedData.InstanceId = instanceId;
}
public InstanceLockData GetInstanceInitializationData() { return _sharedData; }
public SharedInstanceLockData GetSharedData() { return _sharedData; }
}
public struct MapDb2Entries
{
public MapRecord Map;
public MapDifficultyRecord MapDifficulty;
public MapDb2Entries(uint mapId, Difficulty difficulty)
{
Map = CliDB.MapStorage.LookupByKey(mapId);
MapDifficulty = Global.DB2Mgr.GetMapDifficultyData(mapId, difficulty);
}
public MapDb2Entries(MapRecord map, MapDifficultyRecord mapDifficulty)
{
Map = map;
MapDifficulty = mapDifficulty;
}
public InstanceLockKey GetKey()
{
return Tuple.Create(MapDifficulty.MapID, (uint)MapDifficulty.LockID);
}
public bool IsInstanceIdBound()
{
return !Map.IsFlexLocking() && !MapDifficulty.IsUsingEncounterLocks();
}
}
public struct InstanceLockUpdateEvent
{
public uint InstanceId;
public string NewData;
public uint InstanceCompletedEncountersMask;
public DungeonEncounterRecord CompletedEncounter;
public InstanceLockUpdateEvent(uint instanceId, string newData, uint instanceCompletedEncountersMask, DungeonEncounterRecord completedEncounter)
{
InstanceId = instanceId;
NewData = newData;
InstanceCompletedEncountersMask = instanceCompletedEncountersMask;
CompletedEncounter = completedEncounter;
}
}
}
+81 -5
View File
@@ -18,6 +18,7 @@
using Framework.Constants; using Framework.Constants;
using Framework.Database; using Framework.Database;
using Framework.IO; using Framework.IO;
using Game.DataStorage;
using Game.Entities; using Game.Entities;
using Game.Groups; using Game.Groups;
using Game.Networking.Packets; using Game.Networking.Packets;
@@ -25,6 +26,7 @@ using Game.Scenarios;
using Game.Spells; using Game.Spells;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
namespace Game.Maps namespace Game.Maps
@@ -164,6 +166,19 @@ namespace Game.Maps
} }
} }
void LoadDungeonEncounterData(DungeonEncounterData[] encounters)
{
foreach (DungeonEncounterData encounter in encounters)
LoadDungeonEncounterData(encounter.BossId, encounter.DungeonEncounterId);
}
void LoadDungeonEncounterData(uint bossId, uint[] dungeonEncounterIds)
{
if (bossId < bosses.Count)
for (int i = 0; i < MapConst.MaxDungeonEncountersPerBoss; ++i)
bosses[bossId].DungeonEncounters[i] = CliDB.DungeonEncounterStorage.LookupByKey(dungeonEncounterIds[i]);
}
public virtual void UpdateDoorState(GameObject door) public virtual void UpdateDoorState(GameObject door)
{ {
var range = doors.LookupByKey(door.GetEntry()); var range = doors.LookupByKey(door.GetEntry());
@@ -379,6 +394,7 @@ namespace Game.Maps
} }
} }
DungeonEncounterRecord dungeonEncounter = null;
switch (state) switch (state)
{ {
case EncounterState.InProgress: case EncounterState.InProgress:
@@ -395,9 +411,18 @@ namespace Game.Maps
break; break;
} }
case EncounterState.Fail: case EncounterState.Fail:
ResetCombatResurrections();
SendEncounterEnd();
break;
case EncounterState.Done: case EncounterState.Done:
ResetCombatResurrections(); ResetCombatResurrections();
SendEncounterEnd(); SendEncounterEnd();
dungeonEncounter = bossInfo.GetDungeonEncounterForDifficulty(instance.GetDifficultyID());
if (dungeonEncounter != null)
{
DoUpdateCriteria(CriteriaType.DefeatDungeonEncounter, dungeonEncounter.Id);
SendBossKillCredit(dungeonEncounter.Id);
}
break; break;
default: default:
break; break;
@@ -405,6 +430,8 @@ namespace Game.Maps
bossInfo.state = state; bossInfo.state = state;
SaveToDB(); SaveToDB();
if (state == EncounterState.Done)
instance.UpdateInstanceLock(dungeonEncounter, new(id, state));
} }
for (uint type = 0; type < (int)DoorType.Max; ++type) for (uint type = 0; type < (int)DoorType.Max; ++type)
@@ -508,6 +535,32 @@ namespace Game.Maps
return saveStream.ToString(); return saveStream.ToString();
} }
public string UpdateSaveData(string oldData, UpdateSaveDataEvent saveEvent)
{
if (!instance.GetMapDifficulty().IsUsingEncounterLocks())
return GetSaveData();
int position = (int)(headers.Count + saveEvent.BossId) * 2;
StringBuilder newData = new(oldData);
if (position >= oldData.Length)
{
// Initialize blank data
StringBuilder saveStream = new();
WriteSaveDataHeaders(saveStream);
for (var i = 0; i < bosses.Count; ++i)
saveStream.Append($"{(uint)EncounterState.NotStarted} ");
WriteSaveDataMore(saveStream);
newData = saveStream;
}
newData.Remove(position, 2);
newData.Insert(position, $"{(uint)saveEvent.NewState}0");
return newData.ToString();
}
void WriteSaveDataHeaders(StringBuilder data) void WriteSaveDataHeaders(StringBuilder data)
{ {
foreach (char header in headers) foreach (char header in headers)
@@ -962,7 +1015,7 @@ namespace Game.Maps
Dictionary<uint, uint> _creatureInfo = new(); Dictionary<uint, uint> _creatureInfo = new();
Dictionary<uint, uint> _gameObjectInfo = new(); Dictionary<uint, uint> _gameObjectInfo = new();
Dictionary<uint, ObjectGuid> _objectGuids = new(); Dictionary<uint, ObjectGuid> _objectGuids = new();
uint completedEncounters; uint completedEncounters; // DEPRECATED, REMOVE
List<InstanceSpawnGroupInfo> _instanceSpawnGroups = new(); List<InstanceSpawnGroupInfo> _instanceSpawnGroups = new();
List<uint> _activatedAreaTriggers = new(); List<uint> _activatedAreaTriggers = new();
uint _entranceId; uint _entranceId;
@@ -972,6 +1025,11 @@ namespace Game.Maps
bool _combatResurrectionTimerStarted; bool _combatResurrectionTimerStarted;
} }
class DungeonEncounterData
{
public uint BossId;
public uint[] DungeonEncounterId = new uint[4];
}
public class DoorData public class DoorData
{ {
@@ -1025,6 +1083,12 @@ namespace Game.Maps
public class BossInfo public class BossInfo
{ {
public EncounterState state;
public List<ObjectGuid>[] door = new List<ObjectGuid>[(int)DoorType.Max];
public List<ObjectGuid> minion = new();
public List<AreaBoundary> boundary = new();
public DungeonEncounterRecord[] DungeonEncounters = new DungeonEncounterRecord[MapConst.MaxDungeonEncountersPerBoss];
public BossInfo() public BossInfo()
{ {
state = EncounterState.ToBeDecided; state = EncounterState.ToBeDecided;
@@ -1032,10 +1096,10 @@ namespace Game.Maps
door[i] = new List<ObjectGuid>(); door[i] = new List<ObjectGuid>();
} }
public EncounterState state; public DungeonEncounterRecord GetDungeonEncounterForDifficulty(Difficulty difficulty)
public List<ObjectGuid>[] door = new List<ObjectGuid>[(int)DoorType.Max]; {
public List<ObjectGuid> minion = new(); return DungeonEncounters.FirstOrDefault(dungeonEncounter => dungeonEncounter?.DifficultyID == 0 || (Difficulty)dungeonEncounter?.DifficultyID == difficulty);
public List<AreaBoundary> boundary = new(); }
} }
class DoorInfo class DoorInfo
{ {
@@ -1057,4 +1121,16 @@ namespace Game.Maps
public BossInfo bossInfo; public BossInfo bossInfo;
} }
public struct UpdateSaveDataEvent
{
public uint BossId;
public EncounterState NewState;
public UpdateSaveDataEvent(uint bossId, EncounterState state)
{
BossId = bossId;
NewState = state;
}
}
} }
+109 -178
View File
@@ -1709,35 +1709,26 @@ namespace Game.Maps
Log.outDebug(LogFilter.Maps, $"Map::CanPlayerEnter - player '{player.GetName()}' is dead but does not have a corpse!"); Log.outDebug(LogFilter.Maps, $"Map::CanPlayerEnter - player '{player.GetName()}' is dead but does not have a corpse!");
} }
//Get instance where player's group is bound & its map if (entry.Instanceable())
if (!loginCheck && group)
{ {
InstanceBind boundInstance = group.GetBoundInstance(entry); //Get instance where player's group is bound & its map
if (boundInstance != null && boundInstance.save != null) uint instanceIdToCheck = Global.MapMgr.FindInstanceIdForPlayer(mapid, player);
if (instanceIdToCheck != 0)
{ {
Map boundMap = Global.MapMgr.FindMap(mapid, boundInstance.save.GetInstanceId()); Map boundMap = Global.MapMgr.FindMap(mapid, instanceIdToCheck);
if (boundMap != null) if (boundMap != null)
{ {
EnterState denyReason = boundMap.CannotEnter(player); EnterState denyReason = boundMap.CannotEnter(player);
if (denyReason != 0) if (denyReason != 0)
return denyReason; return denyReason;
} }
// players are only allowed to enter 10 instances per hour
if (entry.IsDungeon() && !player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead())
return EnterState.CannotEnterTooManyInstances;
} }
} }
// players are only allowed to enter 5 instances per hour
if (entry.IsDungeon() && (!player.GetGroup() || (player.GetGroup() && !player.GetGroup().IsLFGGroup())))
{
uint instanceIdToCheck = 0;
InstanceSave save = player.GetInstanceSave(mapid);
if (save != null)
instanceIdToCheck = save.GetInstanceId();
// instanceId can never be 0 - will not be found
if (!player.CheckInstanceCount(instanceIdToCheck) && !player.IsDead())
return EnterState.CannotEnterTooManyInstances;
}
return EnterState.CanEnter; return EnterState.CanEnter;
} }
@@ -3414,6 +3405,7 @@ namespace Game.Maps
} }
public InstanceMap ToInstanceMap() { return IsDungeon() ? (this as InstanceMap) : null; } public InstanceMap ToInstanceMap() { return IsDungeon() ? (this as InstanceMap) : null; }
public BattlegroundMap ToBattlegroundMap() { return IsBattlegroundOrArena() ? (this as BattlegroundMap) : null; } public BattlegroundMap ToBattlegroundMap() { return IsBattlegroundOrArena() ? (this as BattlegroundMap) : null; }
public void Balance() public void Balance()
@@ -4759,8 +4751,10 @@ namespace Game.Maps
public class InstanceMap : Map public class InstanceMap : Map
{ {
public InstanceMap(uint id, long expiry, uint InstanceId, Difficulty spawnMode, int instanceTeam) : base(id, expiry, InstanceId, spawnMode) public InstanceMap(uint id, long expiry, uint InstanceId, Difficulty spawnMode, int instanceTeam, InstanceLock instanceLock) : base(id, expiry, InstanceId, spawnMode)
{ {
i_instanceLock = instanceLock;
//lets initialize visibility distance for dungeons //lets initialize visibility distance for dungeons
InitVisibilityDistance(); InitVisibilityDistance();
@@ -4804,121 +4798,43 @@ namespace Game.Maps
if (!player.IsLoading() && IsRaid() && GetInstanceScript() != null && GetInstanceScript().IsEncounterInProgress()) if (!player.IsLoading() && IsRaid() && GetInstanceScript() != null && GetInstanceScript().IsEncounterInProgress())
return EnterState.CannotEnterZoneInCombat; return EnterState.CannotEnterZoneInCombat;
// cannot enter if player is permanent saved to a different instance id if (i_instanceLock != null)
InstanceBind playerBind = player.GetBoundInstance(GetId(), GetDifficultyID()); {
if (playerBind != null) // cannot enter if player is permanent saved to a different instance id
if (playerBind.perm && playerBind.save != null) TransferAbortReason lockError = Global.InstanceLockMgr.CanJoinInstanceLock(player.GetGUID(), new MapDb2Entries(GetEntry(), GetMapDifficulty()), i_instanceLock);
if (playerBind.save.GetInstanceId() != GetInstanceId()) if (lockError == TransferAbortReason.LockedToDifferentInstance)
return EnterState.CannotEnterInstanceBindMismatch; return EnterState.CannotEnterInstanceBindMismatch;
if (lockError == TransferAbortReason.AlreadyCompletedEncounter)
return EnterState.CannotEnterAlreadyCompletedEncounter;
}
return base.CannotEnter(player); return base.CannotEnter(player);
} }
public override bool AddPlayerToMap(Player player, bool initPlayer = true) public override bool AddPlayerToMap(Player player, bool initPlayer = true)
{ {
Group group = player.GetGroup();
// increase current instances (hourly limit) // increase current instances (hourly limit)
if (!group || !group.IsLFGGroup()) player.AddInstanceEnterTime(GetInstanceId(), GameTime.GetGameTime());
player.AddInstanceEnterTime(GetInstanceId(), GameTime.GetGameTime());
// get or create an instance save for the map MapDb2Entries entries = new(GetEntry(), GetMapDifficulty());
InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId()); if (entries.MapDifficulty.HasResetSchedule() && i_instanceLock != null && i_instanceLock.GetData().CompletedEncountersMask != 0)
if (mapSave == null)
{ {
Log.outInfo(LogFilter.Maps, "InstanceMap.Add: creating instance save for map {0} spawnmode {1} with instance id {2}", GetId(), GetDifficultyID(), GetInstanceId()); if (!entries.MapDifficulty.IsUsingEncounterLocks())
mapSave = Global.InstanceSaveMgr.AddInstanceSave(GetId(), GetInstanceId(), GetDifficultyID(), 0, 0, true);
}
Cypher.Assert(mapSave != null);
// check for existing instance binds
InstanceBind playerBind = player.GetBoundInstance(GetId(), GetDifficultyID());
if (playerBind != null && playerBind.perm)
{
// cannot enter other instances if bound permanently
if (playerBind.save != mapSave)
{ {
Log.outError(LogFilter.Maps, "InstanceMap.Add: player {0}({1}) is permanently bound to instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is being put into instance {9} {10}, {11}, {12}, {13}, {14}, {15}", InstanceLock playerLock = Global.InstanceLockMgr.FindActiveInstanceLock(player.GetGUID(), entries);
player.GetName(), player.GetGUID().ToString(), GetMapName(), playerBind.save.GetMapId(), if (playerLock == null || (playerLock.IsExpired() && playerLock.IsExtended()) ||
playerBind.save.GetInstanceId(), playerBind.save.GetDifficultyID(), playerLock.GetData().CompletedEncountersMask != i_instanceLock.GetData().CompletedEncountersMask)
playerBind.save.GetPlayerCount(), playerBind.save.GetGroupCount(),
playerBind.save.CanReset(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(),
mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(),
mapSave.CanReset());
return false;
}
}
else
{
if (group)
{
// solo saves should have been reset when the map was loaded
InstanceBind groupBind = group.GetBoundInstance(this);
if (playerBind != null && playerBind.save != mapSave)
{ {
Log.outError(LogFilter.Maps, PendingRaidLock pendingRaidLock = new();
"InstanceMapAdd: player {0}({1}) is being put into instance {2} {3}, {4}, {5}, {6}, {7}, {8} but he is in group {9} and is bound to instance {10}, {11}, {12}, {13}, {14}, {15}!", pendingRaidLock.TimeUntilLock = 60000;
player.GetName(), player.GetGUID().ToString(), GetMapName(), mapSave.GetMapId(), mapSave.GetInstanceId(), pendingRaidLock.CompletedMask = i_instanceLock.GetData().CompletedEncountersMask;
mapSave.GetDifficultyID(), mapSave.GetPlayerCount(), mapSave.GetGroupCount(), pendingRaidLock.Extending = playerLock != null && playerLock.IsExtended();
mapSave.CanReset(), group.GetLeaderGUID().ToString(), pendingRaidLock.WarningOnly = entries.Map.IsFlexLocking(); // events it triggers: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START
playerBind.save.GetMapId(), playerBind.save.GetInstanceId(), player.GetSession().SendPacket(pendingRaidLock);
playerBind.save.GetDifficultyID(), playerBind.save.GetPlayerCount(), if (!entries.Map.IsFlexLocking())
playerBind.save.GetGroupCount(), playerBind.save.CanReset()); player.SetPendingBind(GetInstanceId(), 60000);
if (groupBind != null)
Log.outError(LogFilter.Maps,
"InstanceMap.Add: the group is bound to the instance {0} {1}, {2}, {3}, {4}, {5}, {6}",
GetMapName(), groupBind.save.GetMapId(), groupBind.save.GetInstanceId(),
groupBind.save.GetDifficultyID(), groupBind.save.GetPlayerCount(),
groupBind.save.GetGroupCount(), groupBind.save.CanReset());
Cypher.Assert(false);
return false;
} }
// bind to the group or keep using the group save
if (groupBind == null)
group.BindToInstance(mapSave, false);
else
{
// cannot jump to a different instance without resetting it
if (groupBind.save != mapSave)
{
Log.outError(LogFilter.Maps,
"InstanceMap.Add: player {0}({1}) is being put into instance {2}, {3}, {4} but he is in group {5} which is bound to instance {6}, {7}, {8}!",
player.GetName(), player.GetGUID().ToString(), mapSave.GetMapId(), mapSave.GetInstanceId(),
mapSave.GetDifficultyID(), group.GetLeaderGUID().ToString(),
groupBind.save.GetMapId(), groupBind.save.GetInstanceId(),
groupBind.save.GetDifficultyID());
Log.outError(LogFilter.Maps, "MapSave players: {0}, group count: {1}",
mapSave.GetPlayerCount(), mapSave.GetGroupCount());
if (groupBind.save != null)
Log.outError(LogFilter.Maps, "GroupBind save players: {0}, group count: {1}",
groupBind.save.GetPlayerCount(), groupBind.save.GetGroupCount());
else
Log.outError(LogFilter.Maps, "GroupBind save NULL");
return false;
}
// if the group/leader is permanently bound to the instance
// players also become permanently bound when they enter
if (groupBind.perm)
{
PendingRaidLock pendingRaidLock = new();
pendingRaidLock.TimeUntilLock = 60000;
pendingRaidLock.CompletedMask = i_data != null ? i_data.GetCompletedEncounterMask() : 0;
pendingRaidLock.Extending = false;
pendingRaidLock.WarningOnly = false; // events it throws: 1 : INSTANCE_LOCK_WARNING 0 : INSTANCE_LOCK_STOP / INSTANCE_LOCK_START
player.SendPacket(pendingRaidLock);
player.SetPendingBind(mapSave.GetInstanceId(), 60000);
}
}
}
else
{
// set up a solo bind or continue using it
if (playerBind == null)
player.BindToInstance(mapSave, false);
else
// cannot jump to a different instance without resetting it
Cypher.Assert(playerBind.save == mapSave);
} }
} }
@@ -4980,7 +4896,7 @@ namespace Game.Maps
Global.InstanceSaveMgr.UnloadInstanceSave(GetInstanceId()); Global.InstanceSaveMgr.UnloadInstanceSave(GetInstanceId());
} }
public void CreateInstanceData(bool load) public void CreateInstanceData()
{ {
if (i_data != null) if (i_data != null)
return; return;
@@ -4995,24 +4911,19 @@ namespace Game.Maps
if (i_data == null) if (i_data == null)
return; return;
if (load) if (i_instanceLock != null)
{ {
// @todo make a global storage for this MapDb2Entries entries = new(GetEntry(), GetMapDifficulty());
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_INSTANCE); if (entries.IsInstanceIdBound() || IsRaid() || entries.MapDifficulty.IsRestoringDungeonState() || !i_owningGroupRef.isValid())
stmt.AddValue(0, GetId());
stmt.AddValue(1, i_InstanceId);
SQLResult result = DB.Characters.Query(stmt);
if (!result.IsEmpty())
{ {
var data = result.Read<string>(0); InstanceLockData lockData = i_instanceLock.GetInstanceInitializationData();
i_data.SetCompletedEncountersMask(result.Read<uint>(1)); i_data.SetCompletedEncountersMask(lockData.CompletedEncountersMask);
i_data.SetEntranceLocation(result.Read<uint>(2)); i_data.SetEntranceLocation(lockData.EntranceWorldSafeLocId);
if (data != "") if (!lockData.Data.IsEmpty())
{ {
Log.outDebug(LogFilter.Maps, "Loading instance data for `{0}` with id {1}", Log.outDebug(LogFilter.Maps, "Loading instance data for `{0}` with id {1}",
Global.ObjectMgr.GetScriptName(i_script_id), i_InstanceId); Global.ObjectMgr.GetScriptName(i_script_id), i_InstanceId);
i_data.Load(data); i_data.Load(lockData.Data);
} }
} }
} }
@@ -5076,54 +4987,70 @@ namespace Game.Maps
return Global.ObjectMgr.GetScriptName(i_script_id); return Global.ObjectMgr.GetScriptName(i_script_id);
} }
public void PermBindAllPlayers() public void UpdateInstanceLock(DungeonEncounterRecord dungeonEncounter, UpdateSaveDataEvent updateSaveDataEvent)
{ {
if (!IsDungeon()) if (i_instanceLock != null)
return;
InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(GetInstanceId());
if (save == null)
{ {
Log.outError(LogFilter.Maps, "Cannot bind players to instance map (Name: {0}, Entry: {1}, Difficulty: {2}, ID: {3}) because no instance save is available!", GetMapName(), GetId(), GetDifficultyID(), GetInstanceId()); uint instanceCompletedEncounters = i_instanceLock.GetData().CompletedEncountersMask;
return; if (dungeonEncounter != null)
instanceCompletedEncounters |= 1u << dungeonEncounter.Bit;
MapDb2Entries entries = new(GetEntry(), GetMapDifficulty());
SQLTransaction trans = new();
if (entries.IsInstanceIdBound())
Global.InstanceLockMgr.UpdateSharedInstanceLock(trans, new InstanceLockUpdateEvent(GetInstanceId(), i_data.GetSaveData(), instanceCompletedEncounters, dungeonEncounter));
foreach (var player in GetPlayers())
{
// never instance bind GMs with GM mode enabled
if (player.IsGameMaster())
continue;
InstanceLock playerLock = Global.InstanceLockMgr.FindActiveInstanceLock(player.GetGUID(), entries);
string oldData = "";
if (playerLock != null)
oldData = playerLock.GetData().Data;
bool isNewLock = playerLock == null || playerLock.GetData().CompletedEncountersMask == 0 || playerLock.IsExpired();
InstanceLock newLock = Global.InstanceLockMgr.UpdateInstanceLockForPlayer(trans, player.GetGUID(), entries, new InstanceLockUpdateEvent(GetInstanceId(), i_data.UpdateSaveData(oldData, updateSaveDataEvent), instanceCompletedEncounters, dungeonEncounter));
if (isNewLock)
{
InstanceSaveCreated data = new();
data.Gm = player.IsGameMaster();
player.SendPacket(data);
player.GetSession().SendCalendarRaidLockoutAdded(newLock);
}
}
DB.Characters.CommitTransaction(trans);
} }
}
// perm bind all players that are currently inside the instance public void CreateInstanceLockForPlayer(Player player)
foreach (Player player in GetPlayers()) {
MapDb2Entries entries = new(GetEntry(), GetMapDifficulty());
InstanceLock playerLock = Global.InstanceLockMgr.FindActiveInstanceLock(player.GetGUID(), entries);
bool isNewLock = playerLock == null || playerLock.GetData().CompletedEncountersMask == 0 || playerLock.IsExpired();
SQLTransaction trans = new();
InstanceLock newLock = Global.InstanceLockMgr.UpdateInstanceLockForPlayer(trans, player.GetGUID(), entries, new InstanceLockUpdateEvent(GetInstanceId(), i_data.GetSaveData(), i_instanceLock.GetData().CompletedEncountersMask, null));
DB.Characters.CommitTransaction(trans);
if (isNewLock)
{ {
// never instance bind GMs with GM mode enabled InstanceSaveCreated data = new();
if (player.IsGameMaster()) data.Gm = player.IsGameMaster();
continue; player.SendPacket(data);
InstanceBind bind = player.GetBoundInstance(save.GetMapId(), save.GetDifficultyID()); player.GetSession().SendCalendarRaidLockoutAdded(newLock);
if (bind != null && bind.perm)
{
if (bind.save != null && bind.save.GetInstanceId() != save.GetInstanceId())
{
Log.outError(LogFilter.Maps, "Player (GUID: {0}, Name: {1}) is in instance map (Name: {2}, Entry: {3}, Difficulty: {4}, ID: {5}) that is being bound, but already has a save for the map on ID {6}!",
player.GetGUID().GetCounter(), player.GetName(), GetMapName(), save.GetMapId(), save.GetDifficultyID(), save.GetInstanceId(), bind.save.GetInstanceId());
}
else if (bind.save == null)
{
Log.outError(LogFilter.Maps, "Player (GUID: {0}, Name: {1}) is in instance map (Name: {2}, Entry: {3}, Difficulty: {4}, ID: {5}) that is being bound, but already has a bind (without associated save) for the map!",
player.GetGUID().GetCounter(), player.GetName(), GetMapName(), save.GetMapId(), save.GetDifficultyID(), save.GetInstanceId());
}
}
else
{
player.BindToInstance(save, true);
InstanceSaveCreated data = new();
data.Gm = player.IsGameMaster();
player.SendPacket(data);
player.GetSession().SendCalendarRaidLockout(save, true);
// if group leader is in instance, group also gets bound
Group group = player.GetGroup();
if (group)
if (group.GetLeaderGUID() == player.GetGUID())
group.BindToInstance(save, true);
}
} }
} }
@@ -5213,11 +5140,15 @@ namespace Game.Maps
} }
public InstanceScenario GetInstanceScenario() { return i_scenario; } public InstanceScenario GetInstanceScenario() { return i_scenario; }
public void SetInstanceScenario(InstanceScenario scenario) { i_scenario = scenario; } public void SetInstanceScenario(InstanceScenario scenario) { i_scenario = scenario; }
public InstanceLock GetInstanceLock() { return i_instanceLock; }
InstanceScript i_data; InstanceScript i_data;
uint i_script_id; uint i_script_id;
InstanceScenario i_scenario; InstanceScenario i_scenario;
InstanceLock i_instanceLock;
bool m_resetAfterUnload; bool m_resetAfterUnload;
bool m_unloadWhenEmpty; bool m_unloadWhenEmpty;
} }
+50 -62
View File
@@ -70,7 +70,7 @@ namespace Game.Entities
return map; return map;
} }
InstanceMap CreateInstance(uint mapId, uint instanceId, InstanceSave save, Difficulty difficulty, int team) InstanceMap CreateInstance(uint mapId, uint instanceId, InstanceLock instanceLock, Difficulty difficulty, int team, Group group)
{ {
// make sure we have a valid map id // make sure we have a valid map id
var entry = CliDB.MapStorage.LookupByKey(mapId); var entry = CliDB.MapStorage.LookupByKey(mapId);
@@ -84,19 +84,16 @@ namespace Game.Entities
// some instances only have one difficulty // some instances only have one difficulty
Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty); Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty);
Log.outDebug(LogFilter.Maps, $"MapInstanced::CreateInstance: {(save != null ? "" : "new ")} map instance {instanceId} for {mapId} created with difficulty {difficulty}"); Log.outDebug(LogFilter.Maps, $"MapInstanced::CreateInstance: {(instanceLock?.GetInstanceId() != 0 ? "" : "new ")}map instance {instanceId} for {mapId} created with difficulty {difficulty}");
InstanceMap map = new InstanceMap(mapId, i_gridCleanUpDelay, instanceId, difficulty, team); InstanceMap map = new InstanceMap(mapId, i_gridCleanUpDelay, instanceId, difficulty, team, instanceLock);
Cypher.Assert(map.IsDungeon()); Cypher.Assert(map.IsDungeon());
map.LoadRespawnTimes(); map.LoadRespawnTimes();
map.LoadCorpseData(); map.LoadCorpseData();
bool load_data = save != null; map.CreateInstanceData();
map.CreateInstanceData(load_data); map.SetInstanceScenario(Global.ScenarioMgr.CreateInstanceScenario(map, team));
var instanceScenario = Global.ScenarioMgr.CreateInstanceScenario(map, team);
if (instanceScenario != null)
map.SetInstanceScenario(instanceScenario);
if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids)) if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids))
map.LoadAllCells(); map.LoadAllCells();
@@ -130,7 +127,7 @@ namespace Game.Entities
/// <param name="player"></param> /// <param name="player"></param>
/// <param name="loginInstanceId"></param> /// <param name="loginInstanceId"></param>
/// <returns>the right instance for the object, based on its InstanceId</returns> /// <returns>the right instance for the object, based on its InstanceId</returns>
public Map CreateMap(uint mapId, Player player, uint loginInstanceId = 0) public Map CreateMap(uint mapId, Player player)
{ {
if (!player) if (!player)
return null; return null;
@@ -167,63 +164,49 @@ namespace Game.Entities
} }
else if (entry.IsDungeon()) else if (entry.IsDungeon())
{ {
InstanceBind pBind = player.GetBoundInstance(mapId, player.GetDifficultyID(entry)); Group group = player.GetGroup();
InstanceSave pSave = pBind != null ? pBind.save : null; Difficulty difficulty = group != null ? group.GetDifficultyID(entry) : player.GetDifficultyID(entry);
MapDb2Entries entries = new(entry, Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty));
// priority: ObjectGuid instanceOwnerGuid = group != null ? group.GetRecentInstanceOwner(mapId) : player.GetGUID();
// 1. player's permanent bind InstanceLock instanceLock = Global.InstanceLockMgr.FindActiveInstanceLock(instanceOwnerGuid, entries);
// 2. player's current instance id if this is at login if (instanceLock != null)
// 3. group's current bind
// 4. player's current bind
if (pBind == null || !pBind.perm)
{ {
if (loginInstanceId != 0) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null) newInstanceId = instanceLock.GetInstanceId();
{
map = FindMap_i(mapId, loginInstanceId);
if (map == null && pSave != null && pSave.GetInstanceId() == loginInstanceId)
{
map = CreateInstance(mapId, loginInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId());
i_maps[(map.GetId(), map.GetInstanceId())] = map;
}
return map;
}
InstanceBind groupBind = null; // Reset difficulty to the one used in instance lock
Group group = player.GetGroup(); if (!entries.Map.IsFlexLocking())
// use the player's difficulty setting (it may not be the same as the group's) difficulty = instanceLock.GetDifficultyId();
if (group != null)
{
groupBind = group.GetBoundInstance(entry);
if (groupBind != null)
{
// solo saves should be reset when entering a group's instance
player.UnbindInstance(mapId, player.GetDifficultyID(entry));
pSave = groupBind.save;
}
}
}
if (pSave != null)
{
// solo/perm/group
newInstanceId = pSave.GetInstanceId();
map = FindMap_i(mapId, newInstanceId);
// it is possible that the save exists but the map doesn't
if (!map)
map = CreateInstance(mapId, newInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId());
} }
else else
{ {
Difficulty diff = player.GetGroup() ? player.GetGroup().GetDifficultyID(entry) : player.GetDifficultyID(entry); // Try finding instance id for normal dungeon
if (!entries.MapDifficulty.HasResetSchedule())
newInstanceId = group ? group.GetRecentInstanceId(mapId) : player.GetRecentInstanceId(mapId);
// if no instanceId via group members or instance saves is found // If not found or instance is not a normal dungeon, generate new one
// the instance will be created for the first time if (newInstanceId == 0)
newInstanceId = GenerateInstanceId();
instanceLock = Global.InstanceLockMgr.CreateInstanceLockForNewInstance(instanceOwnerGuid, entries, newInstanceId);
}
// it is possible that the save exists but the map doesn't
map = FindMap_i(mapId, newInstanceId);
// is is also 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)
{
newInstanceId = GenerateInstanceId(); newInstanceId = GenerateInstanceId();
instanceLock.SetInstanceId(newInstanceId);
map = null;
}
//Seems it is now possible, but I do not know if it should be allowed if (!map)
//ASSERT(!FindInstanceMap(NewInstanceId)); {
map = FindMap_i(mapId, newInstanceId); map = CreateInstance(mapId, newInstanceId, instanceLock, difficulty, player.GetTeamId(), group);
if (!map) if (group)
map = CreateInstance(mapId, newInstanceId, null, diff, player.GetTeamId()); group.SetRecentInstance(mapId, instanceOwnerGuid, newInstanceId);
else
player.SetRecentInstance(mapId, newInstanceId);
} }
} }
else if (entry.IsGarrison()) else if (entry.IsGarrison())
@@ -342,11 +325,16 @@ namespace Game.Entities
{ {
_nextInstanceId = 1; _nextInstanceId = 1;
SQLResult result = DB.Characters.Query("SELECT IFNULL(MAX(id), 0) FROM instance"); ulong maxExistingInstanceId = 0;
SQLResult result = DB.Characters.Query("SELECT IFNULL(MAX(instanceId), 0) FROM instance");
if (!result.IsEmpty()) if (!result.IsEmpty())
_freeInstanceIds = new BitSet(result.Read<int>(0) + 2, true); // make space for one extra to be able to access [_nextInstanceId] index in case all slots are taken maxExistingInstanceId = Math.Max(maxExistingInstanceId, result.Read<ulong>(0));
else
_freeInstanceIds = new BitSet((int)_nextInstanceId + 1, true); result = DB.Characters.Query("SELECT IFNULL(MAX(instanceId), 0) FROM character_instance_lock");
if (!result.IsEmpty())
maxExistingInstanceId = Math.Max(maxExistingInstanceId, result.Read<ulong>(0));
_freeInstanceIds.Length = (int)(maxExistingInstanceId + 2); // make space for one extra to be able to access [_nextInstanceId] index in case all slots are taken
// never allow 0 id // never allow 0 id
_freeInstanceIds[0] = false; _freeInstanceIds[0] = false;
@@ -852,13 +852,13 @@ namespace Game.Networking.Packets
data.WriteUInt64(InstanceID); data.WriteUInt64(InstanceID);
data.WriteInt32(MapID); data.WriteInt32(MapID);
data.WriteUInt32(DifficultyID); data.WriteUInt32(DifficultyID);
data.WriteUInt32((uint)ExpireTime); data.WriteInt32(ExpireTime);
} }
public ulong InstanceID; public ulong InstanceID;
public int MapID; public int MapID;
public uint DifficultyID; public uint DifficultyID;
public long ExpireTime; public int ExpireTime;
} }
struct CalendarSendCalendarEventInfo struct CalendarSendCalendarEventInfo
@@ -41,11 +41,11 @@ namespace Game.Networking.Packets
{ {
_worldPacket.WriteInt32(LockList.Count); _worldPacket.WriteInt32(LockList.Count);
foreach (InstanceLock lockInfos in LockList) foreach (InstanceLockPkt lockInfos in LockList)
lockInfos.Write(_worldPacket); lockInfos.Write(_worldPacket);
} }
public List<InstanceLock> LockList = new(); public List<InstanceLockPkt> LockList = new();
} }
class ResetInstances : ClientPacket class ResetInstances : ClientPacket
@@ -270,7 +270,7 @@ namespace Game.Networking.Packets
} }
//Structs //Structs
public struct InstanceLock public struct InstanceLockPkt
{ {
public void Write(WorldPacket data) public void Write(WorldPacket data)
{ {
+2
View File
@@ -516,6 +516,8 @@ namespace Game
Log.outInfo(LogFilter.ServerLoading, "Loading instances..."); Log.outInfo(LogFilter.ServerLoading, "Loading instances...");
Global.InstanceSaveMgr.LoadInstances(); Global.InstanceSaveMgr.LoadInstances();
Global.InstanceLockMgr.Load();
Log.outInfo(LogFilter.ServerLoading, "Loading Localization strings..."); Log.outInfo(LogFilter.ServerLoading, "Loading Localization strings...");
uint oldMSTime = Time.GetMSTime(); uint oldMSTime = Time.GetMSTime();
Global.ObjectMgr.LoadCreatureLocales(); Global.ObjectMgr.LoadCreatureLocales();
+1
View File
@@ -122,6 +122,7 @@ namespace WorldServer
Global.MapMgr.UnloadAll(); // unload all grids (including locked in memory) Global.MapMgr.UnloadAll(); // unload all grids (including locked in memory)
Global.TerrainMgr.UnloadAll(); Global.TerrainMgr.UnloadAll();
Global.InstanceLockMgr.Unload();
Global.ScriptMgr.Unload(); Global.ScriptMgr.Unload();
// set server offline // set server offline
+1
View File
@@ -209,6 +209,7 @@ Logger.Network = 3,Console Server
#Logger.Condition=3,Console Server #Logger.Condition=3,Console Server
#Logger.Gameevent=3,Console Server #Logger.Gameevent=3,Console Server
#Logger.Guild=3,Console Server #Logger.Guild=3,Console Server
#Logger.Instance=3,Console Server
#Logger.Lfg=3,Console Server #Logger.Lfg=3,Console Server
#Logger.Loot=3,Console Server #Logger.Loot=3,Console Server
#Logger.MapsScript=3,Console Server #Logger.MapsScript=3,Console Server