Ported .Net Core commits:
hondacrx: - Initial commit: Switch to .Net Core 2.0 - Fix build and removed not needed files Fabi: - Updated solution platforms. - Changed folder structure. - Change library target framework to netstandard2.0. - Updated solution platforms again... - Removed windows specific kernel32 function usage (Ctrl-C handler).
This commit is contained in:
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Scenarios;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class InstanceSaveManager : Singleton<InstanceSaveManager>
|
||||
{
|
||||
InstanceSaveManager() { }
|
||||
|
||||
public InstanceSave AddInstanceSave(uint mapId, uint instanceId, Difficulty difficulty, long resetTime, uint entranceId, bool canReset, bool load = false)
|
||||
{
|
||||
InstanceSave old_save = GetInstanceSave(instanceId);
|
||||
if (old_save != null)
|
||||
return old_save;
|
||||
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(mapId);
|
||||
if (entry == null)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: wrong mapid = {0}, instanceid = {1}!", mapId, instanceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (instanceId == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, wrong instanceid = {1}!", mapId, instanceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null || difficultyEntry.InstanceType != entry.InstanceType)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}, wrong dificalty {2}!", mapId, instanceId, difficulty);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entranceId != 0 && !CliDB.WorldSafeLocsStorage.ContainsKey(entranceId))
|
||||
{
|
||||
Log.outWarn(LogFilter.Misc, "InstanceSaveManager.AddInstanceSave: invalid entranceId = {0} defined for instance save with mapid = {1}, instanceid = {2}!", entranceId, mapId, instanceId);
|
||||
entranceId = 0;
|
||||
}
|
||||
|
||||
if (resetTime == 0)
|
||||
{
|
||||
// initialize reset time
|
||||
// for normal instances if no creatures are killed the instance will reset in two hours
|
||||
if (entry.InstanceType == MapTypes.Raid || difficulty > Difficulty.Normal)
|
||||
resetTime = GetResetTimeFor(mapId, difficulty);
|
||||
else
|
||||
{
|
||||
resetTime = Time.UnixTime + 2 * Time.Hour;
|
||||
// normally this will be removed soon after in InstanceMap.Add, prevent error
|
||||
ScheduleReset(true, resetTime, new InstResetEvent(0, mapId, difficulty, instanceId));
|
||||
}
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Maps, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}", mapId, instanceId);
|
||||
|
||||
InstanceSave save = new InstanceSave(mapId, instanceId, difficulty, entranceId, resetTime, canReset);
|
||||
if (!load)
|
||||
save.SaveToDB();
|
||||
|
||||
m_instanceSaveById[instanceId] = save;
|
||||
return save;
|
||||
}
|
||||
|
||||
public InstanceSave GetInstanceSave(uint InstanceId)
|
||||
{
|
||||
return m_instanceSaveById.LookupByKey(InstanceId);
|
||||
}
|
||||
|
||||
public void DeleteInstanceFromDB(uint instanceid)
|
||||
{
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INSTANCE_BY_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
// Respawn times should be deleted only when the map gets unloaded
|
||||
}
|
||||
|
||||
public void RemoveInstanceSave(uint InstanceId)
|
||||
{
|
||||
var instanceSave = m_instanceSaveById.LookupByKey(InstanceId);
|
||||
if (instanceSave != null)
|
||||
{
|
||||
// save the resettime for normal instances only when they get unloaded
|
||||
long resettime = instanceSave.GetResetTimeForDB();
|
||||
if (resettime != 0)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_RESETTIME);
|
||||
|
||||
stmt.AddValue(0, resettime);
|
||||
stmt.AddValue(1, InstanceId);
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
instanceSave.SetToDelete(true);
|
||||
m_instanceSaveById.Remove(InstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadInstances()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
// Delete expired instances (Instance related spawns are removed in the following cleanup queries)
|
||||
DB.Characters.Execute("DELETE i FROM instance i LEFT JOIN instance_reset ir ON mapid = map AND i.difficulty = ir.difficulty " +
|
||||
"WHERE (i.resettime > 0 AND i.resettime < UNIX_TIMESTAMP()) OR (ir.resettime IS NOT NULL AND ir.resettime < UNIX_TIMESTAMP())");
|
||||
|
||||
// Delete invalid character_instance and group_instance references
|
||||
DB.Characters.Execute("DELETE ci.* FROM character_instance AS ci LEFT JOIN characters AS c ON ci.guid = c.guid WHERE c.guid IS NULL");
|
||||
DB.Characters.Execute("DELETE gi.* FROM group_instance AS gi LEFT JOIN groups AS g ON gi.guid = g.guid WHERE g.guid IS NULL");
|
||||
|
||||
// Delete invalid instance references
|
||||
DB.Characters.Execute("DELETE i.* FROM instance AS i LEFT JOIN character_instance AS ci ON i.id = ci.instance LEFT JOIN group_instance AS gi ON i.id = gi.instance WHERE ci.guid IS NULL AND gi.guid IS NULL");
|
||||
|
||||
// Delete invalid references to instance
|
||||
DB.Characters.Execute("DELETE FROM creature_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
|
||||
DB.Characters.Execute("DELETE FROM gameobject_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
|
||||
DB.Characters.Execute("DELETE tmp.* FROM character_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
|
||||
DB.Characters.Execute("DELETE tmp.* FROM group_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
|
||||
|
||||
// Clean invalid references to instance
|
||||
DB.Characters.Execute("UPDATE corpse SET instanceId = 0 WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
|
||||
DB.Characters.Execute("UPDATE characters AS tmp LEFT JOIN instance ON tmp.instance_id = instance.id SET tmp.instance_id = 0 WHERE tmp.instance_id > 0 AND instance.id IS NULL");
|
||||
|
||||
// Initialize instance id storage (Needs to be done after the trash has been clean out)
|
||||
Global.MapMgr.InitInstanceIds();
|
||||
|
||||
// Load reset times and clean expired instances
|
||||
LoadResetTimes();
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded instances in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
void LoadResetTimes()
|
||||
{
|
||||
long now = Time.UnixTime;
|
||||
long today = (now / Time.Day) * Time.Day;
|
||||
|
||||
// NOTE: Use DirectPExecute for tables that will be queried later
|
||||
|
||||
// get the current reset times for normal instances (these may need to be updated)
|
||||
// these are only kept in memory for InstanceSaves that are loaded later
|
||||
// resettime = 0 in the DB for raid/heroic instances so those are skipped
|
||||
Dictionary<uint, Tuple<uint, long>> instResetTime = new Dictionary<uint, Tuple<uint, long>>();
|
||||
|
||||
// index instance ids by map/difficulty pairs for fast reset warning send
|
||||
MultiMap<uint, uint> mapDiffResetInstances = new MultiMap<uint, uint>();
|
||||
|
||||
SQLResult result = DB.Characters.Query("SELECT id, map, difficulty, resettime FROM instance ORDER BY id ASC");
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint instanceId = result.Read<uint>(0);
|
||||
|
||||
// Instances are pulled in ascending order from db and nextInstanceId is initialized with 1,
|
||||
// so if the instance id is used, increment until we find the first unused one for a potential new instance
|
||||
if (Global.MapMgr.GetNextInstanceId() == instanceId)
|
||||
Global.MapMgr.SetNextInstanceId(instanceId + 1);
|
||||
|
||||
// Mark instance id as being used
|
||||
Global.MapMgr.RegisterInstanceId(instanceId);
|
||||
long resettime = result.Read<uint>(3);
|
||||
if (resettime != 0)
|
||||
{
|
||||
uint mapid = result.Read<ushort>(1);
|
||||
uint difficulty = result.Read<byte>(2);
|
||||
|
||||
instResetTime[instanceId] = Tuple.Create(MathFunctions.MakePair32(mapid, difficulty), resettime);
|
||||
mapDiffResetInstances.Add(MathFunctions.MakePair32(mapid, difficulty), instanceId);
|
||||
}
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
// update reset time for normal instances with the max creature respawn time + X hours
|
||||
SQLResult result2 = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_MAX_CREATURE_RESPAWNS));
|
||||
if (!result2.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint instance = result2.Read<uint>(1);
|
||||
long resettime = result2.Read<uint>(0) + 2 * Time.Hour;
|
||||
var pair = instResetTime.LookupByKey(instance);
|
||||
if (pair != null && pair.Item2 != resettime)
|
||||
{
|
||||
DB.Characters.Execute("UPDATE instance SET resettime = '{0}' WHERE id = '{1}'", resettime, instance);
|
||||
instResetTime[instance] = Tuple.Create(pair.Item1, resettime);
|
||||
}
|
||||
}
|
||||
while (result2.NextRow());
|
||||
}
|
||||
|
||||
// schedule the reset times
|
||||
foreach (var pair in instResetTime)
|
||||
if (pair.Value.Item2 > now)
|
||||
ScheduleReset(true, pair.Value.Item2, new InstResetEvent(0, MathFunctions.Pair32_LoPart(pair.Value.Item1), (Difficulty)MathFunctions.Pair32_HiPart(pair.Value.Item1), pair.Key));
|
||||
}
|
||||
|
||||
// load the global respawn times for raid/heroic instances
|
||||
uint diff = (uint)(WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour) * Time.Hour);
|
||||
result = DB.Characters.Query("SELECT mapid, difficulty, resettime FROM instance_reset");
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint mapid = result.Read<ushort>(0);
|
||||
Difficulty difficulty = (Difficulty)result.Read<byte>(1);
|
||||
ulong oldresettime = result.Read<uint>(2);
|
||||
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty);
|
||||
if (mapDiff == null)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.LoadResetTimes: invalid mapid({0})/difficulty({1}) pair in instance_reset!", mapid, difficulty);
|
||||
DB.Characters.Execute("DELETE FROM instance_reset WHERE mapid = '{0}' AND difficulty = '{1}'", mapid, difficulty);
|
||||
continue;
|
||||
}
|
||||
|
||||
// update the reset time if the hour in the configs changes
|
||||
ulong newresettime = (oldresettime / Time.Day) * Time.Day + diff;
|
||||
if (oldresettime != newresettime)
|
||||
DB.Characters.Execute("UPDATE instance_reset SET resettime = '{0}' WHERE mapid = '{1}' AND difficulty = '{2}'", newresettime, mapid, difficulty);
|
||||
|
||||
InitializeResetTimeFor(mapid, difficulty, (long)newresettime);
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
// calculate new global reset times for expired instances and those that have never been reset yet
|
||||
// add the global reset times to the priority queue
|
||||
foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties())
|
||||
{
|
||||
uint mapid = mapDifficultyPair.Key;
|
||||
|
||||
foreach (var difficultyPair in mapDifficultyPair.Value)
|
||||
{
|
||||
Difficulty difficulty = (Difficulty)difficultyPair.Key;
|
||||
MapDifficultyRecord mapDiff = difficultyPair.Value;
|
||||
if (mapDiff.GetRaidDuration() == 0)
|
||||
continue;
|
||||
|
||||
// the reset_delay must be at least one day
|
||||
uint period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day);
|
||||
if (period < Time.Day)
|
||||
period = Time.Day;
|
||||
|
||||
long t = GetResetTimeFor(mapid, difficulty);
|
||||
if (t == 0)
|
||||
{
|
||||
// initialize the reset time
|
||||
t = today + period + diff;
|
||||
DB.Characters.Execute("INSERT INTO instance_reset VALUES ('{0}', '{1}', '{2}')", mapid, (uint)difficulty, (uint)t);
|
||||
}
|
||||
|
||||
if (t < now)
|
||||
{
|
||||
// assume that expired instances have already been cleaned
|
||||
// calculate the next reset time
|
||||
t = (t / Time.Day) * Time.Day;
|
||||
t += ((today - t) / period + 1) * period + diff;
|
||||
DB.Characters.Execute("UPDATE instance_reset SET resettime = '{0}' WHERE mapid = '{1}' AND difficulty= '{2}'", t, mapid, (uint)difficulty);
|
||||
}
|
||||
|
||||
InitializeResetTimeFor(mapid, difficulty, t);
|
||||
|
||||
// schedule the global reset/warning
|
||||
byte type;
|
||||
for (type = 1; type < 4; ++type)
|
||||
if (t - ResetTimeDelay[type - 1] > now)
|
||||
break;
|
||||
|
||||
ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, 0));
|
||||
|
||||
var range = mapDiffResetInstances.LookupByKey(MathFunctions.MakePair32(mapid, (uint)difficulty));
|
||||
foreach (var id in range)
|
||||
ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, id));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long GetSubsequentResetTime(uint mapid, Difficulty difficulty, long resetTime)
|
||||
{
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty);
|
||||
if (mapDiff == null || mapDiff.GetRaidDuration() == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Misc, "InstanceSaveManager.GetSubsequentResetTime: not valid difficulty or no reset delay for map {0}", mapid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
long diff = WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour) * Time.Hour;
|
||||
long period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day);
|
||||
if (period < Time.Day)
|
||||
period = Time.Day;
|
||||
|
||||
return ((resetTime + Time.Minute) / Time.Day * Time.Day) + period + diff;
|
||||
}
|
||||
|
||||
public void ScheduleReset(bool add, long time, InstResetEvent Event)
|
||||
{
|
||||
if (!add)
|
||||
{
|
||||
// find the event in the queue and remove it
|
||||
var range = m_resetTimeQueue.LookupByKey(time);
|
||||
foreach (var instResetEvent in range)
|
||||
{
|
||||
if (instResetEvent == Event)
|
||||
{
|
||||
m_resetTimeQueue.Remove(time, instResetEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// in case the reset time changed (should happen very rarely), we search the whole queue
|
||||
foreach (var pair in m_resetTimeQueue)
|
||||
{
|
||||
if (pair.Value == Event)
|
||||
{
|
||||
m_resetTimeQueue.Remove(pair);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.ScheduleReset: cannot cancel the reset, the event({0}, {1}, {2}) was not found!", Event.type, Event.mapid, Event.instanceId);
|
||||
}
|
||||
else
|
||||
m_resetTimeQueue.Add(time, Event);
|
||||
}
|
||||
|
||||
public void ForceGlobalReset(uint mapId, Difficulty difficulty)
|
||||
{
|
||||
if (Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty) == null)
|
||||
return;
|
||||
// remove currently scheduled reset times
|
||||
ScheduleReset(false, 0, new InstResetEvent(1, mapId, difficulty, 0));
|
||||
ScheduleReset(false, 0, new InstResetEvent(4, mapId, difficulty, 0));
|
||||
// force global reset on the instance
|
||||
_ResetOrWarnAll(mapId, difficulty, false, Time.UnixTime);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
long now = Time.UnixTime;
|
||||
long t;
|
||||
|
||||
while (!m_resetTimeQueue.Empty())
|
||||
{
|
||||
t = m_resetTimeQueue.First().Key;
|
||||
if (t >= now)
|
||||
break;
|
||||
|
||||
InstResetEvent Event = m_resetTimeQueue.First().Value;
|
||||
if (Event.type == 0)
|
||||
{
|
||||
// for individual normal instances, max creature respawn + X hours
|
||||
_ResetInstance(Event.mapid, Event.instanceId);
|
||||
m_resetTimeQueue.Remove(m_resetTimeQueue.First());
|
||||
}
|
||||
else
|
||||
{
|
||||
// global reset/warning for a certain map
|
||||
long resetTime = GetResetTimeFor(Event.mapid, Event.difficulty);
|
||||
_ResetOrWarnAll(Event.mapid, Event.difficulty, Event.type != 4, resetTime);
|
||||
if (Event.type != 4)
|
||||
{
|
||||
// schedule the next warning/reset
|
||||
++Event.type;
|
||||
ScheduleReset(true, resetTime - ResetTimeDelay[Event.type - 1], Event);
|
||||
}
|
||||
m_resetTimeQueue.Remove(m_resetTimeQueue.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _ResetSave(KeyValuePair<uint, InstanceSave> pair)
|
||||
{
|
||||
// unbind all players bound to the instance
|
||||
// do not allow UnbindInstance to automatically unload the InstanceSaves
|
||||
lock_instLists = true;
|
||||
|
||||
bool shouldDelete = true;
|
||||
var pList = pair.Value.m_playerList;
|
||||
List<Player> temp = new List<Player>(); // list of expired binds that should be unbound
|
||||
foreach (var player in pList)
|
||||
{
|
||||
InstanceBind bind = player.GetBoundInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID());
|
||||
if (bind != null)
|
||||
{
|
||||
Contract.Assert(bind.save == pair.Value);
|
||||
if (bind.perm && bind.extendState != 0) // permanent and not already expired
|
||||
{
|
||||
// actual promotion in DB already happened in caller
|
||||
bind.extendState = bind.extendState == BindExtensionState.Extended ? BindExtensionState.Normal : BindExtensionState.Expired;
|
||||
shouldDelete = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
temp.Add(player);
|
||||
}
|
||||
|
||||
var gList = pair.Value.m_groupList;
|
||||
while (!gList.Empty())
|
||||
{
|
||||
Group group = gList.First();
|
||||
group.UnbindInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID(), true);
|
||||
}
|
||||
|
||||
if (shouldDelete)
|
||||
m_instanceSaveById.Remove(pair.Key);
|
||||
|
||||
lock_instLists = false;
|
||||
}
|
||||
|
||||
void _ResetInstance(uint mapid, uint instanceId)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "InstanceSaveMgr._ResetInstance {0}, {1}", mapid, instanceId);
|
||||
Map map = Global.MapMgr.CreateBaseMap(mapid);
|
||||
if (!map.Instanceable())
|
||||
return;
|
||||
|
||||
var pair = m_instanceSaveById.Find(instanceId);
|
||||
if (pair.Value != null)
|
||||
_ResetSave(pair);
|
||||
|
||||
DeleteInstanceFromDB(instanceId); // even if save not loaded
|
||||
|
||||
Map iMap = ((MapInstanced)map).FindInstanceMap(instanceId);
|
||||
|
||||
if (iMap != null && iMap.IsDungeon())
|
||||
((InstanceMap)iMap).Reset(InstanceResetMethod.RespawnDelay);
|
||||
|
||||
if (iMap != null)
|
||||
{
|
||||
iMap.DeleteRespawnTimes();
|
||||
iMap.DeleteCorpseData();
|
||||
}
|
||||
else
|
||||
Map.DeleteRespawnTimesInDB(mapid, instanceId);
|
||||
|
||||
// Free up the instance id and allow it to be reused
|
||||
Global.MapMgr.FreeInstanceId(instanceId);
|
||||
}
|
||||
|
||||
void _ResetOrWarnAll(uint mapid, Difficulty difficulty, bool warn, long resetTime)
|
||||
{
|
||||
// global reset for all instances of the given map
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid);
|
||||
if (!mapEntry.Instanceable())
|
||||
return;
|
||||
|
||||
Log.outDebug(LogFilter.Misc, "InstanceSaveManager.ResetOrWarnAll: Processing map {0} ({1}) on difficulty {2} (warn? {3})", mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()], mapid, difficulty, warn);
|
||||
long now = Time.UnixTime;
|
||||
|
||||
if (!warn)
|
||||
{
|
||||
// calculate the next reset time
|
||||
long next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime);
|
||||
if (next_reset == 0)
|
||||
return;
|
||||
|
||||
// delete them from the DB, even if not loaded
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
// promote loaded binds to instances of the given map
|
||||
foreach (var pair in m_instanceSaveById.ToList())
|
||||
{
|
||||
if (pair.Value.GetMapId() == mapid && pair.Value.GetDifficultyID() == difficulty)
|
||||
_ResetSave(pair);
|
||||
}
|
||||
|
||||
SetResetTimeFor(mapid, difficulty, next_reset);
|
||||
ScheduleReset(true, next_reset - 3600, new InstResetEvent(1, mapid, difficulty, 0));
|
||||
|
||||
// Update it in the DB
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME);
|
||||
stmt.AddValue(0, next_reset);
|
||||
stmt.AddValue(1, mapid);
|
||||
stmt.AddValue(2, difficulty);
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
// note: this isn't fast but it's meant to be executed very rarely
|
||||
Map map = Global.MapMgr.CreateBaseMap(mapid); // _not_ include difficulty
|
||||
var instMaps = ((MapInstanced)map).GetInstancedMaps();
|
||||
uint timeLeft;
|
||||
|
||||
foreach (var pair in instMaps)
|
||||
{
|
||||
Map map2 = pair.Value;
|
||||
if (!map2.IsDungeon())
|
||||
continue;
|
||||
|
||||
if (warn)
|
||||
{
|
||||
if (now >= resetTime)
|
||||
timeLeft = 0;
|
||||
else
|
||||
timeLeft = (uint)(resetTime - now);
|
||||
|
||||
((InstanceMap)map2).SendResetWarnings(timeLeft);
|
||||
}
|
||||
else
|
||||
((InstanceMap)map2).Reset(InstanceResetMethod.Global);
|
||||
}
|
||||
|
||||
/// @todo delete creature/gameobject respawn times even if the maps are not loaded
|
||||
}
|
||||
|
||||
public uint GetNumBoundPlayersTotal()
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (var pair in m_instanceSaveById)
|
||||
ret += pair.Value.GetPlayerCount();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public uint GetNumBoundGroupsTotal()
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (var pair in m_instanceSaveById)
|
||||
ret += pair.Value.GetGroupCount();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public long GetResetTimeFor(uint mapid, Difficulty d)
|
||||
{
|
||||
return m_resetTimeByMapDifficulty.LookupByKey(MathFunctions.MakePair64(mapid, (uint)d));
|
||||
}
|
||||
|
||||
// Use this on startup when initializing reset times
|
||||
void InitializeResetTimeFor(uint mapid, Difficulty d, long t)
|
||||
{
|
||||
m_resetTimeByMapDifficulty[MathFunctions.MakePair64(mapid, (uint)d)] = t;
|
||||
}
|
||||
// Use this only when updating existing reset times
|
||||
void SetResetTimeFor(uint mapid, Difficulty d, long t)
|
||||
{
|
||||
var key = MathFunctions.MakePair64(mapid, (uint)d);
|
||||
Contract.Assert(m_resetTimeByMapDifficulty.ContainsKey(key));
|
||||
m_resetTimeByMapDifficulty[key] = t;
|
||||
}
|
||||
|
||||
public Dictionary<ulong, long> GetResetTimeMap()
|
||||
{
|
||||
return m_resetTimeByMapDifficulty;
|
||||
}
|
||||
|
||||
public int GetNumInstanceSaves() { return m_instanceSaveById.Count; }
|
||||
|
||||
public class InstResetEvent
|
||||
{
|
||||
public InstResetEvent(byte t = 0, uint _mapid = 0, Difficulty d = Difficulty.Normal, uint _instanceid = 0)
|
||||
{
|
||||
type = t;
|
||||
difficulty = d;
|
||||
mapid = _mapid;
|
||||
instanceId = _instanceid;
|
||||
}
|
||||
|
||||
public byte type;
|
||||
public Difficulty difficulty;
|
||||
public uint mapid;
|
||||
public uint instanceId;
|
||||
}
|
||||
|
||||
static ushort[] ResetTimeDelay = { 3600, 900, 300, 60 };
|
||||
|
||||
// used during global instance resets
|
||||
public bool lock_instLists;
|
||||
// fast lookup by instance id
|
||||
Dictionary<uint, InstanceSave> m_instanceSaveById = new Dictionary<uint, InstanceSave>();
|
||||
// fast lookup for reset times (always use existed functions for access/set)
|
||||
Dictionary<ulong, long> m_resetTimeByMapDifficulty = new Dictionary<ulong, long>();
|
||||
MultiMap<long, InstResetEvent> m_resetTimeQueue = new MultiMap<long, InstResetEvent>();
|
||||
}
|
||||
|
||||
public class InstanceSave
|
||||
{
|
||||
public InstanceSave(uint MapId, uint InstanceId, Difficulty difficulty, uint entranceId, long resetTime, bool canReset)
|
||||
{
|
||||
m_resetTime = resetTime;
|
||||
m_instanceid = InstanceId;
|
||||
m_mapid = MapId;
|
||||
m_difficulty = difficulty;
|
||||
m_entranceId = entranceId;
|
||||
m_canReset = canReset;
|
||||
m_toDelete = false;
|
||||
}
|
||||
|
||||
public void SaveToDB()
|
||||
{
|
||||
// save instance data too
|
||||
string data = "";
|
||||
uint completedEncounters = 0;
|
||||
|
||||
Map map = Global.MapMgr.FindMap(GetMapId(), m_instanceid);
|
||||
if (map != null)
|
||||
{
|
||||
Contract.Assert(map.IsDungeon());
|
||||
InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript();
|
||||
if (instanceScript != null)
|
||||
{
|
||||
data = instanceScript.GetSaveData();
|
||||
completedEncounters = instanceScript.GetCompletedEncounterMask();
|
||||
m_entranceId = instanceScript.GetEntranceLocation();
|
||||
}
|
||||
|
||||
InstanceScenario scenario = map.ToInstanceMap().GetInstanceScenario();
|
||||
if (scenario != null)
|
||||
scenario.SaveToDB();
|
||||
}
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_INSTANCE_SAVE);
|
||||
stmt.AddValue(0, m_instanceid);
|
||||
stmt.AddValue(1, GetMapId());
|
||||
stmt.AddValue(2, GetResetTimeForDB());
|
||||
stmt.AddValue(3, (uint)GetDifficultyID());
|
||||
stmt.AddValue(4, completedEncounters);
|
||||
stmt.AddValue(5, data);
|
||||
stmt.AddValue(6, m_entranceId);
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
public long GetResetTimeForDB()
|
||||
{
|
||||
// only save the reset time for normal instances
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(GetMapId());
|
||||
if (entry == null || entry.InstanceType == MapTypes.Raid || GetDifficultyID() == Difficulty.Heroic)
|
||||
return 0;
|
||||
else
|
||||
return GetResetTime();
|
||||
}
|
||||
|
||||
InstanceTemplate GetTemplate()
|
||||
{
|
||||
return Global.ObjectMgr.GetInstanceTemplate(m_mapid);
|
||||
}
|
||||
|
||||
MapRecord GetMapEntry()
|
||||
{
|
||||
return CliDB.MapStorage.LookupByKey(m_mapid);
|
||||
}
|
||||
|
||||
public void DeleteFromDB()
|
||||
{
|
||||
Global.InstanceSaveMgr.DeleteInstanceFromDB(GetInstanceId());
|
||||
}
|
||||
|
||||
bool UnloadIfEmpty()
|
||||
{
|
||||
if (m_playerList.Empty() && m_groupList.Empty())
|
||||
{
|
||||
if (!Global.InstanceSaveMgr.lock_instLists)
|
||||
Global.InstanceSaveMgr.RemoveInstanceSave(GetInstanceId());
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetPlayerCount() { return (uint)m_playerList.Count; }
|
||||
public uint GetGroupCount() { return (uint)m_groupList.Count; }
|
||||
|
||||
public uint GetInstanceId() { return m_instanceid; }
|
||||
public uint GetMapId() { return m_mapid; }
|
||||
|
||||
public long GetResetTime() { return m_resetTime; }
|
||||
public void SetResetTime(long resetTime) { m_resetTime = resetTime; }
|
||||
|
||||
public uint GetEntranceLocation() { return m_entranceId; }
|
||||
void SetEntranceLocation(uint entranceId) { m_entranceId = entranceId; }
|
||||
|
||||
public void AddPlayer(Player player)
|
||||
{
|
||||
m_playerList.Add(player);
|
||||
}
|
||||
public bool RemovePlayer(Player player)
|
||||
{
|
||||
m_playerList.Remove(player);
|
||||
|
||||
return UnloadIfEmpty();
|
||||
}
|
||||
|
||||
public void AddGroup(Group group) { m_groupList.Add(group); }
|
||||
public bool RemoveGroup(Group group)
|
||||
{
|
||||
m_groupList.Remove(group);
|
||||
|
||||
return UnloadIfEmpty();
|
||||
}
|
||||
|
||||
public bool CanReset() { return m_canReset; }
|
||||
public void SetCanReset(bool canReset) { m_canReset = canReset; }
|
||||
|
||||
public Difficulty GetDifficultyID() { return m_difficulty; }
|
||||
|
||||
public void SetToDelete(bool toDelete)
|
||||
{
|
||||
m_toDelete = toDelete;
|
||||
}
|
||||
|
||||
public List<Player> m_playerList = new List<Player>();
|
||||
public List<Group> m_groupList = new List<Group>();
|
||||
long m_resetTime;
|
||||
uint m_instanceid;
|
||||
uint m_mapid;
|
||||
Difficulty m_difficulty;
|
||||
uint m_entranceId;
|
||||
bool m_canReset;
|
||||
bool m_toDelete;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,946 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Framework.IO;
|
||||
using Game.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Network.Packets;
|
||||
using Game.Scenarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Text;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class InstanceScript : ZoneScript
|
||||
{
|
||||
public InstanceScript(Map map)
|
||||
{
|
||||
instance = map;
|
||||
}
|
||||
|
||||
public void SaveToDB()
|
||||
{
|
||||
InstanceScenario scenario = instance.ToInstanceMap().GetInstanceScenario();
|
||||
if (scenario != null)
|
||||
scenario.SaveToDB();
|
||||
|
||||
string data = GetSaveData();
|
||||
if (string.IsNullOrEmpty(data))
|
||||
return;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_DATA);
|
||||
stmt.AddValue(0, GetCompletedEncounterMask());
|
||||
stmt.AddValue(1, data);
|
||||
stmt.AddValue(2, _entranceId);
|
||||
stmt.AddValue(3, instance.GetInstanceId());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
public virtual bool IsEncounterInProgress()
|
||||
{
|
||||
foreach (var boss in bosses.Values)
|
||||
{
|
||||
if (boss.state == EncounterState.InProgress)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnCreatureCreate(Creature creature)
|
||||
{
|
||||
AddObject(creature, true);
|
||||
AddMinion(creature, true);
|
||||
}
|
||||
|
||||
public override void OnCreatureRemove(Creature creature)
|
||||
{
|
||||
AddObject(creature, false);
|
||||
AddMinion(creature, false);
|
||||
}
|
||||
|
||||
public override void OnGameObjectCreate(GameObject go)
|
||||
{
|
||||
AddObject(go, true);
|
||||
AddDoor(go, true);
|
||||
}
|
||||
|
||||
public override void OnGameObjectRemove(GameObject go)
|
||||
{
|
||||
AddObject(go, false);
|
||||
AddDoor(go, false);
|
||||
}
|
||||
|
||||
public ObjectGuid GetObjectGuid(uint type)
|
||||
{
|
||||
return _objectGuids.LookupByKey(type);
|
||||
}
|
||||
|
||||
public override ObjectGuid GetGuidData(uint type)
|
||||
{
|
||||
return GetObjectGuid(type);
|
||||
}
|
||||
|
||||
public void SetHeaders(string dataHeaders)
|
||||
{
|
||||
foreach (char header in dataHeaders)
|
||||
if (char.IsLetter(header))
|
||||
headers.Add(header);
|
||||
}
|
||||
|
||||
public void LoadBossBoundaries(BossBoundaryEntry[] data)
|
||||
{
|
||||
foreach (BossBoundaryEntry entry in data)
|
||||
{
|
||||
if (entry.BossId < bosses.Count)
|
||||
bosses[entry.BossId].boundary.Add(entry.Boundary);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadMinionData(params MinionData[] data)
|
||||
{
|
||||
foreach (var minion in data)
|
||||
{
|
||||
if (minion.entry == 0)
|
||||
continue;
|
||||
|
||||
if (minion.bossId < bosses.Count)
|
||||
minions.Add(minion.entry, new MinionInfo(bosses[minion.bossId]));
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadMinionData: {0} minions loaded.", minions.Count);
|
||||
}
|
||||
|
||||
public void LoadDoorData(params DoorData[] data)
|
||||
{
|
||||
foreach (var door in data)
|
||||
{
|
||||
if (door.entry == 0)
|
||||
continue;
|
||||
|
||||
if (door.bossId < bosses.Count)
|
||||
doors.Add(door.entry, new DoorInfo(bosses[door.bossId], door.type));
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadDoorData: {0} doors loaded.", doors.Count);
|
||||
}
|
||||
|
||||
public void LoadObjectData(ObjectData[] creatureData, ObjectData[] gameObjectData)
|
||||
{
|
||||
if (creatureData != null)
|
||||
LoadObjectData(creatureData, _creatureInfo);
|
||||
|
||||
if (gameObjectData != null)
|
||||
LoadObjectData(gameObjectData, _gameObjectInfo);
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadObjectData: {0} objects loaded.", _creatureInfo.Count + _gameObjectInfo.Count);
|
||||
}
|
||||
|
||||
void LoadObjectData(ObjectData[] objectData, Dictionary<uint, uint> objectInfo)
|
||||
{
|
||||
foreach (var data in objectData)
|
||||
{
|
||||
Contract.Assert(!objectInfo.ContainsKey(data.entry));
|
||||
objectInfo[data.entry] = data.type;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateMinionState(Creature minion, EncounterState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case EncounterState.NotStarted:
|
||||
if (!minion.IsAlive())
|
||||
minion.Respawn();
|
||||
else if (minion.IsInCombat())
|
||||
minion.GetAI().EnterEvadeMode();
|
||||
break;
|
||||
case EncounterState.InProgress:
|
||||
if (!minion.IsAlive())
|
||||
minion.Respawn();
|
||||
else if (minion.GetVictim() == null)
|
||||
minion.GetAI().DoZoneInCombat();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateDoorState(GameObject door)
|
||||
{
|
||||
var range = doors.LookupByKey(door.GetEntry());
|
||||
if (range.Empty())
|
||||
return;
|
||||
|
||||
bool open = true;
|
||||
foreach (var info in range)
|
||||
{
|
||||
if (!open)
|
||||
break;
|
||||
|
||||
switch (info.type)
|
||||
{
|
||||
case DoorType.Room:
|
||||
open = (info.bossInfo.state != EncounterState.InProgress);
|
||||
break;
|
||||
case DoorType.Passage:
|
||||
open = (info.bossInfo.state == EncounterState.Done);
|
||||
break;
|
||||
case DoorType.SpawnHole:
|
||||
open = (info.bossInfo.state == EncounterState.InProgress);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
door.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready);
|
||||
}
|
||||
|
||||
public BossInfo GetBossInfo(uint id)
|
||||
{
|
||||
Contract.Assert(id < bosses.Count);
|
||||
return bosses[id];
|
||||
}
|
||||
|
||||
void AddObject(Creature obj, bool add)
|
||||
{
|
||||
if (_creatureInfo.ContainsKey(obj.GetEntry()))
|
||||
AddObject(obj, _creatureInfo[obj.GetEntry()], add);
|
||||
}
|
||||
|
||||
void AddObject(GameObject obj, bool add)
|
||||
{
|
||||
if (_gameObjectInfo.ContainsKey(obj.GetEntry()))
|
||||
AddObject(obj, _gameObjectInfo[obj.GetEntry()], add);
|
||||
}
|
||||
|
||||
void AddObject(WorldObject obj, uint type, bool add)
|
||||
{
|
||||
if (add)
|
||||
_objectGuids[type] = obj.GetGUID();
|
||||
else
|
||||
{
|
||||
var guid = _objectGuids.LookupByKey(type);
|
||||
if (!guid.IsEmpty() && guid == obj.GetGUID())
|
||||
_objectGuids.Remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddDoor(GameObject door, bool add)
|
||||
{
|
||||
var range = doors.LookupByKey(door.GetEntry());
|
||||
if (range.Empty())
|
||||
return;
|
||||
|
||||
foreach (var data in range)
|
||||
{
|
||||
if (add)
|
||||
data.bossInfo.door[(int)data.type].Add(door.GetGUID());
|
||||
else
|
||||
data.bossInfo.door[(int)data.type].Remove(door.GetGUID());
|
||||
}
|
||||
|
||||
if (add)
|
||||
UpdateDoorState(door);
|
||||
}
|
||||
|
||||
public void AddMinion(Creature minion, bool add)
|
||||
{
|
||||
var minionInfo = minions.LookupByKey(minion.GetEntry());
|
||||
if (minionInfo == null)
|
||||
return;
|
||||
|
||||
if (add)
|
||||
minionInfo.bossInfo.minion.Add(minion.GetGUID());
|
||||
else
|
||||
minionInfo.bossInfo.minion.Remove(minion.GetGUID());
|
||||
}
|
||||
|
||||
public Creature GetCreature(uint type)
|
||||
{
|
||||
return instance.GetCreature(GetObjectGuid(type));
|
||||
}
|
||||
|
||||
public GameObject GetGameObject(uint type)
|
||||
{
|
||||
return instance.GetGameObject(GetObjectGuid(type));
|
||||
}
|
||||
|
||||
public virtual bool SetBossState(uint id, EncounterState state)
|
||||
{
|
||||
if (id < bosses.Count)
|
||||
{
|
||||
BossInfo bossInfo = bosses[id];
|
||||
if (bossInfo.state == EncounterState.ToBeDecided) // loading
|
||||
{
|
||||
bossInfo.state = state;
|
||||
//Log.outError(LogFilter.General "Inialize boss {0} state as {1}.", id, (uint32)state);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bossInfo.state == state)
|
||||
return false;
|
||||
|
||||
if (state == EncounterState.Done)
|
||||
{
|
||||
foreach (var guid in bossInfo.minion)
|
||||
{
|
||||
Creature minion = instance.GetCreature(guid);
|
||||
if (minion)
|
||||
if (minion.isWorldBoss() && minion.IsAlive())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case EncounterState.InProgress:
|
||||
{
|
||||
uint resInterval = GetCombatResurrectionChargeInterval();
|
||||
InitializeCombatResurrections(1, resInterval);
|
||||
SendEncounterStart(1, 9, resInterval, resInterval);
|
||||
break;
|
||||
}
|
||||
case EncounterState.Fail:
|
||||
case EncounterState.Done:
|
||||
ResetCombatResurrections();
|
||||
SendEncounterEnd();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bossInfo.state = state;
|
||||
SaveToDB();
|
||||
}
|
||||
|
||||
for (uint type = 0; type < (int)DoorType.Max; ++type)
|
||||
{
|
||||
foreach (var guid in bossInfo.door[type])
|
||||
{
|
||||
GameObject door = instance.GetGameObject(guid);
|
||||
if (door)
|
||||
UpdateDoorState(door);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var guid in bossInfo.minion.ToArray())
|
||||
{
|
||||
Creature minion = instance.GetCreature(guid);
|
||||
if (minion)
|
||||
UpdateMinionState(minion, state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool _SkipCheckRequiredBosses(Player player = null)
|
||||
{
|
||||
return player && player.GetSession().HasPermission(RBACPermissions.SkipCheckInstanceRequiredBosses);
|
||||
}
|
||||
|
||||
public virtual void Load(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
OUT_LOAD_INST_DATA_FAIL();
|
||||
return;
|
||||
}
|
||||
|
||||
OUT_LOAD_INST_DATA(data);
|
||||
|
||||
var loadStream = new StringArguments(data);
|
||||
|
||||
if (ReadSaveDataHeaders(loadStream))
|
||||
{
|
||||
ReadSaveDataBossStates(loadStream);
|
||||
ReadSaveDataMore(loadStream);
|
||||
}
|
||||
else
|
||||
OUT_LOAD_INST_DATA_FAIL();
|
||||
|
||||
OUT_LOAD_INST_DATA_COMPLETE();
|
||||
}
|
||||
|
||||
bool ReadSaveDataHeaders(StringArguments data)
|
||||
{
|
||||
foreach (char header in headers)
|
||||
{
|
||||
char buff = data.NextChar();
|
||||
|
||||
if (header != buff)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadSaveDataBossStates(StringArguments data)
|
||||
{
|
||||
uint bossId = 0;
|
||||
foreach (var i in bosses)
|
||||
{
|
||||
EncounterState buff = (EncounterState)data.NextUInt32();
|
||||
if (buff == EncounterState.InProgress || buff == EncounterState.Fail || buff == EncounterState.Special)
|
||||
buff = EncounterState.NotStarted;
|
||||
|
||||
if (buff < EncounterState.ToBeDecided)
|
||||
SetBossState(bossId++, buff);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string GetSaveData()
|
||||
{
|
||||
OUT_SAVE_INST_DATA();
|
||||
|
||||
StringBuilder saveStream = new StringBuilder();
|
||||
|
||||
WriteSaveDataHeaders(saveStream);
|
||||
WriteSaveDataBossStates(saveStream);
|
||||
WriteSaveDataMore(saveStream);
|
||||
|
||||
OUT_SAVE_INST_DATA_COMPLETE();
|
||||
|
||||
return saveStream.ToString();
|
||||
}
|
||||
|
||||
void WriteSaveDataHeaders(StringBuilder data)
|
||||
{
|
||||
foreach (char header in headers)
|
||||
data.AppendFormat("{0} ", header);
|
||||
}
|
||||
|
||||
void WriteSaveDataBossStates(StringBuilder data)
|
||||
{
|
||||
foreach (BossInfo bossInfo in bosses.Values)
|
||||
data.AppendFormat("{0} ", (uint)bossInfo.state);
|
||||
}
|
||||
|
||||
public void HandleGameObject(ObjectGuid guid, bool open, GameObject go = null)
|
||||
{
|
||||
if (!go)
|
||||
go = instance.GetGameObject(guid);
|
||||
if (go)
|
||||
go.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready);
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: HandleGameObject failed");
|
||||
}
|
||||
|
||||
public void DoUseDoorOrButton(ObjectGuid uiGuid, uint withRestoreTime = 0, bool useAlternativeState = false)
|
||||
{
|
||||
if (uiGuid.IsEmpty())
|
||||
return;
|
||||
|
||||
GameObject go = instance.GetGameObject(uiGuid);
|
||||
if (go)
|
||||
{
|
||||
if (go.GetGoType() == GameObjectTypes.Door || go.GetGoType() == GameObjectTypes.Button)
|
||||
{
|
||||
if (go.getLootState() == LootState.Ready)
|
||||
go.UseDoorOrButton(withRestoreTime, useAlternativeState);
|
||||
else if (go.getLootState() == LootState.Activated)
|
||||
go.ResetDoorOrButton();
|
||||
}
|
||||
else
|
||||
Log.outError(LogFilter.Scripts, "InstanceScript: DoUseDoorOrButton can't use gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType());
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: DoUseDoorOrButton failed");
|
||||
}
|
||||
|
||||
void DoCloseDoorOrButton(ObjectGuid guid)
|
||||
{
|
||||
if (guid.IsEmpty())
|
||||
return;
|
||||
|
||||
GameObject go = instance.GetGameObject(guid);
|
||||
if (go)
|
||||
{
|
||||
if (go.GetGoType() == GameObjectTypes.Door || go.GetGoType() == GameObjectTypes.Button)
|
||||
{
|
||||
if (go.getLootState() == LootState.Activated)
|
||||
go.ResetDoorOrButton();
|
||||
}
|
||||
else
|
||||
Log.outError(LogFilter.Scripts, "InstanceScript: DoCloseDoorOrButton can't use gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType());
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: DoCloseDoorOrButton failed");
|
||||
}
|
||||
|
||||
public void DoRespawnGameObject(ObjectGuid guid, uint timeToDespawn)
|
||||
{
|
||||
GameObject go = instance.GetGameObject(guid);
|
||||
if (go)
|
||||
{
|
||||
switch (go.GetGoType())
|
||||
{
|
||||
case GameObjectTypes.Door:
|
||||
case GameObjectTypes.Button:
|
||||
case GameObjectTypes.Trap:
|
||||
case GameObjectTypes.FishingNode:
|
||||
// not expect any of these should ever be handled
|
||||
Log.outError(LogFilter.Scripts, "InstanceScript: DoRespawnGameObject can't respawn gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType());
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (go.isSpawned())
|
||||
return;
|
||||
|
||||
go.SetRespawnTime((int)timeToDespawn);
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: DoRespawnGameObject failed");
|
||||
}
|
||||
|
||||
public void DoUpdateWorldState(uint uiStateId, uint uiStateData)
|
||||
{
|
||||
var lPlayers = instance.GetPlayers();
|
||||
|
||||
if (!lPlayers.Empty())
|
||||
{
|
||||
foreach (var player in lPlayers)
|
||||
player.SendUpdateWorldState(uiStateId, uiStateData);
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "DoUpdateWorldState attempt send data but no players in map.");
|
||||
}
|
||||
|
||||
// Send Notify to all players in instance
|
||||
void DoSendNotifyToInstance(string format, params object[] args)
|
||||
{
|
||||
var players = instance.GetPlayers();
|
||||
|
||||
if (!players.Empty())
|
||||
{
|
||||
foreach (var player in players)
|
||||
{
|
||||
WorldSession session = player.GetSession();
|
||||
if (session != null)
|
||||
session.SendNotification(format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update Achievement Criteria for all players in instance
|
||||
public void DoUpdateCriteria(CriteriaTypes type, uint miscValue1 = 0, uint miscValue2 = 0, Unit unit = null)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.UpdateCriteria(type, miscValue1, miscValue2, 0, unit);
|
||||
}
|
||||
|
||||
// Start timed achievement for all players in instance
|
||||
public void DoStartCriteriaTimer(CriteriaTimedTypes type, uint entry)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.StartCriteriaTimer(type, entry);
|
||||
}
|
||||
|
||||
// Stop timed achievement for all players in instance
|
||||
public void DoStopCriteriaTimer(CriteriaTimedTypes type, uint entry)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.RemoveCriteriaTimer(type, entry);
|
||||
}
|
||||
|
||||
// Remove Auras due to Spell on all players in instance
|
||||
public void DoRemoveAurasDueToSpellOnPlayers(uint spell)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
if (!PlayerList.Empty())
|
||||
{
|
||||
foreach (var player in PlayerList)
|
||||
{
|
||||
player.RemoveAurasDueToSpell(spell);
|
||||
Pet pet = player.GetPet();
|
||||
if (pet != null)
|
||||
pet.RemoveAurasDueToSpell(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cast spell on all players in instance
|
||||
public void DoCastSpellOnPlayers(uint spell)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.CastSpell(player, spell, true);
|
||||
}
|
||||
|
||||
public virtual bool CheckAchievementCriteriaMeet(uint criteria_id, Player source, Unit target = null, uint miscvalue1 = 0)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "Achievement system call CheckAchievementCriteriaMeet but instance script for map {0} not have implementation for achievement criteria {1}",
|
||||
instance.GetId(), criteria_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetEntranceLocation(uint worldSafeLocationId)
|
||||
{
|
||||
_entranceId = worldSafeLocationId;
|
||||
if (_temporaryEntranceId != 0)
|
||||
_temporaryEntranceId = 0;
|
||||
}
|
||||
|
||||
public void SendEncounterUnit(EncounterFrameType type, Unit unit = null, byte priority = 0)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EncounterFrameType.Engage:
|
||||
if (unit == null)
|
||||
return;
|
||||
|
||||
InstanceEncounterEngageUnit encounterEngageMessage = new InstanceEncounterEngageUnit();
|
||||
encounterEngageMessage.Unit = unit.GetGUID();
|
||||
encounterEngageMessage.TargetFramePriority = priority;
|
||||
instance.SendToPlayers(encounterEngageMessage);
|
||||
break;
|
||||
case EncounterFrameType.Disengage:
|
||||
if (!unit)
|
||||
return;
|
||||
|
||||
InstanceEncounterDisengageUnit encounterDisengageMessage = new InstanceEncounterDisengageUnit();
|
||||
encounterDisengageMessage.Unit = unit.GetGUID();
|
||||
instance.SendToPlayers(encounterDisengageMessage);
|
||||
break;
|
||||
case EncounterFrameType.UpdatePriority:
|
||||
if (!unit)
|
||||
return;
|
||||
|
||||
InstanceEncounterChangePriority encounterChangePriorityMessage = new InstanceEncounterChangePriority();
|
||||
encounterChangePriorityMessage.Unit = unit.GetGUID();
|
||||
encounterChangePriorityMessage.TargetFramePriority = priority;
|
||||
instance.SendToPlayers(encounterChangePriorityMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SendEncounterStart(uint inCombatResCount = 0, uint maxInCombatResCount = 0, uint inCombatResChargeRecovery = 0, uint nextCombatResChargeTime = 0)
|
||||
{
|
||||
InstanceEncounterStart encounterStartMessage = new InstanceEncounterStart();
|
||||
encounterStartMessage.InCombatResCount = inCombatResCount;
|
||||
encounterStartMessage.MaxInCombatResCount = maxInCombatResCount;
|
||||
encounterStartMessage.CombatResChargeRecovery = inCombatResChargeRecovery;
|
||||
encounterStartMessage.NextCombatResChargeTime = nextCombatResChargeTime;
|
||||
|
||||
instance.SendToPlayers(encounterStartMessage);
|
||||
}
|
||||
|
||||
void SendEncounterEnd()
|
||||
{
|
||||
instance.SendToPlayers(new InstanceEncounterEnd());
|
||||
}
|
||||
|
||||
void SendBossKillCredit(uint encounterId)
|
||||
{
|
||||
BossKillCredit bossKillCreditMessage = new BossKillCredit();
|
||||
bossKillCreditMessage.DungeonEncounterID = encounterId;
|
||||
|
||||
instance.SendToPlayers(bossKillCreditMessage);
|
||||
}
|
||||
|
||||
public void UpdateEncounterStateForKilledCreature(uint creatureId, Unit source)
|
||||
{
|
||||
UpdateEncounterState(EncounterCreditType.KillCreature, creatureId, source);
|
||||
}
|
||||
|
||||
public void UpdateEncounterStateForSpellCast(uint spellId, Unit source)
|
||||
{
|
||||
UpdateEncounterState(EncounterCreditType.CastSpell, spellId, source);
|
||||
}
|
||||
|
||||
void UpdateEncounterState(EncounterCreditType type, uint creditEntry, Unit source)
|
||||
{
|
||||
var encounters = Global.ObjectMgr.GetDungeonEncounterList(instance.GetId(), instance.GetDifficultyID());
|
||||
if (encounters.Empty())
|
||||
return;
|
||||
|
||||
uint dungeonId = 0;
|
||||
|
||||
foreach (var encounter in encounters)
|
||||
{
|
||||
if (encounter.creditType == type && encounter.creditEntry == creditEntry)
|
||||
{
|
||||
completedEncounters |= (1u << encounter.dbcEntry.Bit);
|
||||
if (encounter.lastEncounterDungeon != 0)
|
||||
{
|
||||
dungeonId = encounter.lastEncounterDungeon;
|
||||
Log.outDebug(LogFilter.Lfg, "UpdateEncounterState: Instance {0} (instanceId {1}) completed encounter {2}. Credit Dungeon: {3}",
|
||||
instance.GetMapName(), instance.GetInstanceId(), encounter.dbcEntry.Name[Global.WorldMgr.GetDefaultDbcLocale()], dungeonId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dungeonId != 0)
|
||||
{
|
||||
var players = instance.GetPlayers();
|
||||
foreach (var player in players)
|
||||
{
|
||||
Group grp = player.GetGroup();
|
||||
if (grp != null)
|
||||
if (grp.isLFGGroup())
|
||||
{
|
||||
Global.LFGMgr.FinishDungeon(grp.GetGUID(), dungeonId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePhasing()
|
||||
{
|
||||
var players = instance.GetPlayers();
|
||||
foreach (var player in players)
|
||||
player.SendUpdatePhasing();
|
||||
}
|
||||
|
||||
public void UpdateCombatResurrection(uint diff)
|
||||
{
|
||||
if (!_combatResurrectionTimerStarted)
|
||||
return;
|
||||
|
||||
_combatResurrectionTimer -= diff;
|
||||
if (_combatResurrectionTimer <= 0)
|
||||
{
|
||||
AddCombatResurrectionCharge();
|
||||
_combatResurrectionTimerStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeCombatResurrections(byte charges = 1, uint interval = 0)
|
||||
{
|
||||
_combatResurrectionCharges = charges;
|
||||
if (interval == 0)
|
||||
return;
|
||||
|
||||
_combatResurrectionTimer = interval;
|
||||
_combatResurrectionTimerStarted = true;
|
||||
}
|
||||
|
||||
public void AddCombatResurrectionCharge()
|
||||
{
|
||||
++_combatResurrectionCharges;
|
||||
_combatResurrectionTimer = GetCombatResurrectionChargeInterval();
|
||||
_combatResurrectionTimerStarted = true;
|
||||
|
||||
var gainCombatResurrectionCharge = new InstanceEncounterGainCombatResurrectionCharge();
|
||||
gainCombatResurrectionCharge.InCombatResCount = _combatResurrectionCharges;
|
||||
gainCombatResurrectionCharge.CombatResChargeRecovery = _combatResurrectionTimer;
|
||||
instance.SendToPlayers(gainCombatResurrectionCharge);
|
||||
}
|
||||
|
||||
public void UseCombatResurrection()
|
||||
{
|
||||
--_combatResurrectionCharges;
|
||||
|
||||
instance.SendToPlayers(new InstanceEncounterInCombatResurrection());
|
||||
}
|
||||
|
||||
public void ResetCombatResurrections()
|
||||
{
|
||||
_combatResurrectionCharges = 0;
|
||||
_combatResurrectionTimer = 0;
|
||||
_combatResurrectionTimerStarted = false;
|
||||
}
|
||||
|
||||
public uint GetCombatResurrectionChargeInterval()
|
||||
{
|
||||
uint interval = 0;
|
||||
int playerCount = instance.GetPlayers().Count;
|
||||
if (playerCount != 0)
|
||||
interval = (uint)(90 * Time.Minute * Time.InMilliseconds / playerCount);
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
bool InstanceHasScript(WorldObject obj, string scriptName)
|
||||
{
|
||||
InstanceMap instance = obj.GetMap().ToInstanceMap();
|
||||
if (instance != null)
|
||||
return instance.GetScriptName() == scriptName;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Initialize() { }
|
||||
public virtual void Update(uint diff) { }
|
||||
public virtual void OnPlayerEnter(Player player) { }
|
||||
|
||||
// Return wether server allow two side groups or not
|
||||
public bool ServerAllowsTwoSideGroups() { return WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup); }
|
||||
|
||||
public EncounterState GetBossState(uint id) { return id < bosses.Count ? bosses[id].state : EncounterState.ToBeDecided; }
|
||||
public List<AreaBoundary> GetBossBoundary(uint id) { return id < bosses.Count ? bosses[id].boundary : null; }
|
||||
|
||||
public virtual bool CheckRequiredBosses(uint bossId, Player player = null) { return true; }
|
||||
|
||||
public void SetCompletedEncountersMask(uint newMask) { completedEncounters = newMask; }
|
||||
|
||||
public uint GetCompletedEncounterMask() { return completedEncounters; }
|
||||
|
||||
// Sets a temporary entrance that does not get saved to db
|
||||
void SetTemporaryEntranceLocation(uint worldSafeLocationId) { _temporaryEntranceId = worldSafeLocationId; }
|
||||
|
||||
// Get's the current entrance id
|
||||
public uint GetEntranceLocation() { return _temporaryEntranceId != 0 ? _temporaryEntranceId : _entranceId; }
|
||||
|
||||
public virtual void FillInitialWorldStates(InitWorldStates data) { }
|
||||
|
||||
public int GetEncounterCount() { return bosses.Count; }
|
||||
|
||||
public byte GetCombatResurrectionCharges() { return _combatResurrectionCharges; }
|
||||
|
||||
public void SetBossNumber(uint number)
|
||||
{
|
||||
for (uint i = 0; i < number; ++i)
|
||||
bosses.Add(i, new BossInfo());
|
||||
}
|
||||
|
||||
public void OUT_SAVE_INST_DATA() { Log.outDebug(LogFilter.Scripts, "Saving Instance Data for Instance {0} (Map {1}, Instance Id {2})", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
public void OUT_SAVE_INST_DATA_COMPLETE() { Log.outDebug(LogFilter.Scripts, "Saving Instance Data for Instance {0} (Map {1}, Instance Id {2}) completed.", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
public void OUT_LOAD_INST_DATA(string input) { Log.outDebug(LogFilter.Scripts, "Loading Instance Data for Instance {0} (Map {1}, Instance Id {2}). Input is '{3}'", instance.GetMapName(), instance.GetId(), instance.GetInstanceId(), input); }
|
||||
public void OUT_LOAD_INST_DATA_COMPLETE() { Log.outDebug(LogFilter.Scripts, "Instance Data Load for Instance {0} (Map {1}, Instance Id: {2}) is complete.", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
public void OUT_LOAD_INST_DATA_FAIL() { Log.outDebug(LogFilter.Scripts, "Unable to load Instance Data for Instance {0} (Map {1}, Instance Id: {2}).", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
|
||||
public virtual void ReadSaveDataMore(StringArguments data) { }
|
||||
|
||||
public virtual void WriteSaveDataMore(StringBuilder data) { }
|
||||
|
||||
public Map instance;
|
||||
List<char> headers = new List<char>();
|
||||
Dictionary<uint, BossInfo> bosses = new Dictionary<uint, BossInfo>();
|
||||
MultiMap<uint, DoorInfo> doors = new MultiMap<uint, DoorInfo>();
|
||||
Dictionary<uint, MinionInfo> minions = new Dictionary<uint, MinionInfo>();
|
||||
Dictionary<uint, uint> _creatureInfo = new Dictionary<uint, uint>();
|
||||
Dictionary<uint, uint> _gameObjectInfo = new Dictionary<uint, uint>();
|
||||
Dictionary<uint, ObjectGuid> _objectGuids = new Dictionary<uint, ObjectGuid>();
|
||||
uint completedEncounters;
|
||||
uint _entranceId;
|
||||
uint _temporaryEntranceId;
|
||||
uint _combatResurrectionTimer;
|
||||
byte _combatResurrectionCharges; // the counter for available battle resurrections
|
||||
bool _combatResurrectionTimerStarted;
|
||||
}
|
||||
|
||||
|
||||
public class DoorData
|
||||
{
|
||||
public DoorData(uint _entry, uint _bossid, DoorType _type)
|
||||
{
|
||||
entry = _entry;
|
||||
bossId = _bossid;
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public uint entry;
|
||||
public uint bossId;
|
||||
public DoorType type;
|
||||
}
|
||||
|
||||
public class BossBoundaryEntry
|
||||
{
|
||||
public BossBoundaryEntry(uint bossId, AreaBoundary boundary)
|
||||
{
|
||||
BossId = bossId;
|
||||
Boundary = boundary;
|
||||
}
|
||||
|
||||
public uint BossId;
|
||||
public AreaBoundary Boundary;
|
||||
}
|
||||
|
||||
public class MinionData
|
||||
{
|
||||
public MinionData(uint _entry, uint _bossid)
|
||||
{
|
||||
entry = _entry;
|
||||
bossId = _bossid;
|
||||
}
|
||||
|
||||
public uint entry;
|
||||
public uint bossId;
|
||||
}
|
||||
|
||||
public struct ObjectData
|
||||
{
|
||||
public ObjectData(uint _entry, uint _type)
|
||||
{
|
||||
entry = _entry;
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public uint entry;
|
||||
public uint type;
|
||||
}
|
||||
|
||||
public class BossInfo
|
||||
{
|
||||
public BossInfo()
|
||||
{
|
||||
state = EncounterState.ToBeDecided;
|
||||
for (var i = 0; i < (int)DoorType.Max; ++i)
|
||||
door[i] = new List<ObjectGuid>();
|
||||
}
|
||||
|
||||
public EncounterState state;
|
||||
public List<ObjectGuid>[] door = new List<ObjectGuid>[(int)DoorType.Max];
|
||||
public List<ObjectGuid> minion = new List<ObjectGuid>();
|
||||
public List<AreaBoundary> boundary = new List<AreaBoundary>();
|
||||
}
|
||||
class DoorInfo
|
||||
{
|
||||
public DoorInfo(BossInfo _bossInfo, DoorType _type)
|
||||
{
|
||||
bossInfo = _bossInfo;
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public BossInfo bossInfo;
|
||||
public DoorType type;
|
||||
}
|
||||
class MinionInfo
|
||||
{
|
||||
public MinionInfo(BossInfo _bossInfo)
|
||||
{
|
||||
bossInfo = _bossInfo;
|
||||
}
|
||||
|
||||
public BossInfo bossInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.BattleGrounds;
|
||||
using Game.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Garrisons;
|
||||
using Game.Groups;
|
||||
using Game.Scenarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class MapInstanced : Map
|
||||
{
|
||||
public MapInstanced(uint id, uint expiry) : base(id, expiry, 0, Difficulty.Normal)
|
||||
{
|
||||
for (uint x = 0; x < MapConst.MaxGrids; ++x)
|
||||
GridMapReference[x] = new uint[MapConst.MaxGrids];
|
||||
}
|
||||
|
||||
public override void InitVisibilityDistance()
|
||||
{
|
||||
if (m_InstancedMaps.Empty())
|
||||
return;
|
||||
//initialize visibility distances for all instance copies
|
||||
foreach (var i in m_InstancedMaps)
|
||||
i.Value.InitVisibilityDistance();
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
base.Update(diff);
|
||||
|
||||
foreach (var i in m_InstancedMaps.ToList())
|
||||
{
|
||||
if (i.Value.CanUnload(diff))
|
||||
{
|
||||
if (!DestroyInstance(i))
|
||||
{
|
||||
//m_unloadTimer
|
||||
}
|
||||
}
|
||||
else
|
||||
i.Value.Update(diff);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DelayedUpdate(uint t_diff)
|
||||
{
|
||||
foreach (var i in m_InstancedMaps)
|
||||
i.Value.DelayedUpdate(t_diff);
|
||||
|
||||
base.DelayedUpdate(t_diff);
|
||||
}
|
||||
|
||||
public override void UnloadAll()
|
||||
{
|
||||
// Unload instanced maps
|
||||
foreach (var i in m_InstancedMaps)
|
||||
i.Value.UnloadAll();
|
||||
|
||||
m_InstancedMaps.Clear();
|
||||
|
||||
base.UnloadAll();
|
||||
}
|
||||
|
||||
public Map CreateInstanceForPlayer(uint mapId, Player player, uint loginInstanceId = 0)
|
||||
{
|
||||
if (GetId() != mapId || player == null)
|
||||
return null;
|
||||
|
||||
Map map = null;
|
||||
uint newInstanceId = 0; // instanceId of the resulting map
|
||||
|
||||
if (IsBattlegroundOrArena())
|
||||
{
|
||||
// instantiate or find existing bg map for player
|
||||
// the instance id is set in Battlegroundid
|
||||
newInstanceId = player.GetBattlegroundId();
|
||||
if (newInstanceId == 0)
|
||||
return null;
|
||||
|
||||
map = Global.MapMgr.FindMap(mapId, newInstanceId);
|
||||
if (map == null)
|
||||
{
|
||||
Battleground bg = player.GetBattleground();
|
||||
if (bg)
|
||||
map = CreateBattleground(newInstanceId, bg);
|
||||
else
|
||||
{
|
||||
player.TeleportToBGEntryPoint();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!IsGarrison())
|
||||
{
|
||||
InstanceBind pBind = player.GetBoundInstance(GetId(), player.GetDifficultyID(GetEntry()));
|
||||
InstanceSave pSave = pBind != null ? pBind.save : null;
|
||||
|
||||
// priority:
|
||||
// 1. player's permanent bind
|
||||
// 2. player's current instance id if this is at login
|
||||
// 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)
|
||||
{
|
||||
map = FindInstanceMap(loginInstanceId);
|
||||
return (map && map.GetId() == GetId()) ? map : null; // is this check necessary? or does MapInstanced only find instances of itself?
|
||||
}
|
||||
|
||||
InstanceBind groupBind = null;
|
||||
Group group = player.GetGroup();
|
||||
// use the player's difficulty setting (it may not be the same as the group's)
|
||||
if (group)
|
||||
{
|
||||
groupBind = group.GetBoundInstance(this);
|
||||
if (groupBind != null)
|
||||
{
|
||||
// solo saves should be reset when entering a group's instance
|
||||
player.UnbindInstance(GetId(), player.GetDifficultyID(GetEntry()));
|
||||
pSave = groupBind.save;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pSave != null)
|
||||
{
|
||||
// solo/perm/group
|
||||
newInstanceId = pSave.GetInstanceId();
|
||||
map = FindInstanceMap(newInstanceId);
|
||||
// it is possible that the save exists but the map doesn't
|
||||
if (map == null)
|
||||
map = CreateInstance(newInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId());
|
||||
}
|
||||
else
|
||||
{
|
||||
// if no instanceId via group members or instance saves is found
|
||||
// the instance will be created for the first time
|
||||
newInstanceId = Global.MapMgr.GenerateInstanceId();
|
||||
|
||||
Difficulty diff = player.GetGroup() != null ? player.GetGroup().GetDifficultyID(GetEntry()) : player.GetDifficultyID(GetEntry());
|
||||
//Seems it is now possible, but I do not know if it should be allowed
|
||||
map = FindInstanceMap(newInstanceId);
|
||||
if (map == null)
|
||||
map = CreateInstance(newInstanceId, null, diff, player.GetTeamId());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newInstanceId = (uint)player.GetGUID().GetCounter();
|
||||
map = FindInstanceMap(newInstanceId);
|
||||
if (!map)
|
||||
map = CreateGarrison(newInstanceId, player);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
InstanceMap CreateInstance(uint InstanceId, InstanceSave save, Difficulty difficulty, int teamId)
|
||||
{
|
||||
// make sure we have a valid map id
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(GetId());
|
||||
if (entry == null)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "CreateInstance: no record for map {0}", GetId());
|
||||
Contract.Assert(false);
|
||||
}
|
||||
InstanceTemplate iTemplate = Global.ObjectMgr.GetInstanceTemplate(GetId());
|
||||
if (iTemplate == null)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "CreateInstance: no instance template for map {0}", GetId());
|
||||
Contract.Assert(false);
|
||||
}
|
||||
|
||||
// some instances only have one difficulty
|
||||
Global.DB2Mgr.GetDownscaledMapDifficultyData(GetId(), ref difficulty);
|
||||
|
||||
Log.outDebug(LogFilter.Maps, "MapInstanced.CreateInstance: {0} map instance {1} for {2} created with difficulty {3}", save != null ? "" : "new ", InstanceId, GetId(), difficulty);
|
||||
|
||||
InstanceMap map = new InstanceMap(GetId(), GetGridExpiry(), InstanceId, difficulty, this);
|
||||
Contract.Assert(map.IsDungeon());
|
||||
|
||||
map.LoadRespawnTimes();
|
||||
map.LoadCorpseData();
|
||||
|
||||
bool load_data = save != null;
|
||||
map.CreateInstanceData(load_data);
|
||||
InstanceScenario instanceScenario = Global.ScenarioMgr.CreateInstanceScenario(map, teamId);
|
||||
if (instanceScenario != null)
|
||||
map.SetInstanceScenario(instanceScenario);
|
||||
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids))
|
||||
map.LoadAllCells();
|
||||
|
||||
m_InstancedMaps[InstanceId] = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
BattlegroundMap CreateBattleground(uint InstanceId, Battleground bg)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MapInstanced.CreateBattleground: map bg {0} for {1} created.", InstanceId, GetId());
|
||||
|
||||
BattlegroundMap map = new BattlegroundMap(GetId(), (uint)GetGridExpiry(), InstanceId, this, Difficulty.None);
|
||||
Contract.Assert(map.IsBattlegroundOrArena());
|
||||
map.SetBG(bg);
|
||||
bg.SetBgMap(map);
|
||||
|
||||
m_InstancedMaps[InstanceId] = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
GarrisonMap CreateGarrison(uint instanceId, Player owner)
|
||||
{
|
||||
GarrisonMap map = new GarrisonMap(GetId(), GetGridExpiry(), instanceId, this, owner.GetGUID());
|
||||
Contract.Assert(map.IsGarrison());
|
||||
|
||||
m_InstancedMaps[instanceId] = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
bool DestroyInstance(KeyValuePair<uint, Map> pair)
|
||||
{
|
||||
pair.Value.RemoveAllPlayers();
|
||||
if (pair.Value.HavePlayers())
|
||||
return false;
|
||||
|
||||
pair.Value.UnloadAll();
|
||||
// should only unload VMaps if this is the last instance and grid unloading is enabled
|
||||
if (m_InstancedMaps.Count <= 1 && WorldConfig.GetBoolValue(WorldCfg.GridUnload))
|
||||
{
|
||||
Global.VMapMgr.unloadMap(pair.Value.GetId());
|
||||
Global.MMapMgr.unloadMap(pair.Value.GetId());
|
||||
// in that case, unload grids of the base map, too
|
||||
// so in the next map creation, (EnsureGridCreated actually) VMaps will be reloaded
|
||||
base.UnloadAll();
|
||||
}
|
||||
|
||||
// Free up the instance id and allow it to be reused for bgs and arenas (other instances are handled in the InstanceSaveMgr)
|
||||
if (pair.Value.IsBattlegroundOrArena())
|
||||
Global.MapMgr.FreeInstanceId(pair.Value.GetInstanceId());
|
||||
|
||||
// erase map
|
||||
m_InstancedMaps.Remove(pair.Key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override EnterState CannotEnter(Player player) { return EnterState.CanEnter; }
|
||||
|
||||
public Map FindInstanceMap(uint instanceId)
|
||||
{
|
||||
return m_InstancedMaps.LookupByKey(instanceId);
|
||||
}
|
||||
|
||||
public void AddGridMapReference(GridCoord p)
|
||||
{
|
||||
++GridMapReference[p.x_coord][p.y_coord];
|
||||
SetUnloadReferenceLock(new GridCoord(63 - p.x_coord, 63 - p.y_coord), true);
|
||||
}
|
||||
|
||||
public void RemoveGridMapReference(GridCoord p)
|
||||
{
|
||||
--GridMapReference[p.x_coord][p.y_coord];
|
||||
if (GridMapReference[p.x_coord][p.y_coord] == 0)
|
||||
SetUnloadReferenceLock(new GridCoord(63 - p.x_coord, 63 - p.y_coord), false);
|
||||
}
|
||||
|
||||
public Dictionary<uint, Map> GetInstancedMaps() { return m_InstancedMaps; }
|
||||
|
||||
Dictionary<uint, Map> m_InstancedMaps = new Dictionary<uint, Map>();
|
||||
uint[][] GridMapReference = new uint[MapConst.MaxGrids][];
|
||||
}
|
||||
|
||||
public class InstanceTemplate
|
||||
{
|
||||
public uint Parent;
|
||||
public uint ScriptId;
|
||||
public bool AllowMount;
|
||||
}
|
||||
|
||||
public class InstanceBind
|
||||
{
|
||||
public InstanceSave save;
|
||||
public bool perm;
|
||||
public BindExtensionState extendState;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user