Core/Instances: Instance lock rewrite (WIP)
Port From (https://github.com/TrinityCore/TrinityCore/commit/17665c929c3a9fb7fe75dd680648129bc1c1f874)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,6 +417,7 @@ public enum LogFilter
|
|||||||
Garrison,
|
Garrison,
|
||||||
Gameevent,
|
Gameevent,
|
||||||
Guild,
|
Guild,
|
||||||
|
Instance,
|
||||||
Lfg,
|
Lfg,
|
||||||
Loot,
|
Loot,
|
||||||
MapsScript,
|
MapsScript,
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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...
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user