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,192 @@
|
||||
/*
|
||||
* 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.DataStorage;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class CinematicManager : IDisposable
|
||||
{
|
||||
public CinematicManager(Player playerref)
|
||||
{
|
||||
player = playerref;
|
||||
m_cinematicDiff = 0;
|
||||
m_lastCinematicCheck = 0;
|
||||
m_activeCinematicCameraId = 0;
|
||||
m_cinematicLength = 0;
|
||||
m_cinematicCamera = null;
|
||||
m_remoteSightPosition = new Position(0.0f, 0.0f, 0.0f);
|
||||
m_CinematicObject = null;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (m_cinematicCamera != null && m_activeCinematicCameraId != 0)
|
||||
EndCinematic();
|
||||
}
|
||||
|
||||
public void BeginCinematic()
|
||||
{
|
||||
// Sanity check for active camera set
|
||||
if (m_activeCinematicCameraId == 0)
|
||||
return;
|
||||
|
||||
var list = M2Storage.GetFlyByCameras(m_activeCinematicCameraId);
|
||||
if (!list.Empty())
|
||||
{
|
||||
// Initialize diff, and set camera
|
||||
m_cinematicDiff = 0;
|
||||
m_cinematicCamera = list;
|
||||
|
||||
if (!m_cinematicCamera.Empty())
|
||||
{
|
||||
FlyByCamera firstCamera = m_cinematicCamera.FirstOrDefault();
|
||||
Position pos = new Position(firstCamera.locations.X, firstCamera.locations.Y, firstCamera.locations.Z, firstCamera.locations.W);
|
||||
if (!pos.IsPositionValid())
|
||||
return;
|
||||
|
||||
player.GetMap().LoadGrid(firstCamera.locations.X, firstCamera.locations.Y);
|
||||
m_CinematicObject = player.SummonCreature(1, pos.posX, pos.posY, pos.posZ, 0.0f, TempSummonType.TimedDespawn, 5 * Time.Minute * Time.InMilliseconds);
|
||||
if (m_CinematicObject)
|
||||
{
|
||||
m_CinematicObject.setActive(true);
|
||||
player.SetViewpoint(m_CinematicObject, true);
|
||||
}
|
||||
|
||||
// Get cinematic length
|
||||
m_cinematicLength = m_cinematicCamera.LastOrDefault().timeStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EndCinematic()
|
||||
{
|
||||
if (m_activeCinematicCameraId == 0)
|
||||
return;
|
||||
|
||||
m_cinematicDiff = 0;
|
||||
m_cinematicCamera = null;
|
||||
m_activeCinematicCameraId = 0;
|
||||
if (m_CinematicObject)
|
||||
{
|
||||
WorldObject vpObject = player.GetViewpoint();
|
||||
if (vpObject)
|
||||
if (vpObject == m_CinematicObject)
|
||||
player.SetViewpoint(m_CinematicObject, false);
|
||||
|
||||
m_CinematicObject.AddObjectToRemoveList();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCinematicLocation(uint diff)
|
||||
{
|
||||
if (m_activeCinematicCameraId == 0 || m_cinematicCamera == null || m_cinematicCamera.Count == 0)
|
||||
return;
|
||||
|
||||
Position lastPosition = new Position();
|
||||
uint lastTimestamp = 0;
|
||||
Position nextPosition = new Position();
|
||||
uint nextTimestamp = 0;
|
||||
|
||||
// Obtain direction of travel
|
||||
foreach (FlyByCamera cam in m_cinematicCamera)
|
||||
{
|
||||
if (cam.timeStamp > m_cinematicDiff)
|
||||
{
|
||||
nextPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
nextTimestamp = cam.timeStamp;
|
||||
break;
|
||||
}
|
||||
lastPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
lastTimestamp = cam.timeStamp;
|
||||
}
|
||||
float angle = lastPosition.GetAngle(nextPosition);
|
||||
angle -= lastPosition.GetOrientation();
|
||||
if (angle < 0)
|
||||
angle += 2 * MathFunctions.PI;
|
||||
|
||||
// Look for position around 2 second ahead of us.
|
||||
int workDiff = (int)m_cinematicDiff;
|
||||
|
||||
// Modify result based on camera direction (Humans for example, have the camera point behind)
|
||||
workDiff += (int)((2 * Time.InMilliseconds) * Math.Cos(angle));
|
||||
|
||||
// Get an iterator to the last entry in the cameras, to make sure we don't go beyond the end
|
||||
var endItr = m_cinematicCamera.LastOrDefault();
|
||||
if (endItr != null && workDiff > endItr.timeStamp)
|
||||
workDiff = (int)endItr.timeStamp;
|
||||
|
||||
// Never try to go back in time before the start of cinematic!
|
||||
if (workDiff < 0)
|
||||
workDiff = (int)m_cinematicDiff;
|
||||
|
||||
// Obtain the previous and next waypoint based on timestamp
|
||||
foreach (FlyByCamera cam in m_cinematicCamera)
|
||||
{
|
||||
if (cam.timeStamp >= workDiff)
|
||||
{
|
||||
nextPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
nextTimestamp = cam.timeStamp;
|
||||
break;
|
||||
}
|
||||
lastPosition = new Position(cam.locations.X, cam.locations.Y, cam.locations.Z, cam.locations.W);
|
||||
lastTimestamp = cam.timeStamp;
|
||||
}
|
||||
|
||||
// Never try to go beyond the end of the cinematic
|
||||
if (workDiff > nextTimestamp)
|
||||
workDiff = (int)nextTimestamp;
|
||||
|
||||
// Interpolate the position for this moment in time (or the adjusted moment in time)
|
||||
uint timeDiff = nextTimestamp - lastTimestamp;
|
||||
uint interDiff = (uint)(workDiff - lastTimestamp);
|
||||
float xDiff = nextPosition.posX - lastPosition.posX;
|
||||
float yDiff = nextPosition.posY - lastPosition.posY;
|
||||
float zDiff = nextPosition.posZ - lastPosition.posZ;
|
||||
Position interPosition = new Position(lastPosition.posX + (xDiff * (interDiff / timeDiff)), lastPosition.posY +
|
||||
(yDiff * (interDiff / timeDiff)), lastPosition.posZ + (zDiff * (interDiff / timeDiff)));
|
||||
|
||||
// Advance (at speed) to this position. The remote sight object is used
|
||||
// to send update information to player in cinematic
|
||||
if (m_CinematicObject && interPosition.IsPositionValid())
|
||||
m_CinematicObject.MonsterMoveWithSpeed(interPosition.posX, interPosition.posY, interPosition.posZ, 500.0f, false, true);
|
||||
|
||||
// If we never received an end packet 10 seconds after the final timestamp then force an end
|
||||
if (m_cinematicDiff > m_cinematicLength + 10 * Time.InMilliseconds)
|
||||
EndCinematic();
|
||||
}
|
||||
|
||||
uint GetActiveCinematicCamera() { return m_activeCinematicCameraId; }
|
||||
public void SetActiveCinematicCamera(uint cinematicCameraId = 0) { m_activeCinematicCameraId = cinematicCameraId; }
|
||||
public bool IsOnCinematic() { return (m_cinematicCamera != null); }
|
||||
|
||||
// Remote location information
|
||||
Player player;
|
||||
|
||||
public uint m_cinematicDiff;
|
||||
public uint m_lastCinematicCheck;
|
||||
public uint m_activeCinematicCameraId;
|
||||
public uint m_cinematicLength;
|
||||
List<FlyByCamera> m_cinematicCamera;
|
||||
Position m_remoteSightPosition;
|
||||
TempSummon m_CinematicObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,862 @@
|
||||
/*
|
||||
* 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.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class CollectionMgr
|
||||
{
|
||||
public static void LoadMountDefinitions()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
SQLResult result = DB.World.Query("SELECT spellId, otherFactionSpellId FROM mount_definitions");
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 mount definitions. DB table `mount_definitions` is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
uint spellId = result.Read<uint>(0);
|
||||
uint otherFactionSpellId = result.Read<uint>(1);
|
||||
|
||||
if (Global.DB2Mgr.GetMount(spellId) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Mount spell {0} defined in `mount_definitions` does not exist in Mount.db2, skipped", spellId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (otherFactionSpellId != 0 && Global.DB2Mgr.GetMount(otherFactionSpellId) == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "otherFactionSpellId {0} defined in `mount_definitions` for spell {1} does not exist in Mount.db2, skipped", otherFactionSpellId, spellId);
|
||||
continue;
|
||||
}
|
||||
|
||||
FactionSpecificMounts[spellId] = otherFactionSpellId;
|
||||
} while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} mount definitions in {1} ms", FactionSpecificMounts.Count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
public CollectionMgr(WorldSession owner)
|
||||
{
|
||||
_owner = owner;
|
||||
_appearances = new System.Collections.BitSet(0);
|
||||
}
|
||||
|
||||
public void LoadToys()
|
||||
{
|
||||
foreach (var value in _toys.Keys)
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Toys, value);
|
||||
}
|
||||
|
||||
public bool AddToy(uint itemId, bool isFavourite = false)
|
||||
{
|
||||
if (UpdateAccountToys(itemId, isFavourite))
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Toys, itemId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void LoadAccountToys(SQLResult result)
|
||||
{
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint itemId = result.Read<uint>(0);
|
||||
bool isFavourite = result.Read<bool>(1);
|
||||
|
||||
_toys[itemId] = isFavourite;
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
public void SaveAccountToys(SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
foreach (var pair in _toys)
|
||||
{
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_TOYS);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, pair.Key);
|
||||
stmt.AddValue(2, pair.Value);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateAccountToys(uint itemId, bool isFavourite = false)
|
||||
{
|
||||
if (_toys.ContainsKey(itemId))
|
||||
return false;
|
||||
|
||||
_toys.Add(itemId, isFavourite);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ToySetFavorite(uint itemId, bool favorite)
|
||||
{
|
||||
if (!_toys.ContainsKey(itemId))
|
||||
return;
|
||||
|
||||
_toys[itemId] = favorite;
|
||||
}
|
||||
|
||||
public void OnItemAdded(Item item)
|
||||
{
|
||||
if (Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry()) != null)
|
||||
AddHeirloom(item.GetEntry(), 0);
|
||||
|
||||
AddItemAppearance(item);
|
||||
}
|
||||
|
||||
public void LoadAccountHeirlooms(SQLResult result)
|
||||
{
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint itemId = result.Read<uint>(0);
|
||||
HeirloomPlayerFlags flags = (HeirloomPlayerFlags)result.Read<uint>(1);
|
||||
|
||||
HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(itemId);
|
||||
if (heirloom == null)
|
||||
continue;
|
||||
|
||||
uint bonusId = 0;
|
||||
|
||||
if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel110))
|
||||
bonusId = heirloom.ItemBonusListID[2];
|
||||
else if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel100))
|
||||
bonusId = heirloom.ItemBonusListID[1];
|
||||
else if (flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel90))
|
||||
bonusId = heirloom.ItemBonusListID[0];
|
||||
|
||||
_heirlooms[itemId] = new HeirloomData(flags, bonusId);
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
public void SaveAccountHeirlooms(SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt;
|
||||
foreach (var heirloom in _heirlooms)
|
||||
{
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_HEIRLOOMS);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, heirloom.Key);
|
||||
stmt.AddValue(2, heirloom.Value.flags);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateAccountHeirlooms(uint itemId, HeirloomPlayerFlags flags)
|
||||
{
|
||||
if (_heirlooms.ContainsKey(itemId))
|
||||
return false;
|
||||
|
||||
_heirlooms.Add(itemId, new HeirloomData(flags, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetHeirloomBonus(uint itemId)
|
||||
{
|
||||
var data = _heirlooms.LookupByKey(itemId);
|
||||
if (data != null)
|
||||
return data.bonusId;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void LoadHeirlooms()
|
||||
{
|
||||
foreach (var item in _heirlooms)
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Heirlooms, item.Key);
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.HeirloomsFlags, (uint)item.Value.flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHeirloom(uint itemId, HeirloomPlayerFlags flags)
|
||||
{
|
||||
if (UpdateAccountHeirlooms(itemId, flags))
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Heirlooms, itemId);
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.HeirloomsFlags, (uint)flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpgradeHeirloom(uint itemId, uint castItem)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(itemId);
|
||||
if (heirloom == null)
|
||||
return;
|
||||
|
||||
var data = _heirlooms.LookupByKey(itemId);
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
HeirloomPlayerFlags flags = data.flags;
|
||||
uint bonusId = 0;
|
||||
|
||||
if (heirloom.UpgradeItemID[0] == castItem)
|
||||
{
|
||||
flags |= HeirloomPlayerFlags.BonusLevel90;
|
||||
bonusId = heirloom.ItemBonusListID[0];
|
||||
}
|
||||
if (heirloom.UpgradeItemID[1] == castItem)
|
||||
{
|
||||
flags |= HeirloomPlayerFlags.BonusLevel100;
|
||||
bonusId = heirloom.ItemBonusListID[1];
|
||||
}
|
||||
if (heirloom.UpgradeItemID[2] == castItem)
|
||||
{
|
||||
flags |= HeirloomPlayerFlags.BonusLevel110;
|
||||
bonusId = heirloom.ItemBonusListID[2];
|
||||
}
|
||||
|
||||
foreach (Item item in player.GetItemListByEntry(itemId, true))
|
||||
item.AddBonuses(bonusId);
|
||||
|
||||
// Get heirloom offset to update only one part of dynamic field
|
||||
var fields = player.GetDynamicValues(PlayerDynamicFields.Heirlooms);
|
||||
ushort offset = (ushort)Array.IndexOf(fields, itemId);
|
||||
|
||||
player.SetDynamicValue(PlayerDynamicFields.HeirloomsFlags, offset, (uint)flags);
|
||||
data.flags = flags;
|
||||
data.bonusId = bonusId;
|
||||
}
|
||||
|
||||
public void CheckHeirloomUpgrades(Item item)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
// Check already owned heirloom for upgrade kits
|
||||
HeirloomRecord heirloom = Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry());
|
||||
if (heirloom != null)
|
||||
{
|
||||
var data = _heirlooms.LookupByKey(item.GetEntry());
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
// Check for heirloom pairs (normal - heroic, heroic - mythic)
|
||||
uint heirloomItemId = heirloom.NextDifficultyItemID;
|
||||
uint newItemId = 0;
|
||||
HeirloomRecord heirloomDiff;
|
||||
while ((heirloomDiff = Global.DB2Mgr.GetHeirloomByItemId(heirloomItemId)) != null)
|
||||
{
|
||||
if (player.GetItemByEntry(heirloomDiff.ItemID))
|
||||
newItemId = heirloomDiff.ItemID;
|
||||
|
||||
HeirloomRecord heirloomSub = Global.DB2Mgr.GetHeirloomByItemId(heirloomDiff.NextDifficultyItemID);
|
||||
if (heirloomSub != null)
|
||||
{
|
||||
heirloomItemId = heirloomSub.ItemID;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (newItemId != 0)
|
||||
{
|
||||
var heirloomFields = player.GetDynamicValues(PlayerDynamicFields.Heirlooms);
|
||||
ushort offset = (ushort)Array.IndexOf(heirloomFields, item.GetEntry());
|
||||
|
||||
player.SetDynamicValue(PlayerDynamicFields.Heirlooms, offset, newItemId);
|
||||
player.SetDynamicValue(PlayerDynamicFields.HeirloomsFlags, offset, 0);
|
||||
|
||||
_heirlooms.Remove(item.GetEntry());
|
||||
_heirlooms[newItemId] = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var fields = item.GetDynamicValues(ItemDynamicFields.BonusListIds);
|
||||
foreach (uint bonusId in fields)
|
||||
if (bonusId != data.bonusId)
|
||||
item.ClearDynamicValue(ItemDynamicFields.BonusListIds);
|
||||
|
||||
if (!fields.Contains(data.bonusId))
|
||||
item.AddBonuses(data.bonusId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanApplyHeirloomXpBonus(uint itemId, uint level)
|
||||
{
|
||||
if (Global.DB2Mgr.GetHeirloomByItemId(itemId) == null)
|
||||
return false;
|
||||
|
||||
var data = _heirlooms.LookupByKey(itemId);
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel110))
|
||||
return level <= 110;
|
||||
if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel100))
|
||||
return level <= 100;
|
||||
if (data.flags.HasAnyFlag(HeirloomPlayerFlags.BonusLevel90))
|
||||
return level <= 90;
|
||||
|
||||
return level <= 60;
|
||||
}
|
||||
|
||||
public void LoadMounts()
|
||||
{
|
||||
foreach (var m in _mounts.ToList())
|
||||
AddMount(m.Key, m.Value, false, false);
|
||||
}
|
||||
|
||||
public void LoadAccountMounts(SQLResult result)
|
||||
{
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint mountSpellId = result.Read<uint>(0);
|
||||
MountStatusFlags flags = (MountStatusFlags)result.Read<byte>(1);
|
||||
|
||||
if (Global.DB2Mgr.GetMount(mountSpellId) == null)
|
||||
continue;
|
||||
|
||||
_mounts[mountSpellId] = flags;
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
public void SaveAccountMounts(SQLTransaction trans)
|
||||
{
|
||||
foreach (var mount in _mounts)
|
||||
{
|
||||
PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.REP_ACCOUNT_MOUNTS);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, mount.Key);
|
||||
stmt.AddValue(2, mount.Value);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddMount(uint spellId, MountStatusFlags flags, bool factionMount = false, bool learned = false)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return false;
|
||||
|
||||
MountRecord mount = Global.DB2Mgr.GetMount(spellId);
|
||||
if (mount == null)
|
||||
return false;
|
||||
|
||||
var value = FactionSpecificMounts.LookupByKey(spellId);
|
||||
if (value != 0 && !factionMount)
|
||||
AddMount(value, flags, true, learned);
|
||||
|
||||
_mounts[spellId] = flags;
|
||||
|
||||
// Mount condition only applies to using it, should still learn it.
|
||||
if (mount.PlayerConditionId != 0)
|
||||
{
|
||||
PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(mount.PlayerConditionId);
|
||||
if (!ConditionManager.IsPlayerMeetingCondition(player, playerCondition))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!learned)
|
||||
{
|
||||
if (!factionMount)
|
||||
SendSingleMountUpdate(spellId, flags);
|
||||
if (!player.HasSpell(spellId))
|
||||
player.LearnSpell(spellId, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void MountSetFavorite(uint spellId, bool favorite)
|
||||
{
|
||||
if (!_mounts.ContainsKey(spellId))
|
||||
return;
|
||||
|
||||
if (favorite)
|
||||
_mounts[spellId] |= MountStatusFlags.IsFavorite;
|
||||
else
|
||||
_mounts[spellId] &= ~MountStatusFlags.IsFavorite;
|
||||
|
||||
SendSingleMountUpdate(spellId, _mounts[spellId]);
|
||||
}
|
||||
|
||||
void SendSingleMountUpdate(uint spellId, MountStatusFlags mountStatusFlags)
|
||||
{
|
||||
Player player = _owner.GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
AccountMountUpdate mountUpdate = new AccountMountUpdate();
|
||||
mountUpdate.IsFullUpdate = false;
|
||||
mountUpdate.Mounts.Add(spellId, mountStatusFlags);
|
||||
player.SendPacket(mountUpdate);
|
||||
}
|
||||
|
||||
public void LoadItemAppearances()
|
||||
{
|
||||
foreach (uint blockValue in _appearances.ToBlockRange())
|
||||
{
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Transmog, blockValue);
|
||||
}
|
||||
|
||||
foreach (var value in _temporaryAppearances.Keys)
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.ConditionalTransmog, value);
|
||||
}
|
||||
|
||||
public void LoadAccountItemAppearances(SQLResult knownAppearances, SQLResult favoriteAppearances)
|
||||
{
|
||||
if (!knownAppearances.IsEmpty())
|
||||
{
|
||||
uint[] blocks = new uint[1];
|
||||
do
|
||||
{
|
||||
ushort blobIndex = knownAppearances.Read<ushort>(0);
|
||||
if (blobIndex >= blocks.Length)
|
||||
Array.Resize(ref blocks, blobIndex + 1);
|
||||
|
||||
blocks[blobIndex] = knownAppearances.Read<uint>(1);
|
||||
|
||||
} while (knownAppearances.NextRow());
|
||||
|
||||
_appearances = new System.Collections.BitSet(blocks);
|
||||
}
|
||||
|
||||
if (!favoriteAppearances.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
_favoriteAppearances[favoriteAppearances.Read<uint>(0)] = FavoriteAppearanceState.Unchanged;
|
||||
} while (favoriteAppearances.NextRow());
|
||||
}
|
||||
|
||||
// Static item appearances known by every player
|
||||
uint[] hiddenAppearanceItems =
|
||||
{
|
||||
134110, // Hidden Helm
|
||||
134111, // Hidden Cloak
|
||||
134112, // Hidden Shoulder
|
||||
142503, // Hidden Shirt
|
||||
142504, // Hidden Tabard
|
||||
143539 // Hidden Belt
|
||||
};
|
||||
|
||||
foreach (uint hiddenItem in hiddenAppearanceItems)
|
||||
{
|
||||
ItemModifiedAppearanceRecord hiddenAppearance = Global.DB2Mgr.GetItemModifiedAppearance(hiddenItem, 0);
|
||||
//ASSERT(hiddenAppearance);
|
||||
if (_appearances.Length <= hiddenAppearance.Id)
|
||||
_appearances.Length = (int)hiddenAppearance.Id + 1;
|
||||
|
||||
_appearances.Set((int)hiddenAppearance.Id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveAccountItemAppearances(SQLTransaction trans)
|
||||
{
|
||||
PreparedStatement stmt;
|
||||
ushort blockIndex = 0;
|
||||
foreach (uint blockValue in _appearances.ToBlockRange())
|
||||
{
|
||||
if (blockValue != 0) // this table is only appended/bits are set (never cleared) so don't save empty blocks
|
||||
{
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, blockIndex);
|
||||
stmt.AddValue(2, blockValue);
|
||||
trans.Append(stmt);
|
||||
}
|
||||
|
||||
++blockIndex;
|
||||
}
|
||||
|
||||
foreach (var key in _favoriteAppearances.Keys)
|
||||
{
|
||||
var appearanceState = _favoriteAppearances[key];
|
||||
switch (appearanceState)
|
||||
{
|
||||
case FavoriteAppearanceState.New:
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, key);
|
||||
trans.Append(stmt);
|
||||
appearanceState = FavoriteAppearanceState.Unchanged;
|
||||
break;
|
||||
case FavoriteAppearanceState.Removed:
|
||||
stmt = DB.Login.GetPreparedStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE);
|
||||
stmt.AddValue(0, _owner.GetBattlenetAccountId());
|
||||
stmt.AddValue(1, key);
|
||||
trans.Append(stmt);
|
||||
_favoriteAppearances.Remove(key);
|
||||
break;
|
||||
case FavoriteAppearanceState.Unchanged:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint[] PlayerClassByArmorSubclass =
|
||||
{
|
||||
(int)Class.ClassMaskAllPlayable, //ITEM_SUBCLASS_ARMOR_MISCELLANEOUS
|
||||
(1 << ((int)Class.Priest - 1)) | (1 << ((int)Class.Mage - 1)) | (1 << ((int)Class.Warlock - 1)), //ITEM_SUBCLASS_ARMOR_CLOTH
|
||||
(1 << ((int)Class.Rogue - 1)) | (1 << ((int)Class.Monk - 1)) | (1 << ((int)Class.Druid - 1)) | (1 << ((int)Class.DemonHunter - 1)), //ITEM_SUBCLASS_ARMOR_LEATHER
|
||||
(1 << ((int)Class.Hunter - 1)) | (1 << ((int)Class.Shaman - 1)), //ITEM_SUBCLASS_ARMOR_MAIL
|
||||
(1 << ((int)Class.Warrior - 1)) | (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Deathknight - 1)), //ITEM_SUBCLASS_ARMOR_PLATE
|
||||
(int)Class.ClassMaskAllPlayable, //ITEM_SUBCLASS_ARMOR_BUCKLER
|
||||
(1 << ((int)Class.Warrior - 1)) | (1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Shaman - 1)), //ITEM_SUBCLASS_ARMOR_SHIELD
|
||||
1 << ((int)Class.Paladin - 1), //ITEM_SUBCLASS_ARMOR_LIBRAM
|
||||
1 << ((int)Class.Druid - 1), //ITEM_SUBCLASS_ARMOR_IDOL
|
||||
1 << ((int)Class.Shaman - 1), //ITEM_SUBCLASS_ARMOR_TOTEM
|
||||
1 << ((int)Class.Deathknight - 1), //ITEM_SUBCLASS_ARMOR_SIGIL
|
||||
(1 << ((int)Class.Paladin - 1)) | (1 << ((int)Class.Deathknight - 1)) | (1 << ((int)Class.Shaman - 1)) | (1 << ((int)Class.Druid - 1)), //ITEM_SUBCLASS_ARMOR_RELIC
|
||||
};
|
||||
|
||||
public void AddItemAppearance(Item item)
|
||||
{
|
||||
if (!item.IsSoulBound())
|
||||
return;
|
||||
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = item.GetItemModifiedAppearance();
|
||||
if (!CanAddAppearance(itemModifiedAppearance))
|
||||
return;
|
||||
|
||||
if (Convert.ToBoolean(item.GetUInt32Value(ItemFields.Flags) & (uint)(ItemFieldFlags.BopTradeable | ItemFieldFlags.Refundable)))
|
||||
{
|
||||
AddTemporaryAppearance(item.GetGUID(), itemModifiedAppearance);
|
||||
return;
|
||||
}
|
||||
|
||||
AddItemAppearance(itemModifiedAppearance);
|
||||
}
|
||||
|
||||
public void AddItemAppearance(uint itemId, uint appearanceModId = 0)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = Global.DB2Mgr.GetItemModifiedAppearance(itemId, appearanceModId);
|
||||
if (!CanAddAppearance(itemModifiedAppearance))
|
||||
return;
|
||||
|
||||
AddItemAppearance(itemModifiedAppearance);
|
||||
}
|
||||
|
||||
bool CanAddAppearance(ItemModifiedAppearanceRecord itemModifiedAppearance)
|
||||
{
|
||||
if (itemModifiedAppearance == null)
|
||||
return false;
|
||||
|
||||
if (itemModifiedAppearance.SourceType == 6 || itemModifiedAppearance.SourceType == 9)
|
||||
return false;
|
||||
|
||||
if (!CliDB.ItemSearchNameStorage.ContainsKey(itemModifiedAppearance.ItemID))
|
||||
return false;
|
||||
|
||||
ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemModifiedAppearance.ItemID);
|
||||
if (itemTemplate == null)
|
||||
return false;
|
||||
|
||||
if (_owner.GetPlayer().CanUseItem(itemTemplate) != InventoryResult.Ok)
|
||||
return false;
|
||||
|
||||
if (itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.NoSourceForItemVisual) || itemTemplate.GetQuality() == ItemQuality.Artifact)
|
||||
return false;
|
||||
|
||||
switch (itemTemplate.GetClass())
|
||||
{
|
||||
case ItemClass.Weapon:
|
||||
{
|
||||
if (!Convert.ToBoolean(_owner.GetPlayer().GetWeaponProficiency() & (1 << (int)itemTemplate.GetSubClass())))
|
||||
return false;
|
||||
if (itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Exotic ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Exotic2 ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Miscellaneous ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Thrown ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.Spear ||
|
||||
itemTemplate.GetSubClass() == (int)ItemSubClassWeapon.FishingPole)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case ItemClass.Armor:
|
||||
{
|
||||
switch (itemTemplate.GetInventoryType())
|
||||
{
|
||||
case InventoryType.Body:
|
||||
case InventoryType.Shield:
|
||||
case InventoryType.Cloak:
|
||||
case InventoryType.Tabard:
|
||||
case InventoryType.Holdable:
|
||||
break;
|
||||
case InventoryType.Head:
|
||||
case InventoryType.Shoulders:
|
||||
case InventoryType.Chest:
|
||||
case InventoryType.Waist:
|
||||
case InventoryType.Legs:
|
||||
case InventoryType.Feet:
|
||||
case InventoryType.Wrists:
|
||||
case InventoryType.Hands:
|
||||
case InventoryType.Robe:
|
||||
if ((ItemSubClassArmor)itemTemplate.GetSubClass() == ItemSubClassArmor.Miscellaneous)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (itemTemplate.GetInventoryType() != InventoryType.Cloak)
|
||||
if (!Convert.ToBoolean(PlayerClassByArmorSubclass[itemTemplate.GetSubClass()] & _owner.GetPlayer().getClassMask()))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemTemplate.GetQuality() < ItemQuality.Uncommon)
|
||||
if (!itemTemplate.GetFlags2().HasAnyFlag(ItemFlags2.IgnoreQualityForItemVisualSource) || !itemTemplate.GetFlags3().HasAnyFlag(ItemFlags3.ActsAsTransmogHiddenVisualOption))
|
||||
return false;
|
||||
|
||||
if (itemModifiedAppearance.Id < _appearances.Count && _appearances.Get((int)itemModifiedAppearance.Id))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
//todo check this
|
||||
void AddItemAppearance(ItemModifiedAppearanceRecord itemModifiedAppearance)
|
||||
{
|
||||
if (_appearances.Count <= itemModifiedAppearance.Id)
|
||||
{
|
||||
uint numBlocks = (uint)(_appearances.Count << 2);
|
||||
_appearances.Length = (int)itemModifiedAppearance.Id + 1;
|
||||
numBlocks = (uint)(_appearances.Count << 2) - numBlocks;
|
||||
while (numBlocks-- != 0)
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.Transmog, 0);
|
||||
}
|
||||
|
||||
_appearances.Set((int)itemModifiedAppearance.Id, true);
|
||||
uint blockIndex = itemModifiedAppearance.Id / 32;
|
||||
uint bitIndex = itemModifiedAppearance.Id % 32;
|
||||
uint currentMask = _owner.GetPlayer().GetDynamicValue(PlayerDynamicFields.Transmog, (ushort)blockIndex);
|
||||
_owner.GetPlayer().SetDynamicValue(PlayerDynamicFields.Transmog, (ushort)blockIndex, (uint)((int)currentMask | (1 << (int)bitIndex)));
|
||||
var temporaryAppearance = _temporaryAppearances.LookupByKey(itemModifiedAppearance.Id);
|
||||
if (!temporaryAppearance.Empty())
|
||||
{
|
||||
_owner.GetPlayer().RemoveDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id);
|
||||
_temporaryAppearances.Remove(itemModifiedAppearance.Id);
|
||||
}
|
||||
|
||||
ItemRecord item = CliDB.ItemStorage.LookupByKey(itemModifiedAppearance.ItemID);
|
||||
if (item != null)
|
||||
{
|
||||
int transmogSlot = Item.ItemTransmogrificationSlots[(int)item.inventoryType];
|
||||
if (transmogSlot >= 0)
|
||||
_owner.GetPlayer().UpdateCriteria(CriteriaTypes.AppearanceUnlockedBySlot, (ulong)transmogSlot, 1);
|
||||
}
|
||||
|
||||
var sets = Global.DB2Mgr.GetTransmogSetsForItemModifiedAppearance(itemModifiedAppearance.Id);
|
||||
foreach (TransmogSetRecord set in sets)
|
||||
if (IsSetCompleted(set.Id))
|
||||
_owner.GetPlayer().UpdateCriteria(CriteriaTypes.TransmogSetUnlocked, set.TransmogSetGroupID);
|
||||
}
|
||||
|
||||
void AddTemporaryAppearance(ObjectGuid itemGuid, ItemModifiedAppearanceRecord itemModifiedAppearance)
|
||||
{
|
||||
var itemsWithAppearance = _temporaryAppearances[itemModifiedAppearance.Id];
|
||||
if (itemsWithAppearance.Empty())
|
||||
_owner.GetPlayer().AddDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id);
|
||||
|
||||
itemsWithAppearance.Add(itemGuid);
|
||||
}
|
||||
|
||||
public void RemoveTemporaryAppearance(Item item)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = item.GetItemModifiedAppearance();
|
||||
if (itemModifiedAppearance == null)
|
||||
return;
|
||||
|
||||
var guid = _temporaryAppearances.LookupByKey(itemModifiedAppearance.Id);
|
||||
if (guid.Empty())
|
||||
return;
|
||||
|
||||
guid.Remove(item.GetGUID());
|
||||
if (guid.Empty())
|
||||
{
|
||||
_owner.GetPlayer().RemoveDynamicValue(PlayerDynamicFields.ConditionalTransmog, itemModifiedAppearance.Id);
|
||||
_temporaryAppearances.Remove(itemModifiedAppearance.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public Tuple<bool, bool> HasItemAppearance(uint itemModifiedAppearanceId)
|
||||
{
|
||||
if (itemModifiedAppearanceId < _appearances.Count && _appearances.Get((int)itemModifiedAppearanceId))
|
||||
return Tuple.Create(true, false);
|
||||
|
||||
if (_temporaryAppearances.ContainsKey(itemModifiedAppearanceId))
|
||||
return Tuple.Create(true, true);
|
||||
|
||||
return Tuple.Create(false, false);
|
||||
}
|
||||
|
||||
public List<ObjectGuid> GetItemsProvidingTemporaryAppearance(uint itemModifiedAppearanceId)
|
||||
{
|
||||
return _temporaryAppearances.LookupByKey(itemModifiedAppearanceId);
|
||||
}
|
||||
|
||||
public void SetAppearanceIsFavorite(uint itemModifiedAppearanceId, bool apply)
|
||||
{
|
||||
var apperanceState = _favoriteAppearances.LookupByKey(itemModifiedAppearanceId);
|
||||
if (apply)
|
||||
{
|
||||
if (!_favoriteAppearances.ContainsKey(itemModifiedAppearanceId))
|
||||
_favoriteAppearances[itemModifiedAppearanceId] = FavoriteAppearanceState.New;
|
||||
else if (apperanceState == FavoriteAppearanceState.Removed)
|
||||
apperanceState = FavoriteAppearanceState.Unchanged;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if (_favoriteAppearances.ContainsKey(itemModifiedAppearanceId))
|
||||
{
|
||||
if (apperanceState == FavoriteAppearanceState.New)
|
||||
_favoriteAppearances.Remove(itemModifiedAppearanceId);
|
||||
else
|
||||
apperanceState = FavoriteAppearanceState.Removed;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
_favoriteAppearances[itemModifiedAppearanceId] = apperanceState;
|
||||
|
||||
TransmogCollectionUpdate transmogCollectionUpdate = new TransmogCollectionUpdate();
|
||||
transmogCollectionUpdate.IsFullUpdate = false;
|
||||
transmogCollectionUpdate.IsSetFavorite = apply;
|
||||
transmogCollectionUpdate.FavoriteAppearances.Add(itemModifiedAppearanceId);
|
||||
|
||||
_owner.SendPacket(transmogCollectionUpdate);
|
||||
}
|
||||
|
||||
public void SendFavoriteAppearances()
|
||||
{
|
||||
TransmogCollectionUpdate transmogCollectionUpdate = new TransmogCollectionUpdate();
|
||||
transmogCollectionUpdate.IsFullUpdate = true;
|
||||
foreach (var pair in _favoriteAppearances)
|
||||
if (pair.Value != FavoriteAppearanceState.Removed)
|
||||
transmogCollectionUpdate.FavoriteAppearances.Add(pair.Key);
|
||||
|
||||
_owner.SendPacket(transmogCollectionUpdate);
|
||||
}
|
||||
|
||||
public void AddTransmogSet(uint transmogSetId)
|
||||
{
|
||||
var items = Global.DB2Mgr.GetTransmogSetItems(transmogSetId);
|
||||
if (items.Empty())
|
||||
return;
|
||||
|
||||
foreach (TransmogSetItemRecord item in items)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = CliDB.ItemModifiedAppearanceStorage.LookupByKey(item.ItemModifiedAppearanceID);
|
||||
if (itemModifiedAppearance == null)
|
||||
continue;
|
||||
|
||||
AddItemAppearance(itemModifiedAppearance);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSetCompleted(uint transmogSetId)
|
||||
{
|
||||
var transmogSetItems = Global.DB2Mgr.GetTransmogSetItems(transmogSetId);
|
||||
if (transmogSetItems.Empty())
|
||||
return false;
|
||||
|
||||
int[] knownPieces = new int[EquipmentSlot.End];
|
||||
for (var i = 0; i < EquipmentSlot.End; ++i)
|
||||
knownPieces[i] = -1;
|
||||
|
||||
foreach (TransmogSetItemRecord transmogSetItem in transmogSetItems)
|
||||
{
|
||||
ItemModifiedAppearanceRecord itemModifiedAppearance = CliDB.ItemModifiedAppearanceStorage.LookupByKey(transmogSetItem.ItemModifiedAppearanceID);
|
||||
if (itemModifiedAppearance == null)
|
||||
continue;
|
||||
|
||||
ItemRecord item = CliDB.ItemStorage.LookupByKey(itemModifiedAppearance.ItemID);
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
int transmogSlot = Item.ItemTransmogrificationSlots[(int)item.inventoryType];
|
||||
if (transmogSlot < 0 || knownPieces[transmogSlot] == 1)
|
||||
continue;
|
||||
|
||||
(var hasAppearance, var isTemporary) = HasItemAppearance(transmogSetItem.ItemModifiedAppearanceID);
|
||||
|
||||
knownPieces[transmogSlot] = (hasAppearance && !isTemporary) ? 1 : 0;
|
||||
}
|
||||
|
||||
return !knownPieces.Contains(0);
|
||||
}
|
||||
|
||||
public bool HasToy(uint itemId) { return _toys.ContainsKey(itemId); }
|
||||
public Dictionary<uint, bool> GetAccountToys() { return _toys; }
|
||||
public Dictionary<uint, HeirloomData> GetAccountHeirlooms() { return _heirlooms; }
|
||||
public Dictionary<uint, MountStatusFlags> GetAccountMounts() { return _mounts; }
|
||||
|
||||
WorldSession _owner;
|
||||
|
||||
Dictionary<uint, bool> _toys = new Dictionary<uint, bool>();
|
||||
Dictionary<uint, HeirloomData> _heirlooms = new Dictionary<uint, HeirloomData>();
|
||||
Dictionary<uint, MountStatusFlags> _mounts = new Dictionary<uint, MountStatusFlags>();
|
||||
System.Collections.BitSet _appearances;
|
||||
MultiMap<uint, ObjectGuid> _temporaryAppearances = new MultiMap<uint, ObjectGuid>();
|
||||
Dictionary<uint, FavoriteAppearanceState> _favoriteAppearances = new Dictionary<uint, FavoriteAppearanceState>();
|
||||
|
||||
static Dictionary<uint, uint> FactionSpecificMounts = new Dictionary<uint, uint>();
|
||||
}
|
||||
|
||||
enum FavoriteAppearanceState
|
||||
{
|
||||
New,
|
||||
Removed,
|
||||
Unchanged
|
||||
}
|
||||
|
||||
public class HeirloomData
|
||||
{
|
||||
public HeirloomData(HeirloomPlayerFlags _flags = 0, uint _bonusId = 0)
|
||||
{
|
||||
flags = _flags;
|
||||
bonusId = _bonusId;
|
||||
}
|
||||
|
||||
public HeirloomPlayerFlags flags;
|
||||
public uint bonusId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Maps;
|
||||
using Game.Scenarios;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class KillRewarder
|
||||
{
|
||||
public KillRewarder(Player killer, Unit victim, bool isBattleground)
|
||||
{
|
||||
_killer = killer;
|
||||
_victim = victim;
|
||||
_group = killer.GetGroup();
|
||||
_groupRate = 1.0f;
|
||||
_maxNotGrayMember = null;
|
||||
_count = 0;
|
||||
_sumLevel = 0;
|
||||
_xp = 0;
|
||||
_isFullXP = false;
|
||||
_maxLevel = 0;
|
||||
_isBattleground = isBattleground;
|
||||
_isPvP = false;
|
||||
|
||||
// mark the credit as pvp if victim is player
|
||||
if (victim.IsTypeId(TypeId.Player))
|
||||
_isPvP = true;
|
||||
// or if its owned by player and its not a vehicle
|
||||
else if (victim.GetCharmerOrOwnerGUID().IsPlayer())
|
||||
_isPvP = !victim.IsVehicle();
|
||||
|
||||
_InitGroupData();
|
||||
}
|
||||
|
||||
public void Reward()
|
||||
{
|
||||
// 3. Reward killer (and group, if necessary).
|
||||
if (_group)
|
||||
// 3.1. If killer is in group, reward group.
|
||||
_RewardGroup();
|
||||
else
|
||||
{
|
||||
// 3.2. Reward single killer (not group case).
|
||||
// 3.2.1. Initialize initial XP amount based on killer's level.
|
||||
_InitXP(_killer);
|
||||
// To avoid unnecessary calculations and calls,
|
||||
// proceed only if XP is not ZERO or player is not on Battleground
|
||||
// (Battlegroundrewards only XP, that's why).
|
||||
if (!_isBattleground || _xp != 0)
|
||||
// 3.2.2. Reward killer.
|
||||
_RewardPlayer(_killer, false);
|
||||
}
|
||||
|
||||
// 5. Credit instance encounter.
|
||||
// 6. Update guild achievements.
|
||||
// 7. Credit scenario criterias
|
||||
Creature victim = _victim.ToCreature();
|
||||
if (victim != null)
|
||||
{
|
||||
if (victim.IsDungeonBoss())
|
||||
{
|
||||
InstanceScript instance = _victim.GetInstanceScript();
|
||||
if (instance != null)
|
||||
instance.UpdateEncounterStateForKilledCreature(_victim.GetEntry(), _victim);
|
||||
}
|
||||
|
||||
uint guildId = victim.GetMap().GetOwnerGuildId();
|
||||
var guild = Global.GuildMgr.GetGuildById(guildId);
|
||||
if (guild != null)
|
||||
guild.UpdateCriteria(CriteriaTypes.KillCreature, victim.GetEntry(), 1, 0, victim, _killer);
|
||||
|
||||
Scenario scenario = victim.GetScenario();
|
||||
if (scenario != null)
|
||||
scenario.UpdateCriteria(CriteriaTypes.KillCreature, victim.GetEntry(), 1, 0, victim, _killer);
|
||||
}
|
||||
}
|
||||
|
||||
void _InitGroupData()
|
||||
{
|
||||
if (_group)
|
||||
{
|
||||
// 2. In case when player is in group, initialize variables necessary for group calculations:
|
||||
for (GroupReference refe = _group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player member = refe.GetSource();
|
||||
if (member)
|
||||
{
|
||||
if (member.IsAlive() && member.IsAtGroupRewardDistance(_victim))
|
||||
{
|
||||
uint lvl = member.getLevel();
|
||||
// 2.1. _count - number of alive group members within reward distance;
|
||||
++_count;
|
||||
// 2.2. _sumLevel - sum of levels of alive group members within reward distance;
|
||||
_sumLevel += lvl;
|
||||
// 2.3. _maxLevel - maximum level of alive group member within reward distance;
|
||||
if (_maxLevel < lvl)
|
||||
_maxLevel = (byte)lvl;
|
||||
// 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
|
||||
// for whom victim is not gray;
|
||||
uint grayLevel = Formulas.GetGrayLevel(lvl);
|
||||
if (_victim.GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl))
|
||||
_maxNotGrayMember = member;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
|
||||
// so 100% XP will be rewarded (50% otherwise).
|
||||
_isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember.getLevel());
|
||||
}
|
||||
else
|
||||
_count = 1;
|
||||
}
|
||||
|
||||
void _InitXP(Player player)
|
||||
{
|
||||
// Get initial value of XP for kill.
|
||||
// XP is given:
|
||||
// * on Battlegrounds;
|
||||
// * otherwise, not in PvP;
|
||||
// * not if killer is on vehicle.
|
||||
if (_isBattleground || (!_isPvP && _killer.GetVehicle() == null))
|
||||
_xp = Formulas.XPGain(player, _victim, _isBattleground);
|
||||
}
|
||||
|
||||
void _RewardHonor(Player player)
|
||||
{
|
||||
// Rewarded player must be alive.
|
||||
if (player.IsAlive())
|
||||
player.RewardHonor(_victim, _count, -1, true);
|
||||
}
|
||||
|
||||
void _RewardXP(Player player, float rate)
|
||||
{
|
||||
uint xp = _xp;
|
||||
if (_group)
|
||||
{
|
||||
// 4.2.1. If player is in group, adjust XP:
|
||||
// * set to 0 if player's level is more than maximum level of not gray member;
|
||||
// * cut XP in half if _isFullXP is false.
|
||||
if (_maxNotGrayMember != null && player.IsAlive() &&
|
||||
_maxNotGrayMember.getLevel() >= player.getLevel())
|
||||
xp = _isFullXP ?
|
||||
(uint)(xp * rate) : // Reward FULL XP if all group members are not gray.
|
||||
(uint)(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray.
|
||||
else
|
||||
xp = 0;
|
||||
}
|
||||
if (xp != 0)
|
||||
{
|
||||
// 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT and SPELL_AURA_MOD_XP_FROM_CREATURE_TYPE).
|
||||
xp = (uint)(xp * player.GetTotalAuraMultiplier(AuraType.ModXpPct));
|
||||
xp = (uint)(xp * player.GetTotalAuraMultiplierByMiscValue(AuraType.ModXpFromCreatureType, (int)_victim.GetCreatureType()));
|
||||
|
||||
// 4.2.3. Give XP to player.
|
||||
player.GiveXP(xp, _victim, _groupRate);
|
||||
Pet pet = player.GetPet();
|
||||
if (pet)
|
||||
// 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case).
|
||||
pet.GivePetXP(_group ? xp / 2 : xp);
|
||||
}
|
||||
}
|
||||
|
||||
void _RewardReputation(Player player, float rate)
|
||||
{
|
||||
// 4.3. Give reputation (player must not be on BG).
|
||||
// Even dead players and corpses are rewarded.
|
||||
player.RewardReputation(_victim, rate);
|
||||
}
|
||||
|
||||
void _RewardKillCredit(Player player)
|
||||
{
|
||||
// 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse).
|
||||
if (!_group || player.IsAlive() || player.GetCorpse() == null)
|
||||
{
|
||||
Creature target = _victim.ToCreature();
|
||||
if (target != null)
|
||||
{
|
||||
player.KilledMonster(target.GetCreatureTemplate(), target.GetGUID());
|
||||
player.UpdateCriteria(CriteriaTypes.KillCreatureType, (ulong)target.GetCreatureType(), 1, 0, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _RewardPlayer(Player player, bool isDungeon)
|
||||
{
|
||||
// 4. Reward player.
|
||||
if (!_isBattleground)
|
||||
{
|
||||
// 4.1. Give honor (player must be alive and not on BG).
|
||||
_RewardHonor(player);
|
||||
// 4.1.1 Send player killcredit for quests with PlayerSlain
|
||||
if (_victim.IsTypeId(TypeId.Player))
|
||||
player.KilledPlayerCredit();
|
||||
}
|
||||
// Give XP only in PvE or in Battlegrounds.
|
||||
// Give reputation and kill credit only in PvE.
|
||||
if (!_isPvP || _isBattleground)
|
||||
{
|
||||
float rate = _group ? _groupRate * player.getLevel() / _sumLevel : 1.0f;
|
||||
if (_xp != 0)
|
||||
// 4.2. Give XP.
|
||||
_RewardXP(player, rate);
|
||||
if (!_isBattleground)
|
||||
{
|
||||
// If killer is in dungeon then all members receive full reputation at kill.
|
||||
_RewardReputation(player, isDungeon ? 1.0f : rate);
|
||||
_RewardKillCredit(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _RewardGroup()
|
||||
{
|
||||
if (_maxLevel != 0)
|
||||
{
|
||||
if (_maxNotGrayMember != null)
|
||||
// 3.1.1. Initialize initial XP amount based on maximum level of group member,
|
||||
// for whom victim is not gray.
|
||||
_InitXP(_maxNotGrayMember);
|
||||
// To avoid unnecessary calculations and calls,
|
||||
// proceed only if XP is not ZERO or player is not on Battleground
|
||||
// (Battlegroundrewards only XP, that's why).
|
||||
if (!_isBattleground || _xp != 0)
|
||||
{
|
||||
bool isDungeon = !_isPvP && CliDB.MapStorage.LookupByKey(_killer.GetMapId()).IsDungeon();
|
||||
if (!_isBattleground)
|
||||
{
|
||||
// 3.1.2. Alter group rate if group is in raid (not for Battlegrounds).
|
||||
bool isRaid = !_isPvP && CliDB.MapStorage.LookupByKey(_killer.GetMapId()).IsRaid() && _group.isRaidGroup();
|
||||
_groupRate = Formulas.XPInGroupRate(_count, isRaid);
|
||||
}
|
||||
// 3.1.3. Reward each group member (even dead or corpse) within reward distance.
|
||||
for (GroupReference refe = _group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player member = refe.GetSource();
|
||||
if (member)
|
||||
{
|
||||
if (member.IsAtGroupRewardDistance(_victim))
|
||||
{
|
||||
_RewardPlayer(member, isDungeon);
|
||||
member.UpdateCriteria(CriteriaTypes.SpecialPvpKill, 1, 0, 0, _victim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Player _killer;
|
||||
Unit _victim;
|
||||
Group _group;
|
||||
float _groupRate;
|
||||
Player _maxNotGrayMember;
|
||||
uint _count;
|
||||
uint _sumLevel;
|
||||
uint _xp;
|
||||
bool _isFullXP;
|
||||
byte _maxLevel;
|
||||
bool _isBattleground;
|
||||
bool _isPvP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.Achievements;
|
||||
using Game.DataStorage;
|
||||
using Game.Guilds;
|
||||
using Game.Scenarios;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public void ResetAchievements()
|
||||
{
|
||||
m_achievementSys.Reset();
|
||||
}
|
||||
|
||||
public void SendRespondInspectAchievements(Player player)
|
||||
{
|
||||
m_achievementSys.SendAchievementInfo(player);
|
||||
}
|
||||
|
||||
public uint GetAchievementPoints()
|
||||
{
|
||||
return m_achievementSys.GetAchievementPoints();
|
||||
}
|
||||
public bool HasAchieved(uint achievementId)
|
||||
{
|
||||
return m_achievementSys.HasAchieved(achievementId);
|
||||
}
|
||||
public void StartCriteriaTimer(CriteriaTimedTypes type, uint entry, uint timeLost = 0)
|
||||
{
|
||||
m_achievementSys.StartCriteriaTimer(type, entry, timeLost);
|
||||
}
|
||||
|
||||
public void RemoveCriteriaTimer(CriteriaTimedTypes type, uint entry)
|
||||
{
|
||||
m_achievementSys.RemoveCriteriaTimer(type, entry);
|
||||
}
|
||||
|
||||
public void ResetCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, bool evenIfCriteriaComplete = false)
|
||||
{
|
||||
m_achievementSys.ResetCriteria(type, miscValue1, miscValue2, evenIfCriteriaComplete);
|
||||
m_questObjectiveCriteriaMgr.ResetCriteria(type, miscValue1, miscValue2, evenIfCriteriaComplete);
|
||||
}
|
||||
|
||||
public void UpdateCriteria(CriteriaTypes type, ulong miscValue1 = 0, ulong miscValue2 = 0, ulong miscValue3 = 0, Unit unit = null)
|
||||
{
|
||||
m_achievementSys.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
m_questObjectiveCriteriaMgr.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
|
||||
// Update only individual achievement criteria here, otherwise we may get multiple updates
|
||||
// from a single boss kill
|
||||
if (CriteriaManager.IsGroupCriteriaType(type))
|
||||
return;
|
||||
|
||||
Scenario scenario = GetScenario();
|
||||
if (scenario != null)
|
||||
scenario.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
|
||||
Guild guild = Global.GuildMgr.GetGuildById(GetGuildId());
|
||||
if (guild)
|
||||
guild.UpdateCriteria(type, miscValue1, miscValue2, miscValue3, unit, this);
|
||||
}
|
||||
|
||||
public void CompletedAchievement(AchievementRecord entry)
|
||||
{
|
||||
m_achievementSys.CompletedAchievement(entry, this);
|
||||
}
|
||||
|
||||
public bool ModifierTreeSatisfied(uint modifierTreeId)
|
||||
{
|
||||
return m_achievementSys.ModifierTreeSatisfied(modifierTreeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
* 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.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
void SetRegularAttackTime()
|
||||
{
|
||||
for (WeaponAttackType weaponAttackType = 0; weaponAttackType < WeaponAttackType.Max; ++weaponAttackType)
|
||||
{
|
||||
Item tmpitem = GetWeaponForAttack(weaponAttackType, true);
|
||||
if (tmpitem != null && !tmpitem.IsBroken())
|
||||
{
|
||||
ItemTemplate proto = tmpitem.GetTemplate();
|
||||
if (proto.GetDelay() != 0)
|
||||
SetBaseAttackTime(weaponAttackType, proto.GetDelay());
|
||||
}
|
||||
else
|
||||
SetBaseAttackTime(weaponAttackType, SharedConst.BaseAttackTime); // If there is no weapon reset attack time to base (might have been changed from forms)
|
||||
}
|
||||
}
|
||||
|
||||
public void RewardPlayerAndGroupAtKill(Unit victim, bool isBattleground)
|
||||
{
|
||||
new KillRewarder(this, victim, isBattleground).Reward();
|
||||
}
|
||||
|
||||
public void RewardPlayerAndGroupAtEvent(uint creature_id, WorldObject pRewardSource)
|
||||
{
|
||||
if (pRewardSource == null)
|
||||
return;
|
||||
ObjectGuid creature_guid = pRewardSource.IsTypeId(TypeId.Unit) ? pRewardSource.GetGUID() : ObjectGuid.Empty;
|
||||
|
||||
// prepare data for near group iteration
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player player = refe.GetSource();
|
||||
if (!player)
|
||||
continue;
|
||||
|
||||
if (!player.IsAtGroupRewardDistance(pRewardSource))
|
||||
continue; // member (alive or dead) or his corpse at req. distance
|
||||
|
||||
// quest objectives updated only for alive group member or dead but with not released body
|
||||
if (player.IsAlive() || !player.GetCorpse())
|
||||
player.KilledMonsterCredit(creature_id, creature_guid);
|
||||
}
|
||||
}
|
||||
else
|
||||
KilledMonsterCredit(creature_id, creature_guid);
|
||||
}
|
||||
|
||||
public void AddWeaponProficiency(uint newflag) { m_WeaponProficiency |= newflag; }
|
||||
public void AddArmorProficiency(uint newflag) { m_ArmorProficiency |= newflag; }
|
||||
public uint GetWeaponProficiency() { return m_WeaponProficiency; }
|
||||
public uint GetArmorProficiency() { return m_ArmorProficiency; }
|
||||
public void SendProficiency(ItemClass itemClass, uint itemSubclassMask)
|
||||
{
|
||||
SetProficiency packet = new SetProficiency();
|
||||
packet.ProficiencyMask = itemSubclassMask;
|
||||
packet.ProficiencyClass = (byte)itemClass;
|
||||
SendPacket(packet);
|
||||
}
|
||||
|
||||
bool CanTitanGrip() { return m_canTitanGrip; }
|
||||
|
||||
public override bool CanUseAttackType(WeaponAttackType attacktype)
|
||||
{
|
||||
switch (attacktype)
|
||||
{
|
||||
case WeaponAttackType.BaseAttack:
|
||||
return !HasFlag(UnitFields.Flags, UnitFlags.Disarmed);
|
||||
case WeaponAttackType.OffAttack:
|
||||
return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmOffhand);
|
||||
case WeaponAttackType.RangedAttack:
|
||||
return !HasFlag(UnitFields.Flags2, UnitFlags2.DisarmRanged);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float GetRatingMultiplier(CombatRating cr)
|
||||
{
|
||||
GtCombatRatingsRecord Rating = CliDB.CombatRatingsGameTable.GetRow(getLevel());
|
||||
if (Rating == null)
|
||||
return 1.0f;
|
||||
|
||||
float value = GetGameTableColumnForCombatRating(Rating, cr);
|
||||
if (value == 0)
|
||||
return 1.0f; // By default use minimum coefficient (not must be called)
|
||||
|
||||
return 1.0f / value;
|
||||
}
|
||||
public float GetRatingBonusValue(CombatRating cr)
|
||||
{
|
||||
float baseResult = GetFloatValue(PlayerFields.CombatRating1 + (int)cr) * GetRatingMultiplier(cr);
|
||||
if (cr != CombatRating.ResiliencePlayerDamage)
|
||||
return baseResult;
|
||||
return (float)(1.0f - Math.Pow(0.99f, baseResult)) * 100.0f;
|
||||
}
|
||||
|
||||
void GetDodgeFromAgility(float diminishing, float nondiminishing)
|
||||
{
|
||||
/*// Table for base dodge values
|
||||
float[] dodge_base =
|
||||
{
|
||||
0.037580f, // Warrior
|
||||
0.036520f, // Paladin
|
||||
-0.054500f, // Hunter
|
||||
-0.005900f, // Rogue
|
||||
0.031830f, // Priest
|
||||
0.036640f, // DK
|
||||
0.016750f, // Shaman
|
||||
0.034575f, // Mage
|
||||
0.020350f, // Warlock
|
||||
0.0f, // ??
|
||||
0.049510f // Druid
|
||||
};
|
||||
// Crit/agility to dodge/agility coefficient multipliers; 3.2.0 increased required agility by 15%
|
||||
float[] crit_to_dodge =
|
||||
{
|
||||
0.85f/1.15f, // Warrior
|
||||
1.00f/1.15f, // Paladin
|
||||
1.11f/1.15f, // Hunter
|
||||
2.00f/1.15f, // Rogue
|
||||
1.00f/1.15f, // Priest
|
||||
0.85f/1.15f, // DK
|
||||
1.60f/1.15f, // Shaman
|
||||
1.00f/1.15f, // Mage
|
||||
0.97f/1.15f, // Warlock (?)
|
||||
0.0f, // ??
|
||||
2.00f/1.15f // Druid
|
||||
};
|
||||
|
||||
uint level = getLevel();
|
||||
uint pclass = (uint)GetClass();
|
||||
|
||||
if (level > CliDB.GtChanceToMeleeCritStorage.GetTableRowCount())
|
||||
level = CliDB.GtChanceToMeleeCritStorage.GetTableRowCount() - 1;
|
||||
|
||||
// Dodge per agility is proportional to crit per agility, which is available from DBC files
|
||||
var dodgeRatio = CliDB.GtChanceToMeleeCritStorage.EvaluateTable(level - 1, pclass - 1);
|
||||
if (dodgeRatio == null || pclass > (int)Class.Max)
|
||||
return;
|
||||
|
||||
// @todo research if talents/effects that increase total agility by x% should increase non-diminishing part
|
||||
float base_agility = GetCreateStat(Stats.Agility) * m_auraModifiersGroup[(int)UnitMods.StatAgility][(int)UnitModifierType.BasePCT];
|
||||
float bonus_agility = GetStat(Stats.Agility) - base_agility;
|
||||
|
||||
// calculate diminishing (green in char screen) and non-diminishing (white) contribution
|
||||
diminishing = 100.0f * bonus_agility * dodgeRatio.Value * crit_to_dodge[(int)pclass - 1];
|
||||
nondiminishing = 100.0f * (dodge_base[(int)pclass - 1] + base_agility * dodgeRatio.Value * crit_to_dodge[pclass - 1]);
|
||||
*/
|
||||
}
|
||||
|
||||
float GetTotalPercentageModValue(BaseModGroup modGroup)
|
||||
{
|
||||
return m_auraBaseMod[(int)modGroup][0] + m_auraBaseMod[(int)modGroup][1];
|
||||
}
|
||||
|
||||
public float GetExpertiseDodgeOrParryReduction(WeaponAttackType attType)
|
||||
{
|
||||
float baseExpertise = 7.5f;
|
||||
switch (attType)
|
||||
{
|
||||
case WeaponAttackType.BaseAttack:
|
||||
return baseExpertise + GetUInt32Value(PlayerFields.Expertise) / 4.0f;
|
||||
case WeaponAttackType.OffAttack:
|
||||
return baseExpertise + GetUInt32Value(PlayerFields.OffhandExpertise) / 4.0f;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public bool IsUseEquipedWeapon(bool mainhand)
|
||||
{
|
||||
// disarm applied only to mainhand weapon
|
||||
return !IsInFeralForm() && (!mainhand || !HasFlag(UnitFields.Flags, UnitFlags.Disarmed));
|
||||
}
|
||||
|
||||
public void SetCanTitanGrip(bool value, uint penaltySpellId = 0)
|
||||
{
|
||||
if (value == m_canTitanGrip)
|
||||
return;
|
||||
|
||||
m_canTitanGrip = value;
|
||||
m_titanGripPenaltySpellId = penaltySpellId;
|
||||
}
|
||||
|
||||
void CheckTitanGripPenalty()
|
||||
{
|
||||
if (!CanTitanGrip())
|
||||
return;
|
||||
|
||||
bool apply = IsUsingTwoHandedWeaponInOneHand();
|
||||
if (apply)
|
||||
{
|
||||
if (!HasAura(m_titanGripPenaltySpellId))
|
||||
CastSpell((Unit)null, m_titanGripPenaltySpellId, true);
|
||||
}
|
||||
else
|
||||
RemoveAurasDueToSpell(m_titanGripPenaltySpellId);
|
||||
}
|
||||
|
||||
bool IsTwoHandUsed()
|
||||
{
|
||||
Item mainItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
|
||||
if (!mainItem)
|
||||
return false;
|
||||
|
||||
ItemTemplate itemTemplate = mainItem.GetTemplate();
|
||||
return (itemTemplate.GetInventoryType() == InventoryType.Weapon2Hand && !CanTitanGrip()) ||
|
||||
itemTemplate.GetInventoryType() == InventoryType.Ranged ||
|
||||
(itemTemplate.GetInventoryType() == InventoryType.RangedRight && itemTemplate.GetClass() == ItemClass.Weapon && (ItemSubClassWeapon)itemTemplate.GetSubClass() != ItemSubClassWeapon.Wand);
|
||||
}
|
||||
|
||||
bool IsUsingTwoHandedWeaponInOneHand()
|
||||
{
|
||||
Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);
|
||||
if (offItem && offItem.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand)
|
||||
return true;
|
||||
|
||||
Item mainItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
|
||||
if (!mainItem || mainItem.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand)
|
||||
return false;
|
||||
|
||||
if (!offItem)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void _ApplyWeaponDamage(uint slot, Item item, bool apply)
|
||||
{
|
||||
ItemTemplate proto = item.GetTemplate();
|
||||
WeaponAttackType attType = WeaponAttackType.BaseAttack;
|
||||
float damage = 0.0f;
|
||||
|
||||
if (slot == EquipmentSlot.MainHand && (proto.GetInventoryType() == InventoryType.Ranged || proto.GetInventoryType() == InventoryType.RangedRight))
|
||||
attType = WeaponAttackType.RangedAttack;
|
||||
else if (slot == EquipmentSlot.OffHand)
|
||||
attType = WeaponAttackType.OffAttack;
|
||||
|
||||
float minDamage, maxDamage;
|
||||
item.GetDamage(this, out minDamage, out maxDamage);
|
||||
|
||||
if (minDamage > 0)
|
||||
{
|
||||
damage = apply ? minDamage : SharedConst.BaseMinDamage;
|
||||
SetBaseWeaponDamage(attType, WeaponDamageRange.MinDamage, damage);
|
||||
}
|
||||
|
||||
if (maxDamage > 0)
|
||||
{
|
||||
damage = apply ? maxDamage : SharedConst.BaseMaxDamage;
|
||||
SetBaseWeaponDamage(attType, WeaponDamageRange.MaxDamage, damage);
|
||||
}
|
||||
|
||||
SpellShapeshiftFormRecord shapeshift = CliDB.SpellShapeshiftFormStorage.LookupByKey(GetShapeshiftForm());
|
||||
if (proto.GetDelay() != 0 && !(shapeshift != null && shapeshift.CombatRoundTime != 0))
|
||||
SetBaseAttackTime(attType, apply ? proto.GetDelay() : SharedConst.BaseAttackTime);
|
||||
|
||||
if (CanModifyStats() && (damage != 0 || proto.GetDelay() != 0))
|
||||
UpdateDamagePhysical(attType);
|
||||
}
|
||||
|
||||
public void SetCanParry(bool value)
|
||||
{
|
||||
if (m_canParry == value)
|
||||
return;
|
||||
|
||||
m_canParry = value;
|
||||
UpdateParryPercentage();
|
||||
}
|
||||
|
||||
public void SetCanBlock(bool value)
|
||||
{
|
||||
if (m_canBlock == value)
|
||||
return;
|
||||
|
||||
m_canBlock = value;
|
||||
UpdateBlockPercentage();
|
||||
}
|
||||
|
||||
// duel health and mana reset methods
|
||||
public void SaveHealthBeforeDuel() { healthBeforeDuel = (uint)GetHealth(); }
|
||||
public void SaveManaBeforeDuel() { manaBeforeDuel = (uint)GetPower(PowerType.Mana); }
|
||||
public void RestoreHealthAfterDuel() { SetHealth(healthBeforeDuel); }
|
||||
public void RestoreManaAfterDuel() { SetPower(PowerType.Mana, (int)manaBeforeDuel); }
|
||||
|
||||
void UpdateDuelFlag(long currTime)
|
||||
{
|
||||
if (duel == null || duel.startTimer == 0 || currTime < duel.startTimer + 3)
|
||||
return;
|
||||
|
||||
Global.ScriptMgr.OnPlayerDuelStart(this, duel.opponent);
|
||||
|
||||
SetUInt32Value(PlayerFields.DuelTeam, 1);
|
||||
duel.opponent.SetUInt32Value(PlayerFields.DuelTeam, 2);
|
||||
|
||||
duel.startTimer = 0;
|
||||
duel.startTime = currTime;
|
||||
duel.opponent.duel.startTimer = 0;
|
||||
duel.opponent.duel.startTime = currTime;
|
||||
}
|
||||
|
||||
void CheckDuelDistance(long currTime)
|
||||
{
|
||||
if (duel == null)
|
||||
return;
|
||||
|
||||
ObjectGuid duelFlagGUID = GetGuidValue(PlayerFields.DuelArbiter);
|
||||
GameObject obj = GetMap().GetGameObject(duelFlagGUID);
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
if (duel.outOfBound == 0)
|
||||
{
|
||||
if (!IsWithinDistInMap(obj, 50))
|
||||
{
|
||||
duel.outOfBound = currTime;
|
||||
SendPacket(new DuelOutOfBounds());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsWithinDistInMap(obj, 40))
|
||||
{
|
||||
duel.outOfBound = 0;
|
||||
SendPacket(new DuelInBounds());
|
||||
}
|
||||
else if (currTime >= (duel.outOfBound + 10))
|
||||
DuelComplete(DuelCompleteType.Fled);
|
||||
}
|
||||
}
|
||||
public void DuelComplete(DuelCompleteType type)
|
||||
{
|
||||
// duel not requested
|
||||
if (duel == null)
|
||||
return;
|
||||
|
||||
// Check if DuelComplete() has been called already up in the stack and in that case don't do anything else here
|
||||
if (duel.isCompleted || duel.opponent.duel.isCompleted)
|
||||
return;
|
||||
|
||||
duel.isCompleted = true;
|
||||
duel.opponent.duel.isCompleted = true;
|
||||
|
||||
Log.outDebug(LogFilter.Player, "Duel Complete {0} {1}", GetName(), duel.opponent.GetName());
|
||||
|
||||
DuelComplete duelCompleted = new DuelComplete();
|
||||
duelCompleted.Started = type != DuelCompleteType.Interrupted;
|
||||
SendPacket(duelCompleted);
|
||||
|
||||
if (duel.opponent.GetSession() != null)
|
||||
duel.opponent.SendPacket(duelCompleted);
|
||||
|
||||
if (type != DuelCompleteType.Interrupted)
|
||||
{
|
||||
DuelWinner duelWinner = new DuelWinner();
|
||||
duelWinner.BeatenName = (type == DuelCompleteType.Won ? duel.opponent.GetName() : GetName());
|
||||
duelWinner.WinnerName = (type == DuelCompleteType.Won ? GetName() : duel.opponent.GetName());
|
||||
duelWinner.BeatenVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress();
|
||||
duelWinner.WinnerVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress();
|
||||
duelWinner.Fled = type != DuelCompleteType.Won;
|
||||
|
||||
SendMessageToSet(duelWinner, true);
|
||||
}
|
||||
|
||||
Global.ScriptMgr.OnPlayerDuelEnd(duel.opponent, this, type);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case DuelCompleteType.Fled:
|
||||
// if initiator and opponent are on the same team
|
||||
// or initiator and opponent are not PvP enabled, forcibly stop attacking
|
||||
if (duel.initiator.GetTeam() == duel.opponent.GetTeam())
|
||||
{
|
||||
duel.initiator.AttackStop();
|
||||
duel.opponent.AttackStop();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!duel.initiator.IsPvP())
|
||||
duel.initiator.AttackStop();
|
||||
if (!duel.opponent.IsPvP())
|
||||
duel.opponent.AttackStop();
|
||||
}
|
||||
break;
|
||||
case DuelCompleteType.Won:
|
||||
UpdateCriteria(CriteriaTypes.LoseDuel, 1);
|
||||
duel.opponent.UpdateCriteria(CriteriaTypes.WinDuel, 1);
|
||||
|
||||
// Credit for quest Death's Challenge
|
||||
if (GetClass() == Class.Deathknight && duel.opponent.GetQuestStatus(12733) == QuestStatus.Incomplete)
|
||||
duel.opponent.CastSpell(duel.opponent, 52994, true);
|
||||
|
||||
// Honor points after duel (the winner) - ImpConfig
|
||||
int amount = WorldConfig.GetIntValue(WorldCfg.HonorAfterDuel);
|
||||
if (amount != 0)
|
||||
duel.opponent.RewardHonor(null, 1, amount);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Victory emote spell
|
||||
if (type != DuelCompleteType.Interrupted)
|
||||
duel.opponent.CastSpell(duel.opponent, 52852, true);
|
||||
|
||||
//Remove Duel Flag object
|
||||
GameObject obj = GetMap().GetGameObject(GetGuidValue(PlayerFields.DuelArbiter));
|
||||
if (obj)
|
||||
duel.initiator.RemoveGameObject(obj, true);
|
||||
|
||||
//remove auras
|
||||
var itsAuras = duel.opponent.GetAppliedAuras();
|
||||
foreach (var pair in itsAuras)
|
||||
{
|
||||
Aura aura = pair.Value.GetBase();
|
||||
if (!pair.Value.IsPositive() && aura.GetCasterGUID() == GetGUID() && aura.GetApplyTime() >= duel.startTime)
|
||||
duel.opponent.RemoveAura(pair);
|
||||
}
|
||||
|
||||
var myAuras = GetAppliedAuras();
|
||||
foreach (var pair in myAuras)
|
||||
{
|
||||
Aura aura = pair.Value.GetBase();
|
||||
if (!pair.Value.IsPositive() && aura.GetCasterGUID() == duel.opponent.GetGUID() && aura.GetApplyTime() >= duel.startTime)
|
||||
RemoveAura(pair);
|
||||
}
|
||||
|
||||
// cleanup combo points
|
||||
ClearComboPoints();
|
||||
duel.opponent.ClearComboPoints();
|
||||
|
||||
//cleanups
|
||||
SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty);
|
||||
SetUInt32Value(PlayerFields.DuelTeam, 0);
|
||||
duel.opponent.SetGuidValue(PlayerFields.DuelArbiter, ObjectGuid.Empty);
|
||||
duel.opponent.SetUInt32Value(PlayerFields.DuelTeam, 0);
|
||||
|
||||
duel.opponent.duel = null;
|
||||
duel = null;
|
||||
}
|
||||
|
||||
//PVP
|
||||
public void SetPvPDeath(bool on)
|
||||
{
|
||||
if (on)
|
||||
m_ExtraFlags |= PlayerExtraFlags.PVPDeath;
|
||||
else
|
||||
m_ExtraFlags &= ~PlayerExtraFlags.PVPDeath;
|
||||
}
|
||||
|
||||
public void SetContestedPvPTimer(uint newTime) { m_contestedPvPTimer = newTime; }
|
||||
|
||||
public void ResetContestedPvP()
|
||||
{
|
||||
ClearUnitState(UnitState.AttackPlayer);
|
||||
RemoveFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP);
|
||||
m_contestedPvPTimer = 0;
|
||||
}
|
||||
void UpdateAfkReport(long currTime)
|
||||
{
|
||||
if (m_bgData.bgAfkReportedTimer <= currTime)
|
||||
{
|
||||
m_bgData.bgAfkReportedCount = 0;
|
||||
m_bgData.bgAfkReportedTimer = currTime + 5 * Time.Minute;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateContestedPvP(uint diff)
|
||||
{
|
||||
if (m_contestedPvPTimer == 0 || IsInCombat())
|
||||
return;
|
||||
|
||||
if (m_contestedPvPTimer <= diff)
|
||||
{
|
||||
ResetContestedPvP();
|
||||
}
|
||||
else
|
||||
m_contestedPvPTimer -= diff;
|
||||
}
|
||||
|
||||
public void UpdatePvPFlag(long currTime)
|
||||
{
|
||||
if (!IsPvP())
|
||||
return;
|
||||
|
||||
if (pvpInfo.EndTimer == 0 || currTime < (pvpInfo.EndTimer + 300) || pvpInfo.IsHostile)
|
||||
return;
|
||||
|
||||
UpdatePvP(false);
|
||||
}
|
||||
|
||||
public void UpdatePvP(bool state, bool Override = false)
|
||||
{
|
||||
if (!state || Override)
|
||||
{
|
||||
SetPvP(state);
|
||||
pvpInfo.EndTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pvpInfo.EndTimer = Time.UnixTime;
|
||||
SetPvP(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePvPState(bool onlyFFA = false)
|
||||
{
|
||||
// @todo should we always synchronize UNIT_FIELD_BYTES_2, 1 of controller and controlled?
|
||||
// no, we shouldn't, those are checked for affecting player by client
|
||||
if (!pvpInfo.IsInNoPvPArea && !IsGameMaster()
|
||||
&& (pvpInfo.IsInFFAPvPArea || Global.WorldMgr.IsFFAPvPRealm()))
|
||||
{
|
||||
if (!IsFFAPvP())
|
||||
{
|
||||
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
||||
foreach (var unit in m_Controlled)
|
||||
unit.SetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, (byte)UnitBytes2Flags.FFAPvp);
|
||||
}
|
||||
}
|
||||
else if (IsFFAPvP())
|
||||
{
|
||||
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
||||
foreach (var unit in m_Controlled)
|
||||
unit.RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
||||
}
|
||||
|
||||
if (onlyFFA)
|
||||
return;
|
||||
|
||||
if (pvpInfo.IsHostile) // in hostile area
|
||||
{
|
||||
if (!IsPvP() || pvpInfo.EndTimer != 0)
|
||||
UpdatePvP(true, true);
|
||||
}
|
||||
else // in friendly area
|
||||
{
|
||||
if (IsPvP() && !HasFlag(PlayerFields.Flags, PlayerFlags.InPVP) && pvpInfo.EndTimer == 0)
|
||||
pvpInfo.EndTimer = Time.UnixTime; // start toggle-off
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetPvP(bool state)
|
||||
{
|
||||
base.SetPvP(state);
|
||||
foreach (var unit in m_Controlled)
|
||||
unit.SetPvP(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* 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.Achievements;
|
||||
using Game.Chat;
|
||||
using Game.DataStorage;
|
||||
using Game.Garrisons;
|
||||
using Game.Groups;
|
||||
using Game.Mails;
|
||||
using Game.Maps;
|
||||
using Game.Misc;
|
||||
using Game.Spells;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public WorldSession GetSession() { return Session; }
|
||||
public PlayerSocial GetSocial() { return m_social; }
|
||||
|
||||
//Gossip
|
||||
public PlayerMenu PlayerTalkClass;
|
||||
PlayerSocial m_social;
|
||||
List<Channel> m_channels = new List<Channel>();
|
||||
List<ObjectGuid> WhisperList = new List<ObjectGuid>();
|
||||
public string autoReplyMsg;
|
||||
|
||||
//Inventory
|
||||
Dictionary<ulong, EquipmentSetInfo> _equipmentSets = new Dictionary<ulong, EquipmentSetInfo>();
|
||||
public List<ItemSetEffect> ItemSetEff = new List<ItemSetEffect>();
|
||||
List<EnchantDuration> m_enchantDuration = new List<EnchantDuration>();
|
||||
List<Item> m_itemDuration = new List<Item>();
|
||||
List<ObjectGuid> m_itemSoulboundTradeable = new List<ObjectGuid>();
|
||||
List<ObjectGuid> m_refundableItems = new List<ObjectGuid>();
|
||||
public List<Item> ItemUpdateQueue = new List<Item>();
|
||||
VoidStorageItem[] _voidStorageItems = new VoidStorageItem[SharedConst.VoidStorageMaxSlot];
|
||||
Item[] m_items = new Item[(int)PlayerSlots.Count];
|
||||
uint m_WeaponProficiency;
|
||||
uint m_ArmorProficiency;
|
||||
uint m_currentBuybackSlot;
|
||||
TradeData m_trade;
|
||||
|
||||
//PVP
|
||||
BgBattlegroundQueueID_Rec[] m_bgBattlegroundQueueID = new BgBattlegroundQueueID_Rec[SharedConst.MaxPlayerBGQueues];
|
||||
BGData m_bgData;
|
||||
bool m_IsBGRandomWinner;
|
||||
public PvPInfo pvpInfo;
|
||||
uint m_ArenaTeamIdInvited;
|
||||
long m_lastHonorUpdateTime;
|
||||
uint m_contestedPvPTimer;
|
||||
|
||||
//Groups/Raids
|
||||
GroupReference m_group = new GroupReference();
|
||||
GroupReference m_originalGroup = new GroupReference();
|
||||
Group m_groupInvite;
|
||||
GroupUpdateFlags m_groupUpdateMask;
|
||||
bool m_bPassOnGroupLoot;
|
||||
GroupUpdateCounter[] m_groupUpdateSequences = new GroupUpdateCounter[2];
|
||||
|
||||
public Dictionary<uint, InstanceBind>[] m_boundInstances = new Dictionary<uint, InstanceBind>[(int)Difficulty.Max];
|
||||
Dictionary<uint, long> _instanceResetTimes = new Dictionary<uint, long>();
|
||||
uint _pendingBindId;
|
||||
uint _pendingBindTimer;
|
||||
public bool m_InstanceValid;
|
||||
|
||||
Difficulty m_dungeonDifficulty;
|
||||
Difficulty m_raidDifficulty;
|
||||
Difficulty m_legacyRaidDifficulty;
|
||||
Difficulty m_prevMapDifficulty;
|
||||
|
||||
//Movement
|
||||
public PlayerTaxi m_taxi = new PlayerTaxi();
|
||||
public byte[] m_forced_speed_changes = new byte[(int)UnitMoveType.Max];
|
||||
uint m_lastFallTime;
|
||||
float m_lastFallZ;
|
||||
WorldLocation teleportDest;
|
||||
TeleportToOptions m_teleport_options;
|
||||
bool mSemaphoreTeleport_Near;
|
||||
bool mSemaphoreTeleport_Far;
|
||||
PlayerDelayedOperations m_DelayedOperations;
|
||||
bool m_bCanDelayTeleport;
|
||||
bool m_bHasDelayedTeleport;
|
||||
|
||||
PlayerUnderwaterState m_MirrorTimerFlags;
|
||||
PlayerUnderwaterState m_MirrorTimerFlagsLast;
|
||||
bool m_isInWater;
|
||||
|
||||
//Stats
|
||||
uint m_baseSpellPower;
|
||||
uint m_baseManaRegen;
|
||||
uint m_baseHealthRegen;
|
||||
int m_spellPenetrationItemMod;
|
||||
uint m_lastPotionId;
|
||||
|
||||
//Spell
|
||||
Dictionary<uint, PlayerSpell> m_spells = new Dictionary<uint, PlayerSpell>();
|
||||
Dictionary<uint, SkillStatusData> mSkillStatus = new Dictionary<uint, SkillStatusData>();
|
||||
Dictionary<uint, PlayerCurrency> _currencyStorage = new Dictionary<uint, PlayerCurrency>();
|
||||
List<SpellModifier>[][] m_spellMods = new List<SpellModifier>[(int)SpellModOp.Max][];
|
||||
MultiMap<uint, uint> m_overrideSpells = new MultiMap<uint, uint>();
|
||||
public Spell m_spellModTakingSpell;
|
||||
uint m_oldpetspell;
|
||||
// Rune type / Rune timer
|
||||
uint[] m_runeGraceCooldown = new uint[PlayerConst.MaxRunes];
|
||||
uint[] m_lastRuneGraceTimers = new uint[PlayerConst.MaxRunes];
|
||||
|
||||
//Mail
|
||||
List<Mail> m_mail = new List<Mail>();
|
||||
Dictionary<ulong, Item> mMitems = new Dictionary<ulong, Item>();
|
||||
public byte unReadMails;
|
||||
long m_nextMailDelivereTime;
|
||||
public bool m_mailsLoaded;
|
||||
public bool m_mailsUpdated;
|
||||
|
||||
//Pets
|
||||
public uint m_stableSlots;
|
||||
uint m_temporaryUnsummonedPetNumber;
|
||||
uint m_lastpetnumber;
|
||||
|
||||
// Player summoning
|
||||
long m_summon_expire;
|
||||
WorldLocation m_summon_location;
|
||||
|
||||
RestMgr _restMgr;
|
||||
|
||||
//Combat
|
||||
int[] baseRatingValue = new int[(int)CombatRating.Max];
|
||||
public float[][] m_auraBaseMod = new float[(int)BaseModGroup.End][];
|
||||
public DuelInfo duel;
|
||||
bool m_canParry;
|
||||
bool m_canBlock;
|
||||
bool m_canTitanGrip;
|
||||
uint m_titanGripPenaltySpellId;
|
||||
uint m_deathTimer;
|
||||
long m_deathExpireTime;
|
||||
byte m_swingErrorMsg;
|
||||
uint m_combatExitTime;
|
||||
uint m_regenTimerCount;
|
||||
uint m_weaponChangeTimer;
|
||||
|
||||
//Quest
|
||||
List<uint> m_timedquests = new List<uint>();
|
||||
List<uint> m_weeklyquests = new List<uint>();
|
||||
List<uint> m_monthlyquests = new List<uint>();
|
||||
MultiMap<uint, uint> m_seasonalquests = new MultiMap<uint, uint>();
|
||||
Dictionary<uint, QuestStatusData> m_QuestStatus = new Dictionary<uint, QuestStatusData>();
|
||||
Dictionary<uint, QuestSaveType> m_QuestStatusSave = new Dictionary<uint, QuestSaveType>();
|
||||
List<uint> m_DFQuests = new List<uint>();
|
||||
List<uint> m_RewardedQuests = new List<uint>();
|
||||
Dictionary<uint, QuestSaveType> m_RewardedQuestsSave = new Dictionary<uint, QuestSaveType>();
|
||||
|
||||
bool m_DailyQuestChanged;
|
||||
bool m_WeeklyQuestChanged;
|
||||
bool m_MonthlyQuestChanged;
|
||||
bool m_SeasonalQuestChanged;
|
||||
long m_lastDailyQuestTime;
|
||||
|
||||
Garrison _garrison;
|
||||
|
||||
CinematicManager _cinematicMgr;
|
||||
|
||||
// variables to save health and mana before duel and restore them after duel
|
||||
ulong healthBeforeDuel;
|
||||
uint manaBeforeDuel;
|
||||
|
||||
bool _advancedCombatLoggingEnabled;
|
||||
|
||||
WorldLocation _corpseLocation;
|
||||
|
||||
//Core
|
||||
WorldSession Session;
|
||||
uint m_nextSave;
|
||||
byte m_cinematic;
|
||||
|
||||
uint m_movie;
|
||||
|
||||
SpecializationInfo _specializationInfo;
|
||||
public List<ObjectGuid> m_clientGUIDs = new List<ObjectGuid>();
|
||||
public WorldObject seerView;
|
||||
// only changed for direct client control (possess, vehicle etc.), not stuff you control using pet commands
|
||||
public Unit m_unitMovedByMe;
|
||||
Team m_team;
|
||||
public Stack<uint> m_timeSyncQueue = new Stack<uint>();
|
||||
uint m_timeSyncTimer;
|
||||
public uint m_timeSyncClient;
|
||||
public uint m_timeSyncServer;
|
||||
ReputationMgr reputationMgr;
|
||||
QuestObjectiveCriteriaManager m_questObjectiveCriteriaMgr;
|
||||
public AtLoginFlags atLoginFlags;
|
||||
public bool m_itemUpdateQueueBlocked;
|
||||
|
||||
PlayerExtraFlags m_ExtraFlags;
|
||||
|
||||
public bool isDebugAreaTriggers { get; set; }
|
||||
uint m_zoneUpdateId;
|
||||
uint m_areaUpdateId;
|
||||
uint m_zoneUpdateTimer;
|
||||
|
||||
uint m_ChampioningFaction;
|
||||
byte m_grantableLevels;
|
||||
byte m_fishingSteps;
|
||||
|
||||
// Recall position
|
||||
WorldLocation m_recall_location;
|
||||
WorldLocation homebind;
|
||||
uint homebindAreaId;
|
||||
uint m_HomebindTimer;
|
||||
|
||||
ResurrectionData _resurrectionData;
|
||||
|
||||
PlayerAchievementMgr m_achievementSys;
|
||||
|
||||
SceneMgr m_sceneMgr;
|
||||
|
||||
Dictionary<ObjectGuid /*LootObject*/, ObjectGuid /*WorldObject*/> m_AELootView = new Dictionary<ObjectGuid, ObjectGuid>();
|
||||
|
||||
CUFProfile[] _CUFProfiles = new CUFProfile[PlayerConst.MaxCUFProfiles];
|
||||
float[] m_powerFraction = new float[(int)PowerType.MaxPerClass];
|
||||
int[] m_MirrorTimer = new int[3];
|
||||
|
||||
ulong m_GuildIdInvited;
|
||||
DeclinedName _declinedname;
|
||||
Runes m_runes = new Runes();
|
||||
uint m_drunkTimer;
|
||||
long m_logintime;
|
||||
long m_Last_tick;
|
||||
uint m_PlayedTimeTotal;
|
||||
uint m_PlayedTimeLevel;
|
||||
|
||||
Dictionary<byte, ActionButton> m_actionButtons = new Dictionary<byte, ActionButton>();
|
||||
ObjectGuid m_divider;
|
||||
uint m_ingametime;
|
||||
|
||||
PlayerCommandStates _activeCheats;
|
||||
}
|
||||
|
||||
public class PlayerInfo
|
||||
{
|
||||
public uint MapId;
|
||||
public uint ZoneId;
|
||||
public float PositionX;
|
||||
public float PositionY;
|
||||
public float PositionZ;
|
||||
public float Orientation;
|
||||
|
||||
public uint DisplayId_m;
|
||||
public uint DisplayId_f;
|
||||
|
||||
public List<PlayerCreateInfoItem> item = new List<PlayerCreateInfoItem>();
|
||||
public List<uint> customSpells = new List<uint>();
|
||||
public List<uint> castSpells = new List<uint>();
|
||||
public List<PlayerCreateInfoAction> action = new List<PlayerCreateInfoAction>();
|
||||
public List<SkillRaceClassInfoRecord> skills = new List<SkillRaceClassInfoRecord>();
|
||||
|
||||
public PlayerLevelInfo[] levelInfo = new PlayerLevelInfo[WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel)];
|
||||
}
|
||||
|
||||
public class PlayerCreateInfoItem
|
||||
{
|
||||
public PlayerCreateInfoItem(uint id, uint amount)
|
||||
{
|
||||
item_id = id;
|
||||
item_amount = amount;
|
||||
}
|
||||
|
||||
public uint item_id;
|
||||
public uint item_amount;
|
||||
}
|
||||
|
||||
public class PlayerCreateInfoAction
|
||||
{
|
||||
public PlayerCreateInfoAction() : this(0, 0, 0) { }
|
||||
public PlayerCreateInfoAction(byte _button, uint _action, byte _type)
|
||||
{
|
||||
button = _button;
|
||||
type = _type;
|
||||
action = _action;
|
||||
}
|
||||
|
||||
public byte button;
|
||||
public byte type;
|
||||
public uint action;
|
||||
}
|
||||
|
||||
public class PlayerLevelInfo
|
||||
{
|
||||
public ushort[] stats = new ushort[(int)Stats.Max];
|
||||
}
|
||||
|
||||
public class PlayerCurrency
|
||||
{
|
||||
public PlayerCurrencyState state;
|
||||
public uint Quantity;
|
||||
public uint WeeklyQuantity;
|
||||
public uint TrackedQuantity;
|
||||
public byte Flags;
|
||||
}
|
||||
|
||||
struct PlayerDynamicFieldSpellModByLabel
|
||||
{
|
||||
public uint Mod;
|
||||
public float Value;
|
||||
public uint Label;
|
||||
}
|
||||
|
||||
public class SpecializationInfo
|
||||
{
|
||||
public SpecializationInfo()
|
||||
{
|
||||
for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i)
|
||||
{
|
||||
Talents[i] = new Dictionary<uint, PlayerSpellState>();
|
||||
Glyphs[i] = new List<uint>();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<uint, PlayerSpellState>[] Talents = new Dictionary<uint, PlayerSpellState>[PlayerConst.MaxSpecializations];
|
||||
public List<uint>[] Glyphs = new List<uint>[PlayerConst.MaxSpecializations];
|
||||
public uint ResetTalentsCost;
|
||||
public long ResetTalentsTime;
|
||||
public uint PrimarySpecialization;
|
||||
public byte ActiveGroup;
|
||||
}
|
||||
|
||||
public class Runes
|
||||
{
|
||||
public void SetRuneState(byte index, bool set = true)
|
||||
{
|
||||
var id = CooldownOrder.LookupByIndex(index);
|
||||
if (set)
|
||||
{
|
||||
RuneState |= (byte)(1 << index); // usable
|
||||
if (id == 0)
|
||||
CooldownOrder.Add(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
RuneState &= (byte)~(1 << index); // on cooldown
|
||||
if (id != 0)
|
||||
CooldownOrder.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public List<byte> CooldownOrder = new List<byte>();
|
||||
public uint[] Cooldown = new uint[PlayerConst.MaxRunes];
|
||||
public byte RuneState; // mask of available runes
|
||||
}
|
||||
|
||||
public class ActionButton
|
||||
{
|
||||
public ActionButton()
|
||||
{
|
||||
packedData = 0;
|
||||
uState = ActionButtonUpdateState.New;
|
||||
}
|
||||
|
||||
public ActionButtonType GetButtonType() { return (ActionButtonType)((packedData & 0xFFFFFFFF00000000) >> 56); }
|
||||
public uint GetAction() { return (uint)(packedData & 0x00000000FFFFFFFF); }
|
||||
public void SetActionAndType(ulong action, ActionButtonType type)
|
||||
{
|
||||
ulong newData = action | ((ulong)type << 56);
|
||||
if (newData != packedData || uState == ActionButtonUpdateState.Deleted)
|
||||
{
|
||||
packedData = newData;
|
||||
if (uState != ActionButtonUpdateState.New)
|
||||
uState = ActionButtonUpdateState.Changed;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong packedData;
|
||||
public ActionButtonUpdateState uState;
|
||||
}
|
||||
|
||||
public class ResurrectionData
|
||||
{
|
||||
public ObjectGuid GUID;
|
||||
public WorldLocation Location = new WorldLocation();
|
||||
public uint Health;
|
||||
public uint Mana;
|
||||
public uint Aura;
|
||||
}
|
||||
|
||||
public struct PvPInfo
|
||||
{
|
||||
public bool IsHostile;
|
||||
public bool IsInHostileArea; //> Marks if player is in an area which forces PvP flag
|
||||
public bool IsInNoPvPArea; //> Marks if player is in a sanctuary or friendly capital city
|
||||
public bool IsInFFAPvPArea; //> Marks if player is in an FFAPvP area (such as Gurubashi Arena)
|
||||
public long EndTimer; //> Time when player unflags himself for PvP (flag removed after 5 minutes)
|
||||
}
|
||||
|
||||
public class DuelInfo
|
||||
{
|
||||
public Player initiator;
|
||||
public Player opponent;
|
||||
public long startTimer;
|
||||
public long startTime;
|
||||
public long outOfBound;
|
||||
public bool isMounted;
|
||||
public bool isCompleted;
|
||||
|
||||
public bool IsDueling() { return opponent != null; }
|
||||
}
|
||||
|
||||
public class AccessRequirement
|
||||
{
|
||||
public byte levelMin;
|
||||
public byte levelMax;
|
||||
public uint item;
|
||||
public uint item2;
|
||||
public uint quest_A;
|
||||
public uint quest_H;
|
||||
public uint achievement;
|
||||
public string questFailedText;
|
||||
}
|
||||
|
||||
public class EnchantDuration
|
||||
{
|
||||
public EnchantDuration(Item _item = null, EnchantmentSlot _slot = EnchantmentSlot.Max, uint _leftduration = 0)
|
||||
{
|
||||
item = _item;
|
||||
slot = _slot;
|
||||
leftduration = _leftduration;
|
||||
}
|
||||
|
||||
public Item item;
|
||||
public EnchantmentSlot slot;
|
||||
public uint leftduration;
|
||||
}
|
||||
|
||||
public class VoidStorageItem
|
||||
{
|
||||
public VoidStorageItem(ulong id, uint entry, ObjectGuid creator, ItemRandomEnchantmentId randomPropertyId, uint suffixFactor, uint upgradeId, uint fixedScalingLevel, uint artifactKnowledgeLevel, byte context, ICollection<uint> bonuses)
|
||||
{
|
||||
ItemId = id;
|
||||
ItemEntry = entry;
|
||||
CreatorGuid = creator;
|
||||
ItemRandomPropertyId = randomPropertyId;
|
||||
ItemSuffixFactor = suffixFactor;
|
||||
ItemUpgradeId = upgradeId;
|
||||
FixedScalingLevel = fixedScalingLevel;
|
||||
ArtifactKnowledgeLevel = artifactKnowledgeLevel;
|
||||
Context = context;
|
||||
|
||||
foreach (var value in bonuses)
|
||||
BonusListIDs.Add(value);
|
||||
}
|
||||
|
||||
public ulong ItemId;
|
||||
public uint ItemEntry;
|
||||
public ObjectGuid CreatorGuid;
|
||||
public ItemRandomEnchantmentId ItemRandomPropertyId;
|
||||
public uint ItemSuffixFactor;
|
||||
public uint ItemUpgradeId;
|
||||
public uint FixedScalingLevel;
|
||||
public uint ArtifactKnowledgeLevel;
|
||||
public byte Context;
|
||||
public List<uint> BonusListIDs = new List<uint>();
|
||||
}
|
||||
|
||||
public class EquipmentSetInfo
|
||||
{
|
||||
public EquipmentSetInfo()
|
||||
{
|
||||
state = EquipmentSetUpdateState.New;
|
||||
Data = new EquipmentSetData();
|
||||
}
|
||||
|
||||
public EquipmentSetUpdateState state;
|
||||
public EquipmentSetData Data;
|
||||
|
||||
// Data sent in EquipmentSet related packets
|
||||
public class EquipmentSetData
|
||||
{
|
||||
public EquipmentSetType Type;
|
||||
public ulong Guid; // Set Identifier
|
||||
public uint SetID; // Index
|
||||
public uint IgnoreMask ; // Mask of EquipmentSlot
|
||||
public int AssignedSpecIndex = -1; // Index of character specialization that this set is automatically equipped for
|
||||
public string SetName = "";
|
||||
public string SetIcon = "";
|
||||
public Array<ObjectGuid> Pieces = new Array<ObjectGuid>(EquipmentSlot.End);
|
||||
public Array<int> Appearances = new Array<int>(EquipmentSlot.End); // ItemModifiedAppearanceID
|
||||
public Array<int> Enchants = new Array<int>(2); // SpellItemEnchantmentID
|
||||
}
|
||||
|
||||
public enum EquipmentSetType
|
||||
{
|
||||
Equipment = 0,
|
||||
Transmog = 1
|
||||
}
|
||||
}
|
||||
|
||||
public class BgBattlegroundQueueID_Rec
|
||||
{
|
||||
public BattlegroundQueueTypeId bgQueueTypeId;
|
||||
public uint invitedToInstance;
|
||||
public uint joinTime;
|
||||
}
|
||||
|
||||
// Holder for Battlegrounddata
|
||||
public class BGData
|
||||
{
|
||||
public BGData()
|
||||
{
|
||||
bgTypeID = BattlegroundTypeId.None;
|
||||
ClearTaxiPath();
|
||||
joinPos = new WorldLocation();
|
||||
}
|
||||
|
||||
public uint bgInstanceID; //< This variable is set to bg.m_InstanceID,
|
||||
// when player is teleported to BG - (it is Battleground's GUID)
|
||||
public BattlegroundTypeId bgTypeID;
|
||||
|
||||
public List<ObjectGuid> bgAfkReporter = new List<ObjectGuid>();
|
||||
public byte bgAfkReportedCount;
|
||||
public long bgAfkReportedTimer;
|
||||
|
||||
public uint bgTeam; //< What side the player will be added to
|
||||
|
||||
public uint mountSpell;
|
||||
public uint[] taxiPath = new uint[2];
|
||||
|
||||
public WorldLocation joinPos; //< From where player entered BG
|
||||
|
||||
public void ClearTaxiPath() { taxiPath[0] = taxiPath[1] = 0; }
|
||||
public bool HasTaxiPath() { return taxiPath[0] != 0 && taxiPath[1] != 0; }
|
||||
}
|
||||
|
||||
public class CUFProfile
|
||||
{
|
||||
public CUFProfile()
|
||||
{
|
||||
BoolOptions = new BitArray((int)CUFBoolOptions.BoolOptionsCount);
|
||||
}
|
||||
|
||||
public CUFProfile(string name, ushort frameHeight, ushort frameWidth, byte sortBy, byte healthText, uint boolOptions,
|
||||
byte topPoint, byte bottomPoint, byte leftPoint, ushort topOffset, ushort bottomOffset, ushort leftOffset)
|
||||
{
|
||||
ProfileName = name;
|
||||
BoolOptions = new BitArray(new int[] { (int)boolOptions });
|
||||
|
||||
FrameHeight = frameHeight;
|
||||
FrameWidth = frameWidth;
|
||||
SortBy = sortBy;
|
||||
HealthText = healthText;
|
||||
TopPoint = topPoint;
|
||||
BottomPoint = bottomPoint;
|
||||
LeftPoint = leftPoint;
|
||||
TopOffset = topOffset;
|
||||
BottomOffset = bottomOffset;
|
||||
LeftOffset = leftOffset;
|
||||
}
|
||||
|
||||
public void SetOption(CUFBoolOptions opt, byte arg)
|
||||
{
|
||||
BoolOptions.Set((int)opt, arg != 0);
|
||||
}
|
||||
public bool GetOption(CUFBoolOptions opt)
|
||||
{
|
||||
return BoolOptions.Get((int)opt);
|
||||
}
|
||||
public ulong GetUlongOptionValue()
|
||||
{
|
||||
int[] array = new int[1];
|
||||
BoolOptions.CopyTo(array, 0);
|
||||
return (ulong)array[0];
|
||||
}
|
||||
|
||||
public string ProfileName;
|
||||
public ushort FrameHeight;
|
||||
public ushort FrameWidth;
|
||||
public byte SortBy;
|
||||
public byte HealthText;
|
||||
|
||||
// LeftAlign, TopAlight, BottomAlign
|
||||
public byte TopPoint;
|
||||
public byte BottomPoint;
|
||||
public byte LeftPoint;
|
||||
|
||||
// LeftOffset, TopOffset and BottomOffset
|
||||
public ushort TopOffset;
|
||||
public ushort BottomOffset;
|
||||
public ushort LeftOffset;
|
||||
|
||||
public BitArray BoolOptions;
|
||||
|
||||
// More fields can be added to BoolOptions without changing DB schema (up to 32, currently 27)
|
||||
}
|
||||
|
||||
struct GroupUpdateCounter
|
||||
{
|
||||
public ObjectGuid GroupGuid;
|
||||
public int UpdateSequenceNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* 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.Groups;
|
||||
using Game.Maps;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
Player GetNextRandomRaidMember(float radius)
|
||||
{
|
||||
Group group = GetGroup();
|
||||
if (!group)
|
||||
return null;
|
||||
|
||||
List<Player> nearMembers = new List<Player>();
|
||||
|
||||
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player Target = refe.GetSource();
|
||||
|
||||
// IsHostileTo check duel and controlled by enemy
|
||||
if (Target && Target != this && IsWithinDistInMap(Target, radius) &&
|
||||
!Target.HasInvisibilityAura() && !IsHostileTo(Target))
|
||||
nearMembers.Add(Target);
|
||||
}
|
||||
|
||||
if (nearMembers.Empty())
|
||||
return null;
|
||||
|
||||
int randTarget = RandomHelper.IRand(0, nearMembers.Count - 1);
|
||||
return nearMembers[randTarget];
|
||||
}
|
||||
|
||||
public PartyResult CanUninviteFromGroup(ObjectGuid guidMember = default(ObjectGuid))
|
||||
{
|
||||
Group grp = GetGroup();
|
||||
if (!grp)
|
||||
return PartyResult.NotInGroup;
|
||||
|
||||
if (grp.isLFGGroup())
|
||||
{
|
||||
ObjectGuid gguid = grp.GetGUID();
|
||||
if (Global.LFGMgr.GetKicksLeft(gguid) == 0)
|
||||
return PartyResult.PartyLfgBootLimit;
|
||||
|
||||
LfgState state = Global.LFGMgr.GetState(gguid);
|
||||
if (Global.LFGMgr.IsVoteKickActive(gguid))
|
||||
return PartyResult.PartyLfgBootInProgress;
|
||||
|
||||
if (grp.GetMembersCount() <= SharedConst.LFGKickVotesNeeded)
|
||||
return PartyResult.PartyLfgBootTooFewPlayers;
|
||||
|
||||
if (state == LfgState.FinishedDungeon)
|
||||
return PartyResult.PartyLfgBootDungeonComplete;
|
||||
|
||||
if (grp.isRollLootActive())
|
||||
return PartyResult.PartyLfgBootLootRolls;
|
||||
|
||||
// @todo Should also be sent when anyone has recently left combat, with an aprox ~5 seconds timer.
|
||||
for (GroupReference refe = grp.GetFirstMember(); refe != null; refe = refe.next())
|
||||
if (refe.GetSource() && refe.GetSource().IsInCombat())
|
||||
return PartyResult.PartyLfgBootInCombat;
|
||||
|
||||
/* Missing support for these types
|
||||
return ERR_PARTY_LFG_BOOT_COOLDOWN_S;
|
||||
return ERR_PARTY_LFG_BOOT_NOT_ELIGIBLE_S;
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!grp.IsLeader(GetGUID()) && !grp.IsAssistant(GetGUID()))
|
||||
return PartyResult.NotLeader;
|
||||
|
||||
if (InBattleground())
|
||||
return PartyResult.InviteRestricted;
|
||||
|
||||
if (grp.IsLeader(guidMember))
|
||||
return PartyResult.NotLeader;
|
||||
}
|
||||
|
||||
return PartyResult.Ok;
|
||||
}
|
||||
|
||||
public bool isUsingLfg()
|
||||
{
|
||||
return Global.LFGMgr.GetState(GetGUID()) != LfgState.None;
|
||||
}
|
||||
|
||||
bool inRandomLfgDungeon()
|
||||
{
|
||||
if (Global.LFGMgr.selectedRandomLfgDungeon(GetGUID()))
|
||||
{
|
||||
Map map = GetMap();
|
||||
return Global.LFGMgr.inLfgDungeonMap(GetGUID(), map.GetId(), map.GetDifficultyID());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetBattlegroundOrBattlefieldRaid(Group group, byte subgroup)
|
||||
{
|
||||
//we must move references from m_group to m_originalGroup
|
||||
SetOriginalGroup(GetGroup(), GetSubGroup());
|
||||
|
||||
m_group.unlink();
|
||||
m_group.link(group, this);
|
||||
m_group.setSubGroup(subgroup);
|
||||
}
|
||||
|
||||
public void RemoveFromBattlegroundOrBattlefieldRaid()
|
||||
{
|
||||
//remove existing reference
|
||||
m_group.unlink();
|
||||
Group group = GetOriginalGroup();
|
||||
if (group)
|
||||
{
|
||||
m_group.link(group, this);
|
||||
m_group.setSubGroup(GetOriginalSubGroup());
|
||||
}
|
||||
SetOriginalGroup(null);
|
||||
}
|
||||
|
||||
public void SetOriginalGroup(Group group, byte subgroup = 0)
|
||||
{
|
||||
if (!group)
|
||||
m_originalGroup.unlink();
|
||||
else
|
||||
{
|
||||
m_originalGroup.link(group, this);
|
||||
m_originalGroup.setSubGroup(subgroup);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGroup(Group group, byte subgroup = 0)
|
||||
{
|
||||
if (!group)
|
||||
m_group.unlink();
|
||||
else
|
||||
{
|
||||
m_group.link(group, this);
|
||||
m_group.setSubGroup(subgroup);
|
||||
}
|
||||
|
||||
UpdateObjectVisibility(false);
|
||||
}
|
||||
|
||||
public void SetPartyType(GroupCategory category, GroupType type)
|
||||
{
|
||||
Contract.Assert(category < GroupCategory.Max);
|
||||
byte value = GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType);
|
||||
value &= (byte)~((byte)0xFF << ((byte)category * 4));
|
||||
value |= (byte)((byte)type << ((byte)category * 4));
|
||||
SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetPartyType, value);
|
||||
}
|
||||
|
||||
public void ResetGroupUpdateSequenceIfNeeded(Group group)
|
||||
{
|
||||
GroupCategory category = group.GetGroupCategory();
|
||||
// Rejoining the last group should not reset the sequence
|
||||
if (m_groupUpdateSequences[(int)category].GroupGuid != group.GetGUID())
|
||||
{
|
||||
var groupUpdate = m_groupUpdateSequences[(int)category];
|
||||
groupUpdate.GroupGuid = group.GetGUID();
|
||||
groupUpdate.UpdateSequenceNumber = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int NextGroupUpdateSequenceNumber(GroupCategory category)
|
||||
{
|
||||
var groupUpdate = m_groupUpdateSequences[(int)category];
|
||||
return groupUpdate.UpdateSequenceNumber++;
|
||||
}
|
||||
|
||||
public bool IsAtGroupRewardDistance(WorldObject pRewardSource)
|
||||
{
|
||||
if (!pRewardSource)
|
||||
return false;
|
||||
WorldObject player = GetCorpse();
|
||||
if (!player || IsAlive())
|
||||
player = this;
|
||||
|
||||
if (player.GetMapId() != pRewardSource.GetMapId() || player.GetInstanceId() != pRewardSource.GetInstanceId())
|
||||
return false;
|
||||
|
||||
if (player.GetMap().IsDungeon())
|
||||
return true;
|
||||
|
||||
return pRewardSource.GetDistance(player) <= WorldConfig.GetFloatValue(WorldCfg.GroupXpDistance);
|
||||
}
|
||||
|
||||
public Group GetGroupInvite() { return m_groupInvite; }
|
||||
public void SetGroupInvite(Group group) { m_groupInvite = group; }
|
||||
public Group GetGroup() { return m_group.getTarget(); }
|
||||
public GroupReference GetGroupRef() { return m_group; }
|
||||
public byte GetSubGroup() { return m_group.getSubGroup(); }
|
||||
public GroupUpdateFlags GetGroupUpdateFlag() { return m_groupUpdateMask; }
|
||||
public void SetGroupUpdateFlag(GroupUpdateFlags flag) { m_groupUpdateMask |= flag; }
|
||||
public void RemoveGroupUpdateFlag(GroupUpdateFlags flag) { m_groupUpdateMask &= ~flag; }
|
||||
|
||||
public Group GetOriginalGroup() { return m_originalGroup.getTarget(); }
|
||||
public GroupReference GetOriginalGroupRef() { return m_originalGroup; }
|
||||
public byte GetOriginalSubGroup() { return m_originalGroup.getSubGroup(); }
|
||||
|
||||
public void SetPassOnGroupLoot(bool bPassOnGroupLoot) { m_bPassOnGroupLoot = bPassOnGroupLoot; }
|
||||
public bool GetPassOnGroupLoot() { return m_bPassOnGroupLoot; }
|
||||
|
||||
public bool IsGroupVisibleFor(Player p)
|
||||
{
|
||||
switch (WorldConfig.GetIntValue(WorldCfg.GroupVisibility))
|
||||
{
|
||||
default:
|
||||
return IsInSameGroupWith(p);
|
||||
case 1:
|
||||
return IsInSameRaidWith(p);
|
||||
case 2:
|
||||
return GetTeam() == p.GetTeam();
|
||||
}
|
||||
}
|
||||
public bool IsInSameGroupWith(Player p)
|
||||
{
|
||||
return p == this || (GetGroup() &&
|
||||
GetGroup() == p.GetGroup() && GetGroup().SameSubGroup(this, p));
|
||||
}
|
||||
|
||||
public bool IsInSameRaidWith(Player p)
|
||||
{
|
||||
return p == this || (GetGroup() != null && GetGroup() == p.GetGroup());
|
||||
}
|
||||
|
||||
public void UninviteFromGroup()
|
||||
{
|
||||
Group group = GetGroupInvite();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
group.RemoveInvite(this);
|
||||
|
||||
if (group.GetMembersCount() <= 1) // group has just 1 member => disband
|
||||
{
|
||||
if (group.IsCreated())
|
||||
group.Disband(true);
|
||||
else
|
||||
group.RemoveAllInvites();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFromGroup(RemoveMethod method = RemoveMethod.Default) { RemoveFromGroup(GetGroup(), GetGUID(), method); }
|
||||
public static void RemoveFromGroup(Group group, ObjectGuid guid, RemoveMethod method = RemoveMethod.Default, ObjectGuid kicker = default(ObjectGuid), string reason = null)
|
||||
{
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
group.RemoveMember(guid, method, kicker, reason);
|
||||
}
|
||||
|
||||
void SendUpdateToOutOfRangeGroupMembers()
|
||||
{
|
||||
if (m_groupUpdateMask == GroupUpdateFlags.None)
|
||||
return;
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
group.UpdatePlayerOutOfRange(this);
|
||||
|
||||
m_groupUpdateMask = GroupUpdateFlags.None;
|
||||
|
||||
Pet pet = GetPet();
|
||||
if (pet)
|
||||
pet.ResetGroupUpdateFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
* 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.Groups;
|
||||
using Game.Guilds;
|
||||
using Game.Maps;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public override void SetMap(Map map)
|
||||
{
|
||||
base.SetMap(map);
|
||||
}
|
||||
|
||||
public Difficulty GetDifficultyID(MapRecord mapEntry)
|
||||
{
|
||||
if (!mapEntry.IsRaid())
|
||||
return m_dungeonDifficulty;
|
||||
|
||||
MapDifficultyRecord defaultDifficulty = Global.DB2Mgr.GetDefaultMapDifficulty(mapEntry.Id);
|
||||
if (defaultDifficulty == null)
|
||||
return m_legacyRaidDifficulty;
|
||||
|
||||
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(defaultDifficulty.DifficultyID);
|
||||
if (difficulty == null || difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy))
|
||||
return m_legacyRaidDifficulty;
|
||||
|
||||
return m_raidDifficulty;
|
||||
}
|
||||
public Difficulty GetDungeonDifficultyID() { return m_dungeonDifficulty; }
|
||||
public Difficulty GetRaidDifficultyID() { return m_raidDifficulty; }
|
||||
public Difficulty GetLegacyRaidDifficultyID() { return m_legacyRaidDifficulty; }
|
||||
public void SetDungeonDifficultyID(Difficulty dungeon_difficulty) { m_dungeonDifficulty = dungeon_difficulty; }
|
||||
public void SetRaidDifficultyID(Difficulty raid_difficulty) { m_raidDifficulty = raid_difficulty; }
|
||||
public void SetLegacyRaidDifficultyID(Difficulty raid_difficulty) { m_legacyRaidDifficulty = raid_difficulty; }
|
||||
|
||||
public static Difficulty CheckLoadedDungeonDifficultyID(Difficulty difficulty)
|
||||
{
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null)
|
||||
return Difficulty.Normal;
|
||||
|
||||
if (difficultyEntry.InstanceType != MapTypes.Instance)
|
||||
return Difficulty.Normal;
|
||||
|
||||
if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect))
|
||||
return Difficulty.Normal;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
public static Difficulty CheckLoadedRaidDifficultyID(Difficulty difficulty)
|
||||
{
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null)
|
||||
return Difficulty.NormalRaid;
|
||||
|
||||
if (difficultyEntry.InstanceType != MapTypes.Raid)
|
||||
return Difficulty.NormalRaid;
|
||||
|
||||
if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy))
|
||||
return Difficulty.NormalRaid;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
public static Difficulty CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty)
|
||||
{
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null)
|
||||
return Difficulty.Raid10N;
|
||||
|
||||
if (difficultyEntry.InstanceType != MapTypes.Raid)
|
||||
return Difficulty.Raid10N;
|
||||
|
||||
if (!difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.CanSelect) || !difficultyEntry.Flags.HasAnyFlag(DifficultyFlags.Legacy))
|
||||
return Difficulty.Raid10N;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public void SendRaidGroupOnlyMessage(RaidGroupReason reason, int delay)
|
||||
{
|
||||
RaidGroupOnly raidGroupOnly = new RaidGroupOnly();
|
||||
raidGroupOnly.Delay = delay;
|
||||
raidGroupOnly.Reason = reason;
|
||||
|
||||
SendPacket(raidGroupOnly);
|
||||
}
|
||||
|
||||
void UpdateArea(uint newArea)
|
||||
{
|
||||
// FFA_PVP flags are area and not zone id dependent
|
||||
// so apply them accordingly
|
||||
m_areaUpdateId = newArea;
|
||||
|
||||
AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(newArea);
|
||||
pvpInfo.IsInFFAPvPArea = area != null && area.Flags[0].HasAnyFlag(AreaFlags.Arena);
|
||||
UpdatePvPState(true);
|
||||
|
||||
UpdateAreaDependentAuras(newArea);
|
||||
UpdateAreaAndZonePhase();
|
||||
|
||||
// previously this was in UpdateZone (but after UpdateArea) so nothing will break
|
||||
pvpInfo.IsInNoPvPArea = false;
|
||||
if (area != null && area.IsSanctuary()) // in sanctuary
|
||||
{
|
||||
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary);
|
||||
pvpInfo.IsInNoPvPArea = true;
|
||||
if (duel == null)
|
||||
CombatStopWithPets();
|
||||
}
|
||||
else
|
||||
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.Sanctuary);
|
||||
|
||||
AreaFlags areaRestFlag = (GetTeam() == Team.Alliance) ? AreaFlags.RestZoneAlliance : AreaFlags.RestZoneHorde;
|
||||
if (area != null && area.Flags[0].HasAnyFlag(areaRestFlag))
|
||||
_restMgr.SetRestFlag(RestFlag.FactionArea);
|
||||
else
|
||||
_restMgr.RemoveRestFlag(RestFlag.FactionArea);
|
||||
}
|
||||
|
||||
public void UpdateZone(uint newZone, uint newArea)
|
||||
{
|
||||
if (m_zoneUpdateId != newZone)
|
||||
{
|
||||
Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
|
||||
Global.OutdoorPvPMgr.HandlePlayerEnterZone(this, newZone);
|
||||
Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
|
||||
Global.BattleFieldMgr.HandlePlayerEnterZone(this, newZone);
|
||||
SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange...
|
||||
Guild guild = GetGuild();
|
||||
if (guild)
|
||||
guild.UpdateMemberData(this, GuildMemberData.ZoneId, newZone);
|
||||
}
|
||||
|
||||
// group update
|
||||
if (GetGroup())
|
||||
{
|
||||
SetGroupUpdateFlag(GroupUpdateFlags.Full);
|
||||
|
||||
Pet pet = GetPet();
|
||||
if (pet)
|
||||
pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Full);
|
||||
}
|
||||
|
||||
m_zoneUpdateId = newZone;
|
||||
m_zoneUpdateTimer = 1 * Time.InMilliseconds;
|
||||
|
||||
// zone changed, so area changed as well, update it
|
||||
UpdateArea(newArea);
|
||||
|
||||
AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(newZone);
|
||||
if (zone == null)
|
||||
return;
|
||||
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.Weather))
|
||||
GetMap().GetOrGenerateZoneDefaultWeather(newZone);
|
||||
|
||||
GetMap().SendZoneDynamicInfo(newZone, this);
|
||||
|
||||
Global.ScriptMgr.OnPlayerUpdateZone(this, newZone, newArea);
|
||||
|
||||
// in PvP, any not controlled zone (except zone.team == 6, default case)
|
||||
// in PvE, only opposition team capital
|
||||
switch ((AreaTeams)zone.FactionGroupMask)
|
||||
{
|
||||
case AreaTeams.Ally:
|
||||
pvpInfo.IsInHostileArea = GetTeam() != Team.Alliance && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital));
|
||||
break;
|
||||
case AreaTeams.Horde:
|
||||
pvpInfo.IsInHostileArea = GetTeam() != Team.Horde && (Global.WorldMgr.IsPvPRealm() || zone.Flags[0].HasAnyFlag(AreaFlags.Capital));
|
||||
break;
|
||||
case AreaTeams.None:
|
||||
// overwrite for Battlegrounds, maybe batter some zone flags but current known not 100% fit to this
|
||||
pvpInfo.IsInHostileArea = Global.WorldMgr.IsPvPRealm() || InBattleground() || zone.Flags[0].HasAnyFlag(AreaFlags.Wintergrasp);
|
||||
break;
|
||||
default: // 6 in fact
|
||||
pvpInfo.IsInHostileArea = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Treat players having a quest flagging for PvP as always in hostile area
|
||||
pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest();
|
||||
|
||||
if (zone.Flags[0].HasAnyFlag(AreaFlags.Capital)) // Is in a capital city
|
||||
{
|
||||
if (!pvpInfo.IsInHostileArea || zone.IsSanctuary())
|
||||
_restMgr.SetRestFlag(RestFlag.City);
|
||||
pvpInfo.IsInNoPvPArea = true;
|
||||
}
|
||||
else
|
||||
_restMgr.RemoveRestFlag(RestFlag.City);
|
||||
|
||||
UpdatePvPState();
|
||||
|
||||
// remove items with area/map limitations (delete only for alive player to allow back in ghost mode)
|
||||
// if player resurrected at teleport this will be applied in resurrect code
|
||||
if (IsAlive())
|
||||
DestroyZoneLimitedItem(true, newZone);
|
||||
|
||||
// check some item equip limitations (in result lost CanTitanGrip at talent reset, for example)
|
||||
AutoUnequipOffhandIfNeed();
|
||||
|
||||
// recent client version not send leave/join channel packets for built-in local channels
|
||||
UpdateLocalChannels(newZone);
|
||||
|
||||
UpdateZoneDependentAuras(newZone);
|
||||
|
||||
UpdateAreaAndZonePhase();
|
||||
}
|
||||
|
||||
public InstanceBind GetBoundInstance(uint mapid, Difficulty difficulty, bool withExpired = false)
|
||||
{
|
||||
// some instances only have one difficulty
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(mapid, ref difficulty);
|
||||
if (mapDiff == null)
|
||||
return null;
|
||||
|
||||
var bind = m_boundInstances[(int)difficulty].LookupByKey(mapid);
|
||||
if (bind != null)
|
||||
if (bind.extendState != 0 || withExpired)
|
||||
return bind;
|
||||
|
||||
return null;
|
||||
}
|
||||
public Dictionary<uint, InstanceBind> GetBoundInstances(Difficulty difficulty) { return m_boundInstances[(int)difficulty]; }
|
||||
|
||||
public InstanceSave GetInstanceSave(uint mapid)
|
||||
{
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid);
|
||||
InstanceBind pBind = GetBoundInstance(mapid, GetDifficultyID(mapEntry));
|
||||
InstanceSave pSave = pBind?.save;
|
||||
if (pBind == null || !pBind.perm)
|
||||
{
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
{
|
||||
InstanceBind groupBind = group.GetBoundInstance(GetDifficultyID(mapEntry), mapid);
|
||||
if (groupBind != null)
|
||||
pSave = groupBind.save;
|
||||
}
|
||||
}
|
||||
|
||||
return pSave;
|
||||
}
|
||||
|
||||
public void UnbindInstance(uint mapid, Difficulty difficulty, bool unload = false)
|
||||
{
|
||||
var bound = m_boundInstances[(int)difficulty].LookupByKey(mapid);
|
||||
if (bound != null)
|
||||
{
|
||||
if (!unload)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
|
||||
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, bound.save.GetInstanceId());
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
if (bound.perm)
|
||||
GetSession().SendCalendarRaidLockout(bound.save, false);
|
||||
|
||||
bound.save.RemovePlayer(this); // save can become invalid
|
||||
m_boundInstances[(int)difficulty].Remove(mapid);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnbindInstance(KeyValuePair<uint, InstanceBind> pair, Difficulty difficulty, bool unload)
|
||||
{
|
||||
if (pair.Value != null)
|
||||
{
|
||||
if (!unload)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
|
||||
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, pair.Value.save.GetInstanceId());
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
if (pair.Value.perm)
|
||||
GetSession().SendCalendarRaidLockout(pair.Value.save, false);
|
||||
|
||||
pair.Value.save.RemovePlayer(this); // save can become invalid
|
||||
m_boundInstances[(int)difficulty].Remove(pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public InstanceBind BindToInstance(InstanceSave save, bool permanent, BindExtensionState extendState = BindExtensionState.Normal, bool load = false)
|
||||
{
|
||||
if (save != null)
|
||||
{
|
||||
InstanceBind bind = new InstanceBind();
|
||||
if (m_boundInstances[(int)save.GetDifficultyID()].ContainsKey(save.GetMapId()))
|
||||
bind = m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()];
|
||||
|
||||
if (extendState == BindExtensionState.Keep) // special flag, keep the player's current extend state when updating for new boss down
|
||||
{
|
||||
if (save == bind.save)
|
||||
extendState = bind.extendState;
|
||||
else
|
||||
extendState = BindExtensionState.Normal;
|
||||
}
|
||||
|
||||
if (!load)
|
||||
{
|
||||
PreparedStatement stmt;
|
||||
if (bind.save != null)
|
||||
{
|
||||
// update the save when the group kills a boss
|
||||
if (permanent != bind.perm || save != bind.save || extendState != bind.extendState)
|
||||
{
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_INSTANCE);
|
||||
|
||||
stmt.AddValue(0, save.GetInstanceId());
|
||||
stmt.AddValue(1, permanent);
|
||||
stmt.AddValue(2, extendState);
|
||||
stmt.AddValue(3, GetGUID().GetCounter());
|
||||
stmt.AddValue(4, bind.save.GetInstanceId());
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_INSTANCE);
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, save.GetInstanceId());
|
||||
stmt.AddValue(2, permanent);
|
||||
stmt.AddValue(3, extendState);
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
if (bind.save != save)
|
||||
{
|
||||
if (bind.save != null)
|
||||
bind.save.RemovePlayer(this);
|
||||
save.AddPlayer(this);
|
||||
}
|
||||
|
||||
if (permanent)
|
||||
save.SetCanReset(false);
|
||||
|
||||
bind.save = save;
|
||||
bind.perm = permanent;
|
||||
bind.extendState = extendState;
|
||||
if (!load)
|
||||
Log.outDebug(LogFilter.Maps, "Player.BindToInstance: Player '{0}' ({1}) is now bound to map (ID: {2}, Instance {3}, Difficulty {4})", GetName(), GetGUID().ToString(), save.GetMapId(), save.GetInstanceId(), save.GetDifficultyID());
|
||||
|
||||
Global.ScriptMgr.OnPlayerBindToInstance(this, save.GetDifficultyID(), save.GetMapId(), permanent, extendState);
|
||||
|
||||
m_boundInstances[(int)save.GetDifficultyID()][save.GetMapId()] = bind;
|
||||
return bind;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void BindToInstance()
|
||||
{
|
||||
InstanceSave mapSave = Global.InstanceSaveMgr.GetInstanceSave(_pendingBindId);
|
||||
if (mapSave == null) //it seems sometimes mapSave is NULL, but I did not check why
|
||||
return;
|
||||
|
||||
InstanceSaveCreated data = new InstanceSaveCreated();
|
||||
data.Gm = IsGameMaster();
|
||||
SendPacket(data);
|
||||
if (!IsGameMaster())
|
||||
{
|
||||
BindToInstance(mapSave, true, BindExtensionState.Keep);
|
||||
GetSession().SendCalendarRaidLockout(mapSave, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPendingBind(uint instanceId, uint bindTimer)
|
||||
{
|
||||
_pendingBindId = instanceId;
|
||||
_pendingBindTimer = bindTimer;
|
||||
}
|
||||
|
||||
public void SendRaidInfo()
|
||||
{
|
||||
InstanceInfoPkt instanceInfo = new InstanceInfoPkt();
|
||||
|
||||
long now = Time.UnixTime;
|
||||
for (byte i = 0; i < (int)Difficulty.Max; ++i)
|
||||
{
|
||||
foreach (var pair in m_boundInstances[i])
|
||||
{
|
||||
InstanceBind bind = pair.Value;
|
||||
if (bind.perm)
|
||||
{
|
||||
InstanceSave save = pair.Value.save;
|
||||
|
||||
InstanceLockInfos lockInfos;
|
||||
|
||||
lockInfos.InstanceID = save.GetInstanceId();
|
||||
lockInfos.MapID = save.GetMapId();
|
||||
lockInfos.DifficultyID = (uint)save.GetDifficultyID();
|
||||
if (bind.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;
|
||||
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 = bind.extendState != BindExtensionState.Expired;
|
||||
lockInfos.Extended = bind.extendState == BindExtensionState.Extended;
|
||||
|
||||
instanceInfo.LockList.Add(lockInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SendPacket(instanceInfo);
|
||||
}
|
||||
|
||||
public bool Satisfy(AccessRequirement ar, uint target_map, bool report = false)
|
||||
{
|
||||
if (!IsGameMaster() && ar != null)
|
||||
{
|
||||
byte LevelMin = 0;
|
||||
byte LevelMax = 0;
|
||||
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(target_map);
|
||||
if (mapEntry == null)
|
||||
return false;
|
||||
|
||||
if (!WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreLevel))
|
||||
{
|
||||
if (ar.levelMin != 0 && getLevel() < ar.levelMin)
|
||||
LevelMin = ar.levelMin;
|
||||
if (ar.levelMax != 0 && getLevel() > ar.levelMax)
|
||||
LevelMax = ar.levelMax;
|
||||
}
|
||||
|
||||
uint missingItem = 0;
|
||||
if (ar.item != 0)
|
||||
{
|
||||
if (!HasItemCount(ar.item) &&
|
||||
(ar.item2 == 0 || !HasItemCount(ar.item2)))
|
||||
missingItem = ar.item;
|
||||
}
|
||||
else if (ar.item2 != 0 && !HasItemCount(ar.item2))
|
||||
missingItem = ar.item2;
|
||||
|
||||
if (Global.DisableMgr.IsDisabledFor(DisableType.Map, target_map, this))
|
||||
{
|
||||
GetSession().SendNotification("{0}", Global.ObjectMgr.GetCypherString(CypherStrings.InstanceClosed));
|
||||
return false;
|
||||
}
|
||||
|
||||
uint missingQuest = 0;
|
||||
if (GetTeam() == Team.Alliance && ar.quest_A != 0 && !GetQuestRewardStatus(ar.quest_A))
|
||||
missingQuest = ar.quest_A;
|
||||
else if (GetTeam() == Team.Horde && ar.quest_H != 0 && !GetQuestRewardStatus(ar.quest_H))
|
||||
missingQuest = ar.quest_H;
|
||||
|
||||
uint missingAchievement = 0;
|
||||
Player leader = this;
|
||||
ObjectGuid leaderGuid = GetGroup() != null ? GetGroup().GetLeaderGUID() : GetGUID();
|
||||
if (leaderGuid != GetGUID())
|
||||
leader = Global.ObjAccessor.FindPlayer(leaderGuid);
|
||||
|
||||
if (ar.achievement != 0)
|
||||
if (leader == null || !leader.HasAchieved(ar.achievement))
|
||||
missingAchievement = ar.achievement;
|
||||
|
||||
Difficulty target_difficulty = GetDifficultyID(mapEntry);
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(target_map, ref target_difficulty);
|
||||
if (LevelMin != 0 || LevelMax != 0 || missingItem != 0 || missingQuest != 0 || missingAchievement != 0)
|
||||
{
|
||||
if (report)
|
||||
{
|
||||
if (missingQuest != 0 && !string.IsNullOrEmpty(ar.questFailedText))
|
||||
SendSysMessage("{0}", ar.questFailedText);
|
||||
else if (mapDiff.Message_lang.HasString(Global.WorldMgr.GetDefaultDbcLocale())) // if (missingAchievement) covered by this case
|
||||
SendTransferAborted(target_map, TransferAbortReason.Difficulty, (byte)target_difficulty);
|
||||
else if (missingItem != 0)
|
||||
GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequiredAndItem), LevelMin, Global.ObjectMgr.GetItemTemplate(missingItem).GetName());
|
||||
else if (LevelMin != 0)
|
||||
GetSession().SendNotification(Global.ObjectMgr.GetCypherString(CypherStrings.LevelMinrequired), LevelMin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInstanceLoginGameMasterException()
|
||||
{
|
||||
if (!CanBeGameMaster())
|
||||
return false;
|
||||
|
||||
SendSysMessage(CypherStrings.InstanceLoginGamemasterException);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckInstanceValidity(bool isLogin)
|
||||
{
|
||||
// game masters' instances are always valid
|
||||
if (IsGameMaster())
|
||||
return true;
|
||||
|
||||
// non-instances are always valid
|
||||
Map map = GetMap();
|
||||
if (!map || !map.IsDungeon())
|
||||
return true;
|
||||
|
||||
// raid instances require the player to be in a raid group to be valid
|
||||
if (map.IsRaid() && !WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid))
|
||||
if (!GetGroup() || !GetGroup().isRaidGroup())
|
||||
return false;
|
||||
|
||||
Group group = GetGroup();
|
||||
if (group)
|
||||
{
|
||||
// check if player's group is bound to this instance
|
||||
InstanceBind bind = group.GetBoundInstance(map.GetDifficultyID(), map.GetId());
|
||||
if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId())
|
||||
return false;
|
||||
|
||||
var players = map.GetPlayers();
|
||||
if (!players.Empty())
|
||||
foreach (var otherPlayer in players)
|
||||
{
|
||||
if (otherPlayer.IsGameMaster())
|
||||
continue;
|
||||
if (!otherPlayer.m_InstanceValid) // ignore players that currently have a homebind timer active
|
||||
continue;
|
||||
if (group != otherPlayer.GetGroup())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// instance is invalid if we are not grouped and there are other players
|
||||
if (map.GetPlayersCountExceptGMs() > 1)
|
||||
return false;
|
||||
|
||||
// check if the player is bound to this instance
|
||||
InstanceBind bind = GetBoundInstance(map.GetId(), map.GetDifficultyID());
|
||||
if (bind == null || bind.save == null || bind.save.GetInstanceId() != map.GetInstanceId())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckInstanceCount(uint instanceId)
|
||||
{
|
||||
if (_instanceResetTimes.Count < WorldConfig.GetIntValue(WorldCfg.MaxInstancesPerHour))
|
||||
return true;
|
||||
return _instanceResetTimes.ContainsKey(instanceId);
|
||||
}
|
||||
|
||||
public void AddInstanceEnterTime(uint instanceId, long enterTime)
|
||||
{
|
||||
if (!_instanceResetTimes.ContainsKey(instanceId))
|
||||
_instanceResetTimes.Add(instanceId, enterTime + Time.Hour);
|
||||
}
|
||||
|
||||
public void SendDungeonDifficulty(int forcedDifficulty = -1)
|
||||
{
|
||||
DungeonDifficultySet dungeonDifficultySet = new DungeonDifficultySet();
|
||||
dungeonDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)GetDungeonDifficultyID() : forcedDifficulty;
|
||||
SendPacket(dungeonDifficultySet);
|
||||
}
|
||||
|
||||
public void SendRaidDifficulty(bool legacy, int forcedDifficulty = -1)
|
||||
{
|
||||
RaidDifficultySet raidDifficultySet = new RaidDifficultySet();
|
||||
raidDifficultySet.DifficultyID = forcedDifficulty == -1 ? (int)(legacy ? GetLegacyRaidDifficultyID() : GetRaidDifficultyID()) : forcedDifficulty;
|
||||
raidDifficultySet.Legacy = legacy;
|
||||
SendPacket(raidDifficultySet);
|
||||
}
|
||||
|
||||
public void SendResetFailedNotify(uint mapid)
|
||||
{
|
||||
SendPacket(new ResetFailedNotify());
|
||||
}
|
||||
|
||||
// Reset all solo instances and optionally send a message on success for each
|
||||
public void ResetInstances(InstanceResetMethod method, bool isRaid, bool isLegacy)
|
||||
{
|
||||
// method can be INSTANCE_RESET_ALL, INSTANCE_RESET_CHANGE_DIFFICULTY, INSTANCE_RESET_GROUP_JOIN
|
||||
|
||||
// we assume that when the difficulty changes, all instances that can be reset will be
|
||||
Difficulty diff = GetDungeonDifficultyID();
|
||||
if (isRaid)
|
||||
{
|
||||
if (!isLegacy)
|
||||
diff = GetRaidDifficultyID();
|
||||
else
|
||||
diff = GetLegacyRaidDifficultyID();
|
||||
}
|
||||
|
||||
foreach (var pair in m_boundInstances[(int)diff].ToList())
|
||||
{
|
||||
InstanceSave p = pair.Value.save;
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(pair.Key);
|
||||
if (entry == null || entry.IsRaid() != isRaid || !p.CanReset())
|
||||
continue;
|
||||
|
||||
if (method == InstanceResetMethod.All)
|
||||
{
|
||||
// the "reset all instances" method can only reset normal maps
|
||||
if (entry.InstanceType == MapTypes.Raid || diff == Difficulty.Heroic)
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the map is loaded, reset it
|
||||
Map map = Global.MapMgr.FindMap(p.GetMapId(), p.GetInstanceId());
|
||||
if (map != null && map.IsDungeon())
|
||||
if (!map.ToInstanceMap().Reset(method))
|
||||
continue;
|
||||
|
||||
// since this is a solo instance there should not be any players inside
|
||||
if (method == InstanceResetMethod.All || method == InstanceResetMethod.ChangeDifficulty)
|
||||
SendResetInstanceSuccess(p.GetMapId());
|
||||
|
||||
p.DeleteFromDB();
|
||||
m_boundInstances[(int)diff].Remove(pair.Key);
|
||||
|
||||
// the following should remove the instance save from the manager and delete it as well
|
||||
p.RemovePlayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendResetInstanceSuccess(uint MapId)
|
||||
{
|
||||
InstanceReset data = new InstanceReset();
|
||||
data.MapID = MapId;
|
||||
SendPacket(data);
|
||||
}
|
||||
|
||||
public void SendResetInstanceFailed(ResetFailedReason reason, uint MapId)
|
||||
{
|
||||
InstanceResetFailed data = new InstanceResetFailed();
|
||||
data.MapID = MapId;
|
||||
data.ResetFailedReason = reason;
|
||||
SendPacket(data);
|
||||
}
|
||||
|
||||
public void SendTransferAborted(uint mapid, TransferAbortReason reason, byte arg = 0)
|
||||
{
|
||||
TransferAborted transferAborted = new TransferAborted();
|
||||
transferAborted.MapID = mapid;
|
||||
transferAborted.Arg = arg;
|
||||
transferAborted.TransfertAbort = reason;
|
||||
SendPacket(transferAborted);
|
||||
}
|
||||
|
||||
public void SendInstanceResetWarning(uint mapid, Difficulty difficulty, uint time, bool welcome)
|
||||
{
|
||||
// type of warning, based on the time remaining until reset
|
||||
InstanceResetWarningType type;
|
||||
if (welcome)
|
||||
type = InstanceResetWarningType.Welcome;
|
||||
else if (time > 21600)
|
||||
type = InstanceResetWarningType.Welcome;
|
||||
else if (time > 3600)
|
||||
type = InstanceResetWarningType.WarningHours;
|
||||
else if (time > 300)
|
||||
type = InstanceResetWarningType.WarningMin;
|
||||
else
|
||||
type = InstanceResetWarningType.WarningMinSoon;
|
||||
|
||||
RaidInstanceMessage raidInstanceMessage = new RaidInstanceMessage();
|
||||
raidInstanceMessage.Type = type;
|
||||
raidInstanceMessage.MapID = mapid;
|
||||
raidInstanceMessage.DifficultyID = difficulty;
|
||||
|
||||
InstanceBind bind = GetBoundInstance(mapid, difficulty);
|
||||
if (bind != null)
|
||||
raidInstanceMessage.Locked = bind.perm;
|
||||
else
|
||||
raidInstanceMessage.Locked = false;
|
||||
raidInstanceMessage.Extended = false;
|
||||
SendPacket(raidInstanceMessage);
|
||||
}
|
||||
|
||||
public override void UpdateUnderwaterState(Map m, float x, float y, float z)
|
||||
{
|
||||
LiquidData liquid_status;
|
||||
ZLiquidStatus res = m.getLiquidStatus(x, y, z, MapConst.MapAllLiquidTypes, out liquid_status);
|
||||
if (res == 0)
|
||||
{
|
||||
m_MirrorTimerFlags &= ~(PlayerUnderwaterState.InWater | PlayerUnderwaterState.InLava | PlayerUnderwaterState.InSlime | PlayerUnderwaterState.InDarkWater);
|
||||
if (_lastLiquid != null && _lastLiquid.SpellID != 0)
|
||||
RemoveAurasDueToSpell(_lastLiquid.SpellID);
|
||||
|
||||
_lastLiquid = null;
|
||||
return;
|
||||
}
|
||||
uint liqEntry = liquid_status.entry;
|
||||
if (liqEntry != 0)
|
||||
{
|
||||
LiquidTypeRecord liquid = CliDB.LiquidTypeStorage.LookupByKey(liqEntry);
|
||||
if (_lastLiquid != null && _lastLiquid.SpellID != 0 && _lastLiquid != liquid)
|
||||
RemoveAurasDueToSpell(_lastLiquid.SpellID);
|
||||
|
||||
if (liquid != null && liquid.SpellID != 0)
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater))
|
||||
{
|
||||
if (!HasAura(liquid.SpellID))
|
||||
CastSpell(this, liquid.SpellID, true);
|
||||
}
|
||||
else
|
||||
RemoveAurasDueToSpell(liquid.SpellID);
|
||||
}
|
||||
|
||||
_lastLiquid = liquid;
|
||||
}
|
||||
else if (_lastLiquid != null && _lastLiquid.SpellID != 0)
|
||||
{
|
||||
RemoveAurasDueToSpell(_lastLiquid.SpellID);
|
||||
_lastLiquid = null;
|
||||
}
|
||||
|
||||
|
||||
// All liquids type - check under water position
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeWater | MapConst.MapLiquidTypeOcean | MapConst.MapLiquidTypeMagma | MapConst.MapLiquidTypeSlime))
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater))
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InWater;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InWater;
|
||||
}
|
||||
|
||||
// Allow travel in dark water on taxi or transport
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeDarkWater) && !IsInFlight() && GetTransport() == null)
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InDarkWater;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InDarkWater;
|
||||
|
||||
// in lava check, anywhere in lava level
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeMagma))
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk))
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InLava;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InLava;
|
||||
}
|
||||
// in slime check, anywhere in slime level
|
||||
if (liquid_status.type_flags.HasAnyFlag<uint>(MapConst.MapLiquidTypeSlime))
|
||||
{
|
||||
if (res.HasAnyFlag(ZLiquidStatus.UnderWater | ZLiquidStatus.InWater | ZLiquidStatus.WaterWalk))
|
||||
m_MirrorTimerFlags |= PlayerUnderwaterState.InSlime;
|
||||
else
|
||||
m_MirrorTimerFlags &= ~PlayerUnderwaterState.InSlime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,753 @@
|
||||
/*
|
||||
* 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.Arenas;
|
||||
using Game.BattleFields;
|
||||
using Game.BattleGrounds;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using Game.PvP;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
//PvP
|
||||
public void UpdateHonorFields()
|
||||
{
|
||||
// called when rewarding honor and at each save
|
||||
long now = Time.UnixTime;
|
||||
long today = (Time.UnixTime / Time.Day) * Time.Day;
|
||||
|
||||
if (m_lastHonorUpdateTime < today)
|
||||
{
|
||||
long yesterday = today - Time.Day;
|
||||
|
||||
// update yesterday's contribution
|
||||
if (m_lastHonorUpdateTime >= yesterday)
|
||||
{
|
||||
// this is the first update today, reset today's contribution
|
||||
ushort killsToday = GetUInt16Value(PlayerFields.Kills, 0);
|
||||
SetUInt16Value(PlayerFields.Kills, 0, 0);
|
||||
SetUInt16Value(PlayerFields.Kills, 1, killsToday);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// no honor/kills yesterday or today, reset
|
||||
SetUInt32Value(PlayerFields.Kills, 0);
|
||||
}
|
||||
}
|
||||
|
||||
m_lastHonorUpdateTime = now;
|
||||
}
|
||||
public bool RewardHonor(Unit victim, uint groupsize, int honor = -1, bool pvptoken = false)
|
||||
{
|
||||
// do not reward honor in arenas, but enable onkill spellproc
|
||||
if (InArena())
|
||||
{
|
||||
if (!victim || victim == this || !victim.IsTypeId(TypeId.Player))
|
||||
return false;
|
||||
|
||||
if (GetBGTeam() == victim.ToPlayer().GetBGTeam())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'Inactive' this aura prevents the player from gaining honor points and BattlegroundTokenizer
|
||||
if (HasAura(BattlegroundConst.SpellAuraPlayerInactive))
|
||||
return false;
|
||||
|
||||
ObjectGuid victim_guid = ObjectGuid.Empty;
|
||||
uint victim_rank = 0;
|
||||
|
||||
// need call before fields update to have chance move yesterday data to appropriate fields before today data change.
|
||||
UpdateHonorFields();
|
||||
|
||||
// do not reward honor in arenas, but return true to enable onkill spellproc
|
||||
if (InBattleground() && GetBattleground() && GetBattleground().isArena())
|
||||
return true;
|
||||
|
||||
// Promote to float for calculations
|
||||
float honor_f = honor;
|
||||
|
||||
if (honor_f <= 0)
|
||||
{
|
||||
if (!victim || victim == this || victim.HasAuraType(AuraType.NoPvpCredit))
|
||||
return false;
|
||||
|
||||
victim_guid = victim.GetGUID();
|
||||
Player plrVictim = victim.ToPlayer();
|
||||
if (plrVictim)
|
||||
{
|
||||
if (GetTeam() == plrVictim.GetTeam() && !Global.WorldMgr.IsFFAPvPRealm())
|
||||
return false;
|
||||
|
||||
byte k_level = (byte)getLevel();
|
||||
byte k_grey = (byte)Formulas.GetGrayLevel(k_level);
|
||||
byte v_level = (byte)victim.GetLevelForTarget(this);
|
||||
|
||||
if (v_level <= k_grey)
|
||||
return false;
|
||||
|
||||
// PLAYER_CHOSEN_TITLE VALUES DESCRIPTION
|
||||
// [0] Just name
|
||||
// [1..14] Alliance honor titles and player name
|
||||
// [15..28] Horde honor titles and player name
|
||||
// [29..38] Other title and player name
|
||||
// [39+] Nothing
|
||||
uint victim_title = victim.GetUInt32Value(PlayerFields.ChosenTitle);
|
||||
// Get Killer titles, CharTitlesEntry.bit_index
|
||||
// Ranks:
|
||||
// title[1..14] . rank[5..18]
|
||||
// title[15..28] . rank[5..18]
|
||||
// title[other] . 0
|
||||
if (victim_title == 0)
|
||||
victim_guid.Clear(); // Don't show HK: <rank> message, only log.
|
||||
else if (victim_title < 15)
|
||||
victim_rank = victim_title + 4;
|
||||
else if (victim_title < 29)
|
||||
victim_rank = victim_title - 14 + 4;
|
||||
else
|
||||
victim_guid.Clear(); // Don't show HK: <rank> message, only log.
|
||||
|
||||
honor_f = (float)Math.Ceiling(Formulas.hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
|
||||
|
||||
// count the number of playerkills in one day
|
||||
ApplyModUInt16Value(PlayerFields.Kills, 0, 1, true);
|
||||
// and those in a lifetime
|
||||
ApplyModUInt32Value(PlayerFields.LifetimeHonorableKills, 1, true);
|
||||
UpdateCriteria(CriteriaTypes.EarnHonorableKill);
|
||||
UpdateCriteria(CriteriaTypes.HkClass, (uint)victim.GetClass());
|
||||
UpdateCriteria(CriteriaTypes.HkRace, (uint)victim.GetRace());
|
||||
UpdateCriteria(CriteriaTypes.HonorableKillAtArea, GetAreaId());
|
||||
UpdateCriteria(CriteriaTypes.HonorableKill, 1, 0, 0, victim);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!victim.ToCreature().IsRacialLeader())
|
||||
return false;
|
||||
|
||||
honor_f = 100.0f; // ??? need more info
|
||||
victim_rank = 19; // HK: Leader
|
||||
}
|
||||
}
|
||||
|
||||
if (victim != null)
|
||||
{
|
||||
if (groupsize > 1)
|
||||
honor_f /= groupsize;
|
||||
|
||||
// apply honor multiplier from aura (not stacking-get highest)
|
||||
MathFunctions.AddPct(ref honor_f, GetMaxPositiveAuraModifier(AuraType.ModHonorGainPct));
|
||||
honor_f += _restMgr.GetRestBonusFor(RestTypes.Honor, (uint)honor_f);
|
||||
}
|
||||
|
||||
honor_f *= WorldConfig.GetFloatValue(WorldCfg.RateHonor);
|
||||
// Back to int now
|
||||
honor = (int)honor_f;
|
||||
// honor - for show honor points in log
|
||||
// victim_guid - for show victim name in log
|
||||
// victim_rank [1..4] HK: <dishonored rank>
|
||||
// victim_rank [5..19] HK: <alliance\horde rank>
|
||||
// victim_rank [0, 20+] HK: <>
|
||||
PvPCredit data = new PvPCredit();
|
||||
data.Honor = honor;
|
||||
data.OriginalHonor = honor;
|
||||
data.Target = victim_guid;
|
||||
data.Rank = victim_rank;
|
||||
|
||||
SendPacket(data);
|
||||
|
||||
AddHonorXP((uint)honor);
|
||||
|
||||
if (InBattleground() && honor > 0)
|
||||
{
|
||||
Battleground bg = GetBattleground();
|
||||
if (bg != null)
|
||||
{
|
||||
bg.UpdatePlayerScore(this, ScoreType.BonusHonor, (uint)honor, false); //false: prevent looping
|
||||
}
|
||||
}
|
||||
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.PvpTokenEnable) && pvptoken)
|
||||
{
|
||||
if (!victim || victim == this || victim.HasAuraType(AuraType.NoPvpCredit))
|
||||
return true;
|
||||
|
||||
if (victim.IsTypeId(TypeId.Player))
|
||||
{
|
||||
// Check if allowed to receive it in current map
|
||||
int MapType = WorldConfig.GetIntValue(WorldCfg.PvpTokenMapType);
|
||||
if ((MapType == 1 && !InBattleground() && !IsFFAPvP())
|
||||
|| (MapType == 2 && !IsFFAPvP())
|
||||
|| (MapType == 3 && !InBattleground()))
|
||||
return true;
|
||||
|
||||
uint itemId = WorldConfig.GetUIntValue(WorldCfg.PvpTokenId);
|
||||
uint count = WorldConfig.GetUIntValue(WorldCfg.PvpTokenCount);
|
||||
|
||||
if (AddItem(itemId, count))
|
||||
SendSysMessage("You have been awarded a token for slaying another player.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _InitHonorLevelOnLoadFromDB(uint honor, uint honorLevel, uint prestigeLevel)
|
||||
{
|
||||
SetUInt32Value(PlayerFields.HonorLevel, honorLevel);
|
||||
SetUInt32Value(PlayerFields.Prestige, prestigeLevel);
|
||||
UpdateHonorNextLevel();
|
||||
|
||||
AddHonorXP(honor);
|
||||
if (CanPrestige())
|
||||
Prestige();
|
||||
}
|
||||
|
||||
void RewardPlayerWithRewardPack(uint rewardPackID)
|
||||
{
|
||||
RewardPlayerWithRewardPack(CliDB.RewardPackStorage.LookupByKey(rewardPackID));
|
||||
}
|
||||
|
||||
void RewardPlayerWithRewardPack(RewardPackRecord rewardPackEntry)
|
||||
{
|
||||
if (rewardPackEntry == null)
|
||||
return;
|
||||
|
||||
CharTitlesRecord charTitlesEntry = CliDB.CharTitlesStorage.LookupByKey(rewardPackEntry.TitleID);
|
||||
if (charTitlesEntry != null)
|
||||
SetTitle(charTitlesEntry);
|
||||
|
||||
ModifyMoney(rewardPackEntry.Money);
|
||||
var rewardPackXItems = Global.DB2Mgr.GetRewardPackItemsByRewardID(rewardPackEntry.Id);
|
||||
if (rewardPackXItems != null)
|
||||
{
|
||||
foreach (RewardPackXItemRecord rewardPackXItem in rewardPackXItems)
|
||||
AddItem(rewardPackXItem.ItemID, rewardPackXItem.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHonorXP(uint xp)
|
||||
{
|
||||
uint currentHonorXP = GetUInt32Value(PlayerFields.Honor);
|
||||
uint nextHonorLevelXP = GetUInt32Value(PlayerFields.HonorNextLevel);
|
||||
uint newHonorXP = currentHonorXP + xp;
|
||||
uint honorLevel = GetHonorLevel();
|
||||
|
||||
if (xp < 1 || getLevel() < PlayerConst.LevelMinHonor || IsMaxHonorLevelAndPrestige())
|
||||
return;
|
||||
|
||||
while (newHonorXP >= nextHonorLevelXP)
|
||||
{
|
||||
newHonorXP -= nextHonorLevelXP;
|
||||
|
||||
if (honorLevel < PlayerConst.MaxHonorLevel)
|
||||
SetHonorLevel((byte)(honorLevel + 1));
|
||||
|
||||
honorLevel = GetHonorLevel();
|
||||
nextHonorLevelXP = GetUInt32Value(PlayerFields.HonorNextLevel);
|
||||
}
|
||||
|
||||
SetUInt32Value(PlayerFields.Honor, IsMaxHonorLevelAndPrestige() ? 0 : newHonorXP);
|
||||
}
|
||||
|
||||
void SetHonorLevel(byte level)
|
||||
{
|
||||
byte oldHonorLevel = (byte)GetHonorLevel();
|
||||
byte prestige = (byte)GetPrestigeLevel();
|
||||
if (level == oldHonorLevel)
|
||||
return;
|
||||
|
||||
uint rewardPackID = Global.DB2Mgr.GetRewardPackIDForPvpRewardByHonorLevelAndPrestige(level, prestige);
|
||||
RewardPlayerWithRewardPack(rewardPackID);
|
||||
|
||||
SetUInt32Value(PlayerFields.HonorLevel, level);
|
||||
UpdateHonorNextLevel();
|
||||
|
||||
UpdateCriteria(CriteriaTypes.HonorLevelReached);
|
||||
|
||||
// This code is here because no link was found between those items and this reward condition in the db2 files.
|
||||
// Interesting CriteriaTree found: Tree ids: 51140, 51156 (criteria id 31773, modifier tree id 37759)
|
||||
if (level == 50 && prestige == 1)
|
||||
{
|
||||
if (GetTeam() == Team.Alliance)
|
||||
AddItem(138992, 1);
|
||||
else
|
||||
AddItem(138996, 1);
|
||||
}
|
||||
|
||||
if (CanPrestige())
|
||||
Prestige();
|
||||
}
|
||||
|
||||
public void Prestige()
|
||||
{
|
||||
SetUInt32Value(PlayerFields.Prestige, GetPrestigeLevel() + 1);
|
||||
SetUInt32Value(PlayerFields.HonorLevel, 1);
|
||||
UpdateHonorNextLevel();
|
||||
|
||||
UpdateCriteria(CriteriaTypes.PrestigeReached);
|
||||
}
|
||||
|
||||
public bool CanPrestige()
|
||||
{
|
||||
if (GetSession().GetExpansion() >= Expansion.Legion && getLevel() >= PlayerConst.LevelMinHonor && GetHonorLevel() >= PlayerConst.MaxHonorLevel && GetPrestigeLevel() < Global.DB2Mgr.GetMaxPrestige())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsMaxPrestige()
|
||||
{
|
||||
return GetPrestigeLevel() == Global.DB2Mgr.GetMaxPrestige();
|
||||
}
|
||||
|
||||
void UpdateHonorNextLevel()
|
||||
{
|
||||
uint prestige = Math.Min(16 - 1, GetPrestigeLevel());
|
||||
SetUInt32Value(PlayerFields.HonorNextLevel, (uint)CliDB.HonorLevelGameTable.GetRow(GetHonorLevel()).Prestige[prestige]);
|
||||
}
|
||||
|
||||
public uint GetPrestigeLevel() { return GetUInt32Value(PlayerFields.Prestige); }
|
||||
public uint GetHonorLevel() { return GetUInt32Value(PlayerFields.HonorLevel); }
|
||||
public bool IsMaxHonorLevelAndPrestige() { return IsMaxPrestige() && GetHonorLevel() == PlayerConst.MaxHonorLevel; }
|
||||
|
||||
|
||||
//BGs
|
||||
public Battleground GetBattleground()
|
||||
{
|
||||
if (GetBattlegroundId() == 0)
|
||||
return null;
|
||||
|
||||
return Global.BattlegroundMgr.GetBattleground(GetBattlegroundId(), m_bgData.bgTypeID);
|
||||
}
|
||||
|
||||
public bool InBattlegroundQueue()
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId != BattlegroundQueueTypeId.None)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public BattlegroundQueueTypeId GetBattlegroundQueueTypeId(uint index)
|
||||
{
|
||||
if (index < SharedConst.MaxPlayerBGQueues)
|
||||
return m_bgBattlegroundQueueID[index].bgQueueTypeId;
|
||||
|
||||
return BattlegroundQueueTypeId.None;
|
||||
}
|
||||
|
||||
public uint GetBattlegroundQueueIndex(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
return i;
|
||||
return SharedConst.MaxPlayerBGQueues;
|
||||
}
|
||||
|
||||
public bool IsInvitedForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
return m_bgBattlegroundQueueID[i].invitedToInstance != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool InBattlegroundQueueForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
return GetBattlegroundQueueIndex(bgQueueTypeId) < SharedConst.MaxPlayerBGQueues;
|
||||
}
|
||||
|
||||
public void SetBattlegroundId(uint val, BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
m_bgData.bgInstanceID = val;
|
||||
m_bgData.bgTypeID = bgTypeId;
|
||||
}
|
||||
|
||||
public uint AddBattlegroundQueueId(BattlegroundQueueTypeId val)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
{
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BattlegroundQueueTypeId.None || m_bgBattlegroundQueueID[i].bgQueueTypeId == val)
|
||||
{
|
||||
m_bgBattlegroundQueueID[i].bgQueueTypeId = val;
|
||||
m_bgBattlegroundQueueID[i].invitedToInstance = 0;
|
||||
m_bgBattlegroundQueueID[i].joinTime = Time.GetMSTime();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return SharedConst.MaxPlayerBGQueues;
|
||||
}
|
||||
|
||||
public bool HasFreeBattlegroundQueueId()
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == BattlegroundQueueTypeId.None)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveBattlegroundQueueId(BattlegroundQueueTypeId val)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
{
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == val)
|
||||
{
|
||||
m_bgBattlegroundQueueID[i].bgQueueTypeId = BattlegroundQueueTypeId.None;
|
||||
m_bgBattlegroundQueueID[i].invitedToInstance = 0;
|
||||
m_bgBattlegroundQueueID[i].joinTime = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInviteForBattlegroundQueueType(BattlegroundQueueTypeId bgQueueTypeId, uint instanceId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
m_bgBattlegroundQueueID[i].invitedToInstance = instanceId;
|
||||
}
|
||||
|
||||
public bool IsInvitedForBattlegroundInstance(uint instanceId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].invitedToInstance == instanceId)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public WorldLocation GetBattlegroundEntryPoint() { return m_bgData.joinPos; }
|
||||
|
||||
public bool InBattleground() { return m_bgData.bgInstanceID != 0; }
|
||||
public uint GetBattlegroundId() { return m_bgData.bgInstanceID; }
|
||||
public BattlegroundTypeId GetBattlegroundTypeId() { return m_bgData.bgTypeID; }
|
||||
|
||||
public uint GetBattlegroundQueueJoinTime(BattlegroundQueueTypeId bgQueueTypeId)
|
||||
{
|
||||
for (byte i = 0; i < SharedConst.MaxPlayerBGQueues; ++i)
|
||||
if (m_bgBattlegroundQueueID[i].bgQueueTypeId == bgQueueTypeId)
|
||||
return m_bgBattlegroundQueueID[i].joinTime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool CanUseBattlegroundObject(GameObject gameobject)
|
||||
{
|
||||
// It is possible to call this method with a null pointer, only skipping faction check.
|
||||
if (gameobject)
|
||||
{
|
||||
FactionTemplateRecord playerFaction = GetFactionTemplateEntry();
|
||||
FactionTemplateRecord faction = CliDB.FactionTemplateStorage.LookupByKey(gameobject.GetUInt32Value(GameObjectFields.Faction));
|
||||
|
||||
if (playerFaction != null && faction != null && !playerFaction.IsFriendlyTo(faction))
|
||||
return false;
|
||||
}
|
||||
|
||||
// BUG: sometimes when player clicks on flag in AB - client won't send gameobject_use, only gameobject_report_use packet
|
||||
// Note: Mount, stealth and invisibility will be removed when used
|
||||
return (!isTotalImmune() && // Damage immune
|
||||
!HasAura(BattlegroundConst.SpellRecentlyDroppedFlag) && // Still has recently held flag debuff
|
||||
IsAlive()); // Alive
|
||||
}
|
||||
|
||||
public bool CanCaptureTowerPoint()
|
||||
{
|
||||
return (!HasStealthAura() && // not stealthed
|
||||
!HasInvisibilityAura() && // not invisible
|
||||
IsAlive()); // live player
|
||||
}
|
||||
|
||||
public void SetBattlegroundEntryPoint()
|
||||
{
|
||||
// Taxi path store
|
||||
if (!m_taxi.empty())
|
||||
{
|
||||
m_bgData.mountSpell = 0;
|
||||
m_bgData.taxiPath[0] = m_taxi.GetTaxiSource();
|
||||
m_bgData.taxiPath[1] = m_taxi.GetTaxiDestination();
|
||||
|
||||
// On taxi we don't need check for dungeon
|
||||
m_bgData.joinPos = new WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_bgData.ClearTaxiPath();
|
||||
|
||||
// Mount spell id storing
|
||||
if (IsMounted())
|
||||
{
|
||||
var auras = GetAuraEffectsByType(AuraType.Mounted);
|
||||
if (!auras.Empty())
|
||||
m_bgData.mountSpell = auras[0].GetId();
|
||||
}
|
||||
else
|
||||
m_bgData.mountSpell = 0;
|
||||
|
||||
// If map is dungeon find linked graveyard
|
||||
if (GetMap().IsDungeon())
|
||||
{
|
||||
WorldSafeLocsRecord entry = Global.ObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam());
|
||||
if (entry != null)
|
||||
m_bgData.joinPos = new WorldLocation(entry.MapID, entry.Loc.X, entry.Loc.Y, entry.Loc.Z, 0.0f);
|
||||
else
|
||||
Log.outError(LogFilter.Player, "SetBattlegroundEntryPoint: Dungeon map {0} has no linked graveyard, setting home location as entry point.", GetMapId());
|
||||
}
|
||||
// If new entry point is not BG or arena set it
|
||||
else if (!GetMap().IsBattlegroundOrArena())
|
||||
m_bgData.joinPos = new WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
||||
}
|
||||
|
||||
if (m_bgData.joinPos.GetMapId() == 0xFFFFFFFF) // In error cases use homebind position
|
||||
m_bgData.joinPos = new WorldLocation(GetHomebind());
|
||||
}
|
||||
|
||||
public void SetBGTeam(Team team)
|
||||
{
|
||||
m_bgData.bgTeam = (uint)team;
|
||||
SetByteValue(PlayerFields.Bytes4, PlayerFieldOffsets.Bytes4OffsetArenaFaction, (byte)(team == Team.Alliance ? 1 : 0));
|
||||
}
|
||||
|
||||
public Team GetBGTeam()
|
||||
{
|
||||
return m_bgData.bgTeam != 0 ? (Team)m_bgData.bgTeam : GetTeam();
|
||||
}
|
||||
|
||||
public void LeaveBattleground(bool teleportToEntryPoint = true)
|
||||
{
|
||||
Battleground bg = GetBattleground();
|
||||
if (bg)
|
||||
{
|
||||
bg.RemovePlayerAtLeave(GetGUID(), teleportToEntryPoint, true);
|
||||
|
||||
// call after remove to be sure that player resurrected for correct cast
|
||||
if (bg.isBattleground() && !IsGameMaster() && WorldConfig.GetBoolValue(WorldCfg.BattlegroundCastDeserter))
|
||||
{
|
||||
if (bg.GetStatus() == BattlegroundStatus.InProgress || bg.GetStatus() == BattlegroundStatus.WaitJoin)
|
||||
{
|
||||
//lets check if player was teleported from BG and schedule delayed Deserter spell cast
|
||||
if (IsBeingTeleportedFar())
|
||||
{
|
||||
ScheduleDelayedOperation(PlayerDelayedOperations.SpellCastDeserter);
|
||||
return;
|
||||
}
|
||||
|
||||
CastSpell(this, 26013, true); // Deserter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanJoinToBattleground(Battleground bg)
|
||||
{
|
||||
// check Deserter debuff
|
||||
if (HasAura(26013))
|
||||
return false;
|
||||
|
||||
if (bg.isArena() && !GetSession().HasPermission(RBACPermissions.JoinArenas))
|
||||
return false;
|
||||
|
||||
if (bg.IsRandom() && !GetSession().HasPermission(RBACPermissions.JoinRandomBg))
|
||||
return false;
|
||||
|
||||
if (!GetSession().HasPermission(RBACPermissions.JoinNormalBg))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearAfkReports() { m_bgData.bgAfkReporter.Clear(); }
|
||||
|
||||
bool CanReportAfkDueToLimit()
|
||||
{
|
||||
// a player can complain about 15 people per 5 minutes
|
||||
if (m_bgData.bgAfkReportedCount++ >= 15)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This player has been blamed to be inactive in a Battleground
|
||||
/// </summary>
|
||||
/// <param name="reporter"></param>
|
||||
public void ReportedAfkBy(Player reporter)
|
||||
{
|
||||
ReportPvPPlayerAFKResult reportAfkResult = new ReportPvPPlayerAFKResult();
|
||||
reportAfkResult.Offender = GetGUID();
|
||||
Battleground bg = GetBattleground();
|
||||
// Battleground also must be in progress!
|
||||
if (!bg || bg != reporter.GetBattleground() || GetTeam() != reporter.GetTeam() || bg.GetStatus() != BattlegroundStatus.InProgress)
|
||||
{
|
||||
reporter.SendPacket(reportAfkResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if player has 'Idle' or 'Inactive' debuff
|
||||
if (!m_bgData.bgAfkReporter.Contains(reporter.GetGUID()) && !HasAura(43680) && !HasAura(43681) && reporter.CanReportAfkDueToLimit())
|
||||
{
|
||||
m_bgData.bgAfkReporter.Add(reporter.GetGUID());
|
||||
// by default 3 players have to complain to apply debuff
|
||||
if (m_bgData.bgAfkReporter.Count >= WorldConfig.GetIntValue(WorldCfg.BattlegroundReportAfk))
|
||||
{
|
||||
// cast 'Idle' spell
|
||||
CastSpell(this, 43680, true);
|
||||
m_bgData.bgAfkReporter.Clear();
|
||||
reportAfkResult.NumBlackMarksOnOffender = (byte)m_bgData.bgAfkReporter.Count;
|
||||
reportAfkResult.NumPlayersIHaveReported = reporter.m_bgData.bgAfkReportedCount;
|
||||
reportAfkResult.Result = ReportPvPPlayerAFKResult.ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
reporter.SendPacket(reportAfkResult);
|
||||
}
|
||||
|
||||
public bool GetRandomWinner() { return m_IsBGRandomWinner; }
|
||||
public void SetRandomWinner(bool isWinner)
|
||||
{
|
||||
m_IsBGRandomWinner = isWinner;
|
||||
if (m_IsBGRandomWinner)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_BATTLEGROUND_RANDOM);
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetBGAccessByLevel(BattlegroundTypeId bgTypeId)
|
||||
{
|
||||
// get a template bg instead of running one
|
||||
Battleground bg = Global.BattlegroundMgr.GetBattlegroundTemplate(bgTypeId);
|
||||
if (!bg)
|
||||
return false;
|
||||
|
||||
// limit check leel to dbc compatible level range
|
||||
uint level = getLevel();
|
||||
if (level > WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
||||
level = WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel);
|
||||
|
||||
if (level < bg.GetMinLevel() || level > bg.GetMaxLevel())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendBGWeekendWorldStates()
|
||||
{
|
||||
foreach (var bl in CliDB.BattlemasterListStorage.Values)
|
||||
{
|
||||
if (bl.HolidayWorldState != 0)
|
||||
{
|
||||
if (Global.BattlegroundMgr.IsBGWeekend((BattlegroundTypeId)bl.Id))
|
||||
SendUpdateWorldState(bl.HolidayWorldState, 1);
|
||||
else
|
||||
SendUpdateWorldState(bl.HolidayWorldState, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SendPvpRewards()
|
||||
{
|
||||
//WorldPacket packet(SMSG_REQUEST_PVP_REWARDS_RESPONSE, 24);
|
||||
//SendPacket(packet);
|
||||
}
|
||||
|
||||
//Battlefields
|
||||
void SendBattlefieldWorldStates()
|
||||
{
|
||||
// Send misc stuff that needs to be sent on every login, like the battle timers.
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.WintergraspEnable))
|
||||
{
|
||||
BattleField wg = Global.BattleFieldMgr.GetBattlefieldByBattleId(1);//Wintergrasp battle
|
||||
if (wg != null)
|
||||
{
|
||||
SendUpdateWorldState(3801, (uint)(wg.IsWarTime() ? 0 : 1));
|
||||
uint timer = wg.IsWarTime() ? 0 : (wg.GetTimer() / 1000); // 0 - Time to next battle
|
||||
SendUpdateWorldState(4354, (uint)(Time.UnixTime + timer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Arenas
|
||||
public void SetArenaTeamInfoField(byte slot, ArenaTeamInfoType type, uint value)
|
||||
{
|
||||
SetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)type, value);
|
||||
}
|
||||
|
||||
public void SetInArenaTeam(uint ArenaTeamId, byte slot, byte type)
|
||||
{
|
||||
SetArenaTeamInfoField(slot, ArenaTeamInfoType.Id, ArenaTeamId);
|
||||
SetArenaTeamInfoField(slot, ArenaTeamInfoType.Type, type);
|
||||
}
|
||||
|
||||
public static uint GetArenaTeamIdFromDB(ObjectGuid guid, byte type)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ARENA_TEAM_ID_BY_PLAYER_GUID);
|
||||
stmt.AddValue(0, guid.GetCounter());
|
||||
stmt.AddValue(1, type);
|
||||
SQLResult result = DB.Characters.Query(stmt);
|
||||
|
||||
if (result.IsEmpty())
|
||||
return 0;
|
||||
|
||||
return result.Read<uint>(0);
|
||||
}
|
||||
|
||||
public static void LeaveAllArenaTeams(ObjectGuid guid)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_ARENA_TEAMS);
|
||||
stmt.AddValue(0, guid.GetCounter());
|
||||
SQLResult result = DB.Characters.Query(stmt);
|
||||
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
uint arenaTeamId = result.Read<uint>(0);
|
||||
if (arenaTeamId != 0)
|
||||
{
|
||||
ArenaTeam arenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(arenaTeamId);
|
||||
if (arenaTeam != null)
|
||||
arenaTeam.DelMember(guid, true);
|
||||
}
|
||||
}
|
||||
while (result.NextRow());
|
||||
}
|
||||
public uint GetArenaTeamId(byte slot) { return GetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)ArenaTeamInfoType.Id); }
|
||||
public uint GetArenaPersonalRating(byte slot) { return GetUInt32Value(PlayerFields.ArenaTeamInfo11 + (slot * (int)ArenaTeamInfoType.End) + (int)ArenaTeamInfoType.PersonalRating); }
|
||||
public void SetArenaTeamIdInvited(uint ArenaTeamId) { m_ArenaTeamIdInvited = ArenaTeamId; }
|
||||
public uint GetArenaTeamIdInvited() { return m_ArenaTeamIdInvited; }
|
||||
public uint GetRBGPersonalRating() { return 0; }
|
||||
|
||||
//OutdoorPVP
|
||||
public bool IsOutdoorPvPActive()
|
||||
{
|
||||
return IsAlive() && !HasInvisibilityAura() && !HasStealthAura() && IsPvP() && !HasUnitMovementFlag(MovementFlag.Flying) && !IsInFlight();
|
||||
}
|
||||
public OutdoorPvP GetOutdoorPvP()
|
||||
{
|
||||
return Global.OutdoorPvPMgr.GetOutdoorPvPToZoneId(GetZoneId());
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
* 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.Network.Packets;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public partial class Player
|
||||
{
|
||||
public void InitTalentForLevel()
|
||||
{
|
||||
uint level = getLevel();
|
||||
// talents base at level diff (talents = level - 9 but some can be used already)
|
||||
if (level < PlayerConst.MinSpecializationLevel)
|
||||
ResetTalentSpecialization();
|
||||
|
||||
uint talentTiers = CalculateTalentsTiers();
|
||||
if (level < 15)
|
||||
{
|
||||
// Remove all talent points
|
||||
ResetTalents(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GetSession().HasPermission(RBACPermissions.SkipCheckMoreTalentsThanAllowed))
|
||||
{
|
||||
for (uint t = talentTiers; t < PlayerConst.MaxTalentTiers; ++t)
|
||||
for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c)
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), t, c))
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
|
||||
SetUInt32Value(PlayerFields.MaxTalentTiers, talentTiers);
|
||||
|
||||
if (!GetSession().PlayerLoading())
|
||||
SendTalentsInfoData(); // update at client
|
||||
}
|
||||
|
||||
public bool AddTalent(TalentRecord talent, byte spec, bool learning)
|
||||
{
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID);
|
||||
if (spellInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Spells, "Player.AddTalent: Spell (ID: {0}) does not exist.", talent.SpellID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Global.SpellMgr.IsSpellValid(spellInfo, this, false))
|
||||
{
|
||||
Log.outError(LogFilter.Spells, "Player.AddTalent: Spell (ID: {0}) is invalid", talent.SpellID);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (talent.OverridesSpellID != 0)
|
||||
AddOverrideSpell(talent.OverridesSpellID, talent.SpellID);
|
||||
|
||||
if (GetTalentMap(spec).ContainsKey(talent.Id))
|
||||
GetTalentMap(spec)[talent.Id] = PlayerSpellState.Unchanged;
|
||||
else
|
||||
GetTalentMap(spec)[talent.Id] = learning ? PlayerSpellState.New : PlayerSpellState.Unchanged;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveTalent(TalentRecord talent)
|
||||
{
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID);
|
||||
if (spellInfo == null)
|
||||
return;
|
||||
|
||||
RemoveSpell(talent.SpellID, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
|
||||
if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell)
|
||||
RemoveSpell(effect.TriggerSpell, true);
|
||||
|
||||
if (talent.OverridesSpellID != 0)
|
||||
RemoveOverrideSpell(talent.OverridesSpellID, talent.SpellID);
|
||||
|
||||
var talentMap = GetTalentMap(GetActiveTalentGroup());
|
||||
// if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
|
||||
if (talentMap.ContainsKey(talent.Id))
|
||||
talentMap[talent.Id] = PlayerSpellState.Removed;
|
||||
}
|
||||
|
||||
public TalentLearnResult LearnTalent(uint talentId, ref int spellOnCooldown)
|
||||
{
|
||||
if (IsInCombat())
|
||||
return TalentLearnResult.FailedAffectingCombat;
|
||||
|
||||
if (IsDead() || GetMap().IsBattlegroundOrArena())
|
||||
return TalentLearnResult.FailedCantDoThatRightNow;
|
||||
|
||||
if (GetUInt32Value(PlayerFields.CurrentSpecId) == 0)
|
||||
return TalentLearnResult.FailedNoPrimaryTreeSelected;
|
||||
|
||||
TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(talentId);
|
||||
if (talentInfo == null)
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
if (talentInfo.SpecID != 0 && talentInfo.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// prevent learn talent for different class (cheating)
|
||||
if (talentInfo.ClassID != (byte)GetClass())
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// check if we have enough talent points
|
||||
if (talentInfo.TierID >= GetUInt32Value(PlayerFields.MaxTalentTiers))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// TODO: prevent changing talents that are on cooldown
|
||||
|
||||
// Check if there is a different talent for us to learn in selected slot
|
||||
// Example situation:
|
||||
// Warrior talent row 2 slot 0
|
||||
// Talent.dbc has an entry for each specialization
|
||||
// but only 2 out of 3 have SpecID != 0
|
||||
// We need to make sure that if player is in one of these defined specs he will not learn the other choice
|
||||
TalentRecord bestSlotMatch = null;
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), talentInfo.TierID, talentInfo.ColumnIndex))
|
||||
{
|
||||
if (talent.SpecID == 0)
|
||||
bestSlotMatch = talent;
|
||||
else if (talent.SpecID == GetUInt32Value(PlayerFields.CurrentSpecId))
|
||||
{
|
||||
bestSlotMatch = talent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (talentInfo != bestSlotMatch)
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
// Check if player doesn't have any talent in current tier
|
||||
for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c)
|
||||
{
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(GetClass(), talentInfo.TierID, c))
|
||||
{
|
||||
//Todo test me
|
||||
if (talent.SpecID != GetUInt32Value(PlayerFields.CurrentSpecId))
|
||||
continue;
|
||||
|
||||
if (HasTalent(talent.Id, GetActiveTalentGroup()) && !HasFlag(PlayerFields.Flags, PlayerFlags.Resting) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc))
|
||||
return TalentLearnResult.FailedRestArea;
|
||||
|
||||
if (GetSpellHistory().HasCooldown(talent.SpellID))
|
||||
{
|
||||
spellOnCooldown = (int)talent.SpellID;
|
||||
return TalentLearnResult.FailedCantRemoveTalent;
|
||||
}
|
||||
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
|
||||
// spell not set in talent.dbc
|
||||
uint spellid = talentInfo.SpellID;
|
||||
if (spellid == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Player.LearnTalent: Talent.dbc has no spellInfo for talent: {0} (spell id = 0)", talentId);
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
}
|
||||
|
||||
// already known
|
||||
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
|
||||
return TalentLearnResult.FailedUnknown;
|
||||
|
||||
LearnSpell(spellid, false);
|
||||
|
||||
Log.outDebug(LogFilter.Misc, "Player.LearnTalent: TalentID: {0} Spell: {1} Group: {2}", talentId, spellid, GetActiveTalentGroup());
|
||||
|
||||
return TalentLearnResult.LearnOk;
|
||||
}
|
||||
|
||||
public void ResetTalentSpecialization()
|
||||
{
|
||||
// Reset only talents that have different spells for each spec
|
||||
Class class_ = GetClass();
|
||||
for (uint t = 0; t < PlayerConst.MaxTalentTiers; ++t)
|
||||
{
|
||||
for (uint c = 0; c < PlayerConst.MaxTalentColumns; ++c)
|
||||
{
|
||||
if (Global.DB2Mgr.GetTalentsByPosition(class_, t, c).Count > 1)
|
||||
{
|
||||
foreach (TalentRecord talent in Global.DB2Mgr.GetTalentsByPosition(class_, t, c))
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoveSpecializationSpells();
|
||||
|
||||
ChrSpecializationRecord defaultSpec = Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass());
|
||||
SetPrimarySpecialization(defaultSpec.Id);
|
||||
SetActiveTalentGroup(defaultSpec.OrderIndex);
|
||||
SetUInt32Value(PlayerFields.CurrentSpecId, defaultSpec.Id);
|
||||
|
||||
LearnSpecializationSpells();
|
||||
|
||||
SendTalentsInfoData();
|
||||
UpdateItemSetAuras(false);
|
||||
}
|
||||
|
||||
bool HasTalent(uint talnetId, byte group)
|
||||
{
|
||||
return GetTalentMap(group).ContainsKey(talnetId) && GetTalentMap(group)[talnetId] != PlayerSpellState.Removed;
|
||||
}
|
||||
|
||||
uint GetTalentResetCost() { return _specializationInfo.ResetTalentsCost; }
|
||||
void SetTalentResetCost(uint cost) { _specializationInfo.ResetTalentsCost = cost; }
|
||||
long GetTalentResetTime() { return _specializationInfo.ResetTalentsTime; }
|
||||
void SetTalentResetTime(long time_) { _specializationInfo.ResetTalentsTime = time_; }
|
||||
uint GetPrimarySpecialization() { return _specializationInfo.PrimarySpecialization; }
|
||||
void SetPrimarySpecialization(uint spec) { _specializationInfo.PrimarySpecialization = spec; }
|
||||
public byte GetActiveTalentGroup() { return _specializationInfo.ActiveGroup; }
|
||||
void SetActiveTalentGroup(byte group) { _specializationInfo.ActiveGroup = group; }
|
||||
|
||||
// Loot Spec
|
||||
public void SetLootSpecId(uint id) { SetUInt32Value(PlayerFields.LootSpecId, id); }
|
||||
uint GetLootSpecId() { return GetUInt32Value(PlayerFields.LootSpecId); }
|
||||
|
||||
public uint GetDefaultSpecId()
|
||||
{
|
||||
return Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass()).Id;
|
||||
}
|
||||
|
||||
public void ActivateTalentGroup(ChrSpecializationRecord spec)
|
||||
{
|
||||
if (GetActiveTalentGroup() == spec.OrderIndex)
|
||||
return;
|
||||
|
||||
if (IsNonMeleeSpellCast(false))
|
||||
InterruptNonMeleeSpells(false);
|
||||
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
_SaveActions(trans);
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
// TO-DO: We need more research to know what happens with warlock's reagent
|
||||
Pet pet = GetPet();
|
||||
if (pet)
|
||||
RemovePet(pet, PetSaveMode.NotInSlot);
|
||||
|
||||
ClearAllReactives();
|
||||
UnsummonAllTotems();
|
||||
ExitVehicle();
|
||||
RemoveAllControlled();
|
||||
|
||||
// Let client clear his current Actions
|
||||
SendActionButtons(2);
|
||||
foreach (var talentInfo in CliDB.TalentStorage.Values)
|
||||
{
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (talentInfo.ClassID != (int)GetClass())
|
||||
continue;
|
||||
|
||||
if (talentInfo.SpellID == 0)
|
||||
continue;
|
||||
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID);
|
||||
if (spellInfo == null)
|
||||
continue;
|
||||
|
||||
RemoveSpell(talentInfo.SpellID, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
|
||||
if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell)
|
||||
RemoveSpell(effect.TriggerSpell, true);
|
||||
|
||||
if (talentInfo.OverridesSpellID != 0)
|
||||
RemoveOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID);
|
||||
}
|
||||
|
||||
// Remove spec specific spells
|
||||
RemoveSpecializationSpells();
|
||||
|
||||
foreach (uint glyphId in GetGlyphs(GetActiveTalentGroup()))
|
||||
RemoveAurasDueToSpell(CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID);
|
||||
|
||||
SetActiveTalentGroup(spec.OrderIndex);
|
||||
SetUInt32Value(PlayerFields.CurrentSpecId, spec.Id);
|
||||
if (GetPrimarySpecialization() == 0)
|
||||
SetPrimarySpecialization(spec.Id);
|
||||
|
||||
foreach (var talentInfo in CliDB.TalentStorage.Values)
|
||||
{
|
||||
// learn only talents for character class
|
||||
if (talentInfo.ClassID != (int)GetClass())
|
||||
continue;
|
||||
|
||||
if (talentInfo.SpellID == 0)
|
||||
continue;
|
||||
|
||||
if (HasTalent(talentInfo.SpellID, GetActiveTalentGroup()))
|
||||
{
|
||||
LearnSpell(talentInfo.SpellID, false); // add the talent to the PlayerSpellMap
|
||||
if (talentInfo.OverridesSpellID != 0)
|
||||
AddOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID);
|
||||
}
|
||||
}
|
||||
|
||||
LearnSpecializationSpells();
|
||||
|
||||
if (CanUseMastery())
|
||||
{
|
||||
for (uint i = 0; i < PlayerConst.MaxMasterySpells; ++i)
|
||||
{
|
||||
uint mastery = spec.MasterySpellID[i];
|
||||
if (mastery != 0)
|
||||
LearnSpell(mastery, false);
|
||||
}
|
||||
}
|
||||
|
||||
InitTalentForLevel();
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC);
|
||||
stmt.AddValue(0, GetGUID().GetCounter());
|
||||
stmt.AddValue(1, GetActiveTalentGroup());
|
||||
_LoadActions(DB.Characters.Query(stmt));
|
||||
|
||||
SendActionButtons(1);
|
||||
|
||||
UpdateDisplayPower();
|
||||
PowerType pw = getPowerType();
|
||||
if (pw != PowerType.Mana)
|
||||
SetPower(PowerType.Mana, 0); // Mana must be 0 even if it isn't the active power type.
|
||||
|
||||
SetPower(pw, 0);
|
||||
UpdateItemSetAuras(false);
|
||||
|
||||
// update visible transmog
|
||||
for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i)
|
||||
{
|
||||
Item equippedItem = GetItemByPos(InventorySlots.Bag0, i);
|
||||
if (equippedItem)
|
||||
SetVisibleItemSlot(i, equippedItem);
|
||||
}
|
||||
|
||||
foreach (uint glyphId in GetGlyphs(spec.OrderIndex))
|
||||
CastSpell(this, CliDB.GlyphPropertiesStorage.LookupByKey(glyphId).SpellID, true);
|
||||
|
||||
ActiveGlyphs activeGlyphs = new ActiveGlyphs();
|
||||
foreach (uint glyphId in GetGlyphs(spec.OrderIndex))
|
||||
{
|
||||
List<uint> bindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphId);
|
||||
foreach (uint bindableSpell in bindableSpells)
|
||||
if (HasSpell(bindableSpell) && !m_overrideSpells.ContainsKey(bindableSpell))
|
||||
activeGlyphs.Glyphs.Add(new GlyphBinding(bindableSpell, (ushort)glyphId));
|
||||
}
|
||||
|
||||
activeGlyphs.IsFullUpdate = true;
|
||||
SendPacket(activeGlyphs);
|
||||
}
|
||||
|
||||
public Dictionary<uint, PlayerSpellState> GetTalentMap(uint spec) { return _specializationInfo.Talents[spec]; }
|
||||
public List<uint> GetGlyphs(byte spec) { return _specializationInfo.Glyphs[spec]; }
|
||||
|
||||
public uint GetNextResetTalentsCost()
|
||||
{
|
||||
// The first time reset costs 1 gold
|
||||
if (GetTalentResetCost() < 1 * MoneyConstants.Gold)
|
||||
return 1 * MoneyConstants.Gold;
|
||||
// then 5 gold
|
||||
else if (GetTalentResetCost() < 5 * MoneyConstants.Gold)
|
||||
return 5 * MoneyConstants.Gold;
|
||||
// After that it increases in increments of 5 gold
|
||||
else if (GetTalentResetCost() < 10 * MoneyConstants.Gold)
|
||||
return 10 * MoneyConstants.Gold;
|
||||
else
|
||||
{
|
||||
ulong months = (ulong)(Global.WorldMgr.GetGameTime() - GetTalentResetTime()) / Time.Month;
|
||||
if (months > 0)
|
||||
{
|
||||
// This cost will be reduced by a rate of 5 gold per month
|
||||
uint new_cost = (uint)(GetTalentResetCost() - 5 * MoneyConstants.Gold * months);
|
||||
// to a minimum of 10 gold.
|
||||
return new_cost < 10 * MoneyConstants.Gold ? 10 * MoneyConstants.Gold : new_cost;
|
||||
}
|
||||
else
|
||||
{
|
||||
// After that it increases in increments of 5 gold
|
||||
uint new_cost = GetTalentResetCost() + 5 * MoneyConstants.Gold;
|
||||
// until it hits a cap of 50 gold.
|
||||
if (new_cost > 50 * MoneyConstants.Gold)
|
||||
new_cost = 50 * MoneyConstants.Gold;
|
||||
return new_cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ResetTalents(bool noCost = false)
|
||||
{
|
||||
Global.ScriptMgr.OnPlayerTalentsReset(this, noCost);
|
||||
|
||||
// not need after this call
|
||||
if (HasAtLoginFlag(AtLoginFlags.ResetTalents))
|
||||
RemoveAtLoginFlag(AtLoginFlags.ResetTalents, true);
|
||||
|
||||
uint cost = 0;
|
||||
if (!noCost && !WorldConfig.GetBoolValue(WorldCfg.NoResetTalentCost))
|
||||
{
|
||||
cost = GetNextResetTalentsCost();
|
||||
|
||||
if (!HasEnoughMoney(cost))
|
||||
{
|
||||
SendBuyError(BuyResult.NotEnoughtMoney, null, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RemovePet(null, PetSaveMode.NotInSlot, true);
|
||||
|
||||
foreach (var talentInfo in CliDB.TalentStorage.Values)
|
||||
{
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (talentInfo.ClassID != (uint)GetClass())
|
||||
continue;
|
||||
|
||||
// skip non-existant talent ranks
|
||||
if (talentInfo.SpellID == 0)
|
||||
continue;
|
||||
|
||||
RemoveTalent(talentInfo);
|
||||
}
|
||||
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
_SaveTalents(trans);
|
||||
_SaveSpells(trans);
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
if (!noCost)
|
||||
{
|
||||
ModifyMoney(-cost);
|
||||
UpdateCriteria(CriteriaTypes.GoldSpentForTalents, cost);
|
||||
UpdateCriteria(CriteriaTypes.NumberOfTalentResets, 1);
|
||||
|
||||
SetTalentResetCost(cost);
|
||||
SetTalentResetTime(Time.UnixTime);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SendTalentsInfoData()
|
||||
{
|
||||
UpdateTalentData packet = new UpdateTalentData();
|
||||
packet.Info.PrimarySpecialization = GetPrimarySpecialization();
|
||||
packet.Info.ActiveGroup = GetActiveTalentGroup();
|
||||
|
||||
for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i)
|
||||
{
|
||||
ChrSpecializationRecord spec = Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), i);
|
||||
if (spec == null)
|
||||
continue;
|
||||
|
||||
var talents = GetTalentMap(i);
|
||||
|
||||
|
||||
UpdateTalentData.TalentGroupInfo groupInfoPkt = new UpdateTalentData.TalentGroupInfo();
|
||||
groupInfoPkt.SpecID = spec.Id;
|
||||
|
||||
foreach (var pair in talents)
|
||||
{
|
||||
if (pair.Value == PlayerSpellState.Removed)
|
||||
continue;
|
||||
|
||||
TalentRecord talentInfo = CliDB.TalentStorage.LookupByKey(pair.Key);
|
||||
if (talentInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Player {0} has unknown talent id: {1}", GetName(), pair.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (talentInfo.ClassID != (uint)GetClass())
|
||||
continue;
|
||||
|
||||
SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID);
|
||||
if (spellEntry == null)
|
||||
{
|
||||
Log.outError(LogFilter.Player, "Player {0} has unknown talent spell: {1}", GetName(), talentInfo.SpellID);
|
||||
continue;
|
||||
}
|
||||
|
||||
groupInfoPkt.TalentIDs.Add((ushort)pair.Key);
|
||||
}
|
||||
|
||||
packet.Info.TalentGroups.Add(groupInfoPkt);
|
||||
}
|
||||
|
||||
SendPacket(packet);
|
||||
}
|
||||
|
||||
public void SendRespecWipeConfirm(ObjectGuid guid, uint cost)
|
||||
{
|
||||
RespecWipeConfirm respecWipeConfirm = new RespecWipeConfirm();
|
||||
respecWipeConfirm.RespecMaster = guid;
|
||||
respecWipeConfirm.Cost = cost;
|
||||
respecWipeConfirm.RespecType = SpecResetType.Talents;
|
||||
SendPacket(respecWipeConfirm);
|
||||
}
|
||||
|
||||
uint CalculateTalentsTiers()
|
||||
{
|
||||
uint[] rowLevels = new uint[0];
|
||||
switch (GetClass())
|
||||
{
|
||||
case Class.Deathknight:
|
||||
rowLevels = new uint[] { 57, 58, 59, 60, 75, 90, 100 };
|
||||
break;
|
||||
case Class.DemonHunter:
|
||||
rowLevels = new uint[] { 99, 100, 102, 104, 106, 108, 110 };
|
||||
break;
|
||||
default:
|
||||
rowLevels = new uint[] { 15, 30, 45, 60, 75, 90, 100 };
|
||||
break;
|
||||
}
|
||||
|
||||
for (uint i = PlayerConst.MaxTalentTiers; i != 0; --i)
|
||||
if (getLevel() >= rowLevels[i - 1])
|
||||
return i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class PlayerTaxi
|
||||
{
|
||||
public void InitTaxiNodesForLevel(Race race, Class chrClass, uint level)
|
||||
{
|
||||
// class specific initial known nodes
|
||||
var factionMask = Player.TeamForRace(race) == Team.Horde ? CliDB.HordeTaxiNodesMask : CliDB.AllianceTaxiNodesMask;
|
||||
switch (chrClass)
|
||||
{
|
||||
case Class.Deathknight:
|
||||
{
|
||||
for (byte i = 0; i < PlayerConst.TaxiMaskSize; ++i)
|
||||
m_taximask[i] |= (byte)(CliDB.OldContinentsNodesMask[i] & factionMask[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// race specific initial known nodes: capital and taxi hub masks
|
||||
switch (race)
|
||||
{
|
||||
case Race.Human:
|
||||
case Race.Dwarf:
|
||||
case Race.NightElf:
|
||||
case Race.Gnome:
|
||||
case Race.Draenei:
|
||||
case Race.Worgen:
|
||||
case Race.PandarenAlliance:
|
||||
SetTaximaskNode(2); // Stormwind, Elwynn
|
||||
SetTaximaskNode(6); // Ironforge, Dun Morogh
|
||||
SetTaximaskNode(26); // Lor'danel, Darkshore
|
||||
SetTaximaskNode(27); // Rut'theran Village, Teldrassil
|
||||
SetTaximaskNode(49); // Moonglade (Alliance)
|
||||
SetTaximaskNode(94); // The Exodar
|
||||
SetTaximaskNode(456); // Dolanaar, Teldrassil
|
||||
SetTaximaskNode(457); // Darnassus, Teldrassil
|
||||
SetTaximaskNode(582); // Goldshire, Elwynn
|
||||
SetTaximaskNode(589); // Eastvale Logging Camp, Elwynn
|
||||
SetTaximaskNode(619); // Kharanos, Dun Morogh
|
||||
SetTaximaskNode(620); // Gol'Bolar Quarry, Dun Morogh
|
||||
SetTaximaskNode(624); // Azure Watch, Azuremyst Isle
|
||||
break;
|
||||
case Race.Orc:
|
||||
case Race.Undead:
|
||||
case Race.Tauren:
|
||||
case Race.Troll:
|
||||
case Race.BloodElf:
|
||||
case Race.Goblin:
|
||||
case Race.PandarenHorde:
|
||||
SetTaximaskNode(11); // Undercity, Tirisfal
|
||||
SetTaximaskNode(22); // Thunder Bluff, Mulgore
|
||||
SetTaximaskNode(23); // Orgrimmar, Durotar
|
||||
SetTaximaskNode(69); // Moonglade (Horde)
|
||||
SetTaximaskNode(82); // Silvermoon City
|
||||
SetTaximaskNode(384); // The Bulwark, Tirisfal
|
||||
SetTaximaskNode(402); // Bloodhoof Village, Mulgore
|
||||
SetTaximaskNode(460); // Brill, Tirisfal Glades
|
||||
SetTaximaskNode(536); // Sen'jin Village, Durotar
|
||||
SetTaximaskNode(537); // Razor Hill, Durotar
|
||||
SetTaximaskNode(625); // Fairbreeze Village, Eversong Woods
|
||||
SetTaximaskNode(631); // Falconwing Square, Eversong Woods
|
||||
break;
|
||||
}
|
||||
|
||||
// new continent starting masks (It will be accessible only at new map)
|
||||
switch (Player.TeamForRace(race))
|
||||
{
|
||||
case Team.Alliance:
|
||||
SetTaximaskNode(100);
|
||||
break;
|
||||
case Team.Horde:
|
||||
SetTaximaskNode(99);
|
||||
break;
|
||||
}
|
||||
// level dependent taxi hubs
|
||||
if (level >= 68)
|
||||
SetTaximaskNode(213); //Shattered Sun Staging Area
|
||||
}
|
||||
|
||||
public void LoadTaxiMask(string data)
|
||||
{
|
||||
var split = new StringArray(data, ' ');
|
||||
|
||||
byte index = 0;
|
||||
for (var i = 0; index < PlayerConst.TaxiMaskSize && i != split.Length; ++i, ++index)
|
||||
{
|
||||
// load and set bits only for existing taxi nodes
|
||||
m_taximask[index] = (byte)(CliDB.TaxiNodesMask[index] & uint.Parse(split[i]));
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendTaximaskTo(ShowTaxiNodes data, bool all)
|
||||
{
|
||||
if (all)
|
||||
data.Nodes = CliDB.TaxiNodesMask; // all existed nodes
|
||||
else
|
||||
data.Nodes = m_taximask; // known nodes
|
||||
}
|
||||
|
||||
public bool LoadTaxiDestinationsFromString(string values, Team team)
|
||||
{
|
||||
ClearTaxiDestinations();
|
||||
|
||||
var split = new StringArray(values, ' ');
|
||||
for (var i = 0; i < split.Length; ++i)
|
||||
{
|
||||
uint node = uint.Parse(split[i]);
|
||||
AddTaxiDestination(node);
|
||||
}
|
||||
|
||||
if (m_TaxiDestinations.Empty())
|
||||
return true;
|
||||
|
||||
// Check integrity
|
||||
if (m_TaxiDestinations.Count < 2)
|
||||
return false;
|
||||
|
||||
for (int i = 1; i < m_TaxiDestinations.Count; ++i)
|
||||
{
|
||||
uint cost;
|
||||
uint path;
|
||||
Global.ObjectMgr.GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], out path, out cost);
|
||||
if (path == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// can't load taxi path without mount set (quest taxi path?)
|
||||
if (Global.ObjectMgr.GetTaxiMountDisplayId(GetTaxiSource(), team, true) == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string SaveTaxiDestinationsToString()
|
||||
{
|
||||
if (m_TaxiDestinations.Empty())
|
||||
return "";
|
||||
|
||||
StringBuilder ss = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < m_TaxiDestinations.Count; ++i)
|
||||
ss.AppendFormat("{0} ", m_TaxiDestinations[i]);
|
||||
|
||||
return ss.ToString();
|
||||
}
|
||||
|
||||
public uint GetCurrentTaxiPath()
|
||||
{
|
||||
if (m_TaxiDestinations.Count < 2)
|
||||
return 0;
|
||||
|
||||
uint path;
|
||||
uint cost;
|
||||
|
||||
Global.ObjectMgr.GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], out path, out cost);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public bool RequestEarlyLanding()
|
||||
{
|
||||
if (m_TaxiDestinations.Count <= 2)
|
||||
return false;
|
||||
|
||||
// start from first destination - m_TaxiDestinations[0] is the current starting node
|
||||
for (var i = 1; i < m_TaxiDestinations.Count; ++i)
|
||||
{
|
||||
if (IsTaximaskNodeKnown(m_TaxiDestinations[i]))
|
||||
{
|
||||
if (++i == m_TaxiDestinations.Count - 1)
|
||||
return false; // if we are left with only 1 known node on the path don't change the spline, its our final destination anyway
|
||||
|
||||
m_TaxiDestinations.RemoveRange(i, m_TaxiDestinations.Count - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsTaximaskNodeKnown(uint nodeidx)
|
||||
{
|
||||
byte field = (byte)((nodeidx - 1) / 8);
|
||||
uint submask = (uint)(1 << (int)((nodeidx - 1) % 8));
|
||||
return (m_taximask[field] & submask) == submask;
|
||||
}
|
||||
public bool SetTaximaskNode(uint nodeidx)
|
||||
{
|
||||
byte field = (byte)((nodeidx - 1) / 8);
|
||||
uint submask = (uint)(1 << (int)((nodeidx - 1) % 8));
|
||||
if ((m_taximask[field] & submask) != submask)
|
||||
{
|
||||
m_taximask[field] |= (byte)submask;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ClearTaxiDestinations() { m_TaxiDestinations.Clear(); }
|
||||
public void AddTaxiDestination(uint dest) { m_TaxiDestinations.Add(dest); }
|
||||
void SetTaxiDestination(List<uint> nodes)
|
||||
{
|
||||
m_TaxiDestinations.Clear();
|
||||
m_TaxiDestinations.AddRange(nodes);
|
||||
}
|
||||
public uint GetTaxiSource() { return m_TaxiDestinations.Empty() ? 0 : m_TaxiDestinations[0]; }
|
||||
public uint GetTaxiDestination() { return m_TaxiDestinations.Count < 2 ? 0 : m_TaxiDestinations[1]; }
|
||||
public uint NextTaxiDestination()
|
||||
{
|
||||
m_TaxiDestinations.RemoveAt(0);
|
||||
return GetTaxiDestination();
|
||||
}
|
||||
public List<uint> GetPath() { return m_TaxiDestinations; }
|
||||
public bool empty() { return m_TaxiDestinations.Empty(); }
|
||||
|
||||
public byte[] m_taximask = new byte[PlayerConst.TaxiMaskSize];
|
||||
List<uint> m_TaxiDestinations = new List<uint>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using Framework.Constants;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class RestMgr
|
||||
{
|
||||
public RestMgr(Player player)
|
||||
{
|
||||
_player = player;
|
||||
}
|
||||
|
||||
public void SetRestBonus(RestTypes restType, float restBonus)
|
||||
{
|
||||
byte rest_rested_offset;
|
||||
byte rest_state_offset;
|
||||
PlayerFields next_level_xp_field;
|
||||
bool affectedByRaF = false;
|
||||
|
||||
switch (restType)
|
||||
{
|
||||
case RestTypes.XP:
|
||||
// Reset restBonus (XP only) for max level players
|
||||
if (_player.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
||||
restBonus = 0;
|
||||
|
||||
rest_rested_offset = PlayerFieldOffsets.RestRestedXp;
|
||||
rest_state_offset = PlayerFieldOffsets.RestStateXp;
|
||||
next_level_xp_field = PlayerFields.NextLevelXp;
|
||||
affectedByRaF = true;
|
||||
break;
|
||||
case RestTypes.Honor:
|
||||
// Reset restBonus (Honor only) for players with max honor level.
|
||||
if (_player.IsMaxHonorLevelAndPrestige())
|
||||
restBonus = 0;
|
||||
|
||||
rest_rested_offset = PlayerFieldOffsets.RestRestedHonor;
|
||||
rest_state_offset = PlayerFieldOffsets.RestStateHonor;
|
||||
next_level_xp_field = PlayerFields.HonorNextLevel;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (restBonus < 0)
|
||||
restBonus = 0;
|
||||
|
||||
float rest_bonus_max = (float)(_player.GetUInt32Value(next_level_xp_field)) * 1.5f / 2;
|
||||
|
||||
if (restBonus > rest_bonus_max)
|
||||
_restBonus[(int)restType] = rest_bonus_max;
|
||||
else
|
||||
_restBonus[(int)restType] = restBonus;
|
||||
|
||||
// update data for client
|
||||
if (affectedByRaF && _player.GetsRecruitAFriendBonus(true) && (_player.GetSession().IsARecruiter() || _player.GetSession().GetRecruiterId() != 0))
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.RAFLinked);
|
||||
else
|
||||
{
|
||||
if (_restBonus[(int)restType] > 10)
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.Rested);
|
||||
else if (_restBonus[(int)restType] <= 1)
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_state_offset, (uint)PlayerRestState.NotRAFLinked);
|
||||
}
|
||||
|
||||
// RestTickUpdate
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + rest_rested_offset, (uint)_restBonus[(int)restType]);
|
||||
}
|
||||
|
||||
public void AddRestBonus(RestTypes restType, float restBonus)
|
||||
{
|
||||
// Don't add extra rest bonus to max level players. Note: Might need different condition in next expansion for honor XP (PLAYER_LEVEL_MIN_HONOR perhaps).
|
||||
if (_player.getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
||||
restBonus = 0;
|
||||
|
||||
float totalRestBonus = GetRestBonus(restType) + restBonus;
|
||||
SetRestBonus(restType, totalRestBonus);
|
||||
}
|
||||
|
||||
public void SetRestFlag(RestFlag restFlag, uint triggerId = 0)
|
||||
{
|
||||
RestFlag oldRestMask = _restFlagMask;
|
||||
_restFlagMask |= restFlag;
|
||||
|
||||
if (oldRestMask == 0 && _restFlagMask != 0) // only set flag/time on the first rest state
|
||||
{
|
||||
_restTime = Time.UnixTime;
|
||||
_player.SetFlag(PlayerFields.Flags, PlayerFlags.Resting);
|
||||
}
|
||||
|
||||
if (triggerId != 0)
|
||||
_innAreaTriggerId = triggerId;
|
||||
}
|
||||
|
||||
public void RemoveRestFlag(RestFlag restFlag)
|
||||
{
|
||||
RestFlag oldRestMask = _restFlagMask;
|
||||
_restFlagMask &= ~restFlag;
|
||||
|
||||
if (oldRestMask != 0 && _restFlagMask == 0) // only remove flag/time on the last rest state remove
|
||||
{
|
||||
_restTime = 0;
|
||||
_player.RemoveFlag(PlayerFields.Flags, PlayerFlags.Resting);
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetRestBonusFor(RestTypes restType, uint xp)
|
||||
{
|
||||
uint rested_bonus = (uint)GetRestBonus(restType); // xp for each rested bonus
|
||||
|
||||
if (rested_bonus > xp) // max rested_bonus == xp or (r+x) = 200% xp
|
||||
rested_bonus = xp;
|
||||
|
||||
SetRestBonus(restType, GetRestBonus(restType) - rested_bonus);
|
||||
|
||||
Log.outDebug(LogFilter.Player, "RestMgr.GetRestBonus: Player '{0}' ({1}) gain {2} xp (+{3} Rested Bonus). Rested points={4}",
|
||||
_player.GetGUID().ToString(), _player.GetName(), xp + rested_bonus, rested_bonus, GetRestBonus(restType));
|
||||
return rested_bonus;
|
||||
}
|
||||
|
||||
public void Update(uint now)
|
||||
{
|
||||
if (RandomHelper.randChance(3) && _restTime > 0) // freeze update
|
||||
{
|
||||
long timeDiff = now - _restTime;
|
||||
if (timeDiff >= 10)
|
||||
{
|
||||
_restTime = now;
|
||||
|
||||
float bubble = 0.125f * WorldConfig.GetFloatValue(WorldCfg.RateRestIngame);
|
||||
AddRestBonus(RestTypes.XP, timeDiff * CalcExtraPerSec(RestTypes.XP, bubble));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadRestBonus(RestTypes restType, PlayerRestState state, float restBonus)
|
||||
{
|
||||
_restBonus[(int)restType] = restBonus;
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + (int)restType * 2, (uint)state);
|
||||
_player.SetUInt32Value(PlayerFields.RestInfo + (int)restType * 2 + 1, (uint)restBonus);
|
||||
}
|
||||
|
||||
public float CalcExtraPerSec(RestTypes restType, float bubble)
|
||||
{
|
||||
switch (restType)
|
||||
{
|
||||
case RestTypes.Honor:
|
||||
return (_player.GetUInt32Value(PlayerFields.HonorNextLevel)) / 72000.0f * bubble;
|
||||
case RestTypes.XP:
|
||||
return (_player.GetUInt32Value(PlayerFields.NextLevelXp)) / 72000.0f * bubble;
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetRestBonus(RestTypes restType) { return _restBonus[(int)restType]; }
|
||||
public bool HasRestFlag(RestFlag restFlag) { return (_restFlagMask & restFlag) != 0; }
|
||||
public uint GetInnTriggerId() { return _innAreaTriggerId; }
|
||||
|
||||
Player _player;
|
||||
long _restTime;
|
||||
uint _innAreaTriggerId;
|
||||
float[] _restBonus = new float[(int)RestTypes.Max];
|
||||
RestFlag _restFlagMask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.DataStorage;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class SceneMgr
|
||||
{
|
||||
public SceneMgr(Player player)
|
||||
{
|
||||
_player = player;
|
||||
_standaloneSceneInstanceID = 0;
|
||||
_isDebuggingScenes = false;
|
||||
}
|
||||
|
||||
public uint PlayScene(uint sceneId, Position position = null)
|
||||
{
|
||||
SceneTemplate sceneTemplate = Global.ObjectMgr.GetSceneTemplate(sceneId);
|
||||
return PlaySceneByTemplate(sceneTemplate, position);
|
||||
}
|
||||
|
||||
uint PlaySceneByTemplate(SceneTemplate sceneTemplate, Position position = null)
|
||||
{
|
||||
if (sceneTemplate == null)
|
||||
return 0;
|
||||
|
||||
SceneScriptPackageRecord entry = CliDB.SceneScriptPackageStorage.LookupByKey(sceneTemplate.ScenePackageId);
|
||||
if (entry == null)
|
||||
return 0;
|
||||
|
||||
// By default, take player position
|
||||
if (position == null)
|
||||
position = GetPlayer();
|
||||
|
||||
uint sceneInstanceID = GetNewStandaloneSceneInstanceID();
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugPlay, sceneInstanceID, sceneTemplate.ScenePackageId, sceneTemplate.PlaybackFlags);
|
||||
|
||||
PlayScene playScene = new PlayScene();
|
||||
playScene.SceneID = sceneTemplate.SceneId;
|
||||
playScene.PlaybackFlags = (uint)sceneTemplate.PlaybackFlags;
|
||||
playScene.SceneInstanceID = sceneInstanceID;
|
||||
playScene.SceneScriptPackageID = sceneTemplate.ScenePackageId;
|
||||
playScene.Location = position;
|
||||
playScene.TransportGUID = GetPlayer().GetTransGUID();
|
||||
|
||||
GetPlayer().SendPacket(playScene);
|
||||
|
||||
AddInstanceIdToSceneMap(sceneInstanceID, sceneTemplate);
|
||||
|
||||
Global.ScriptMgr.OnSceneStart(GetPlayer(), sceneInstanceID, sceneTemplate);
|
||||
|
||||
return sceneInstanceID;
|
||||
}
|
||||
|
||||
public uint PlaySceneByPackageId(uint sceneScriptPackageId, SceneFlags playbackflags = SceneFlags.Unk16, Position position = null)
|
||||
{
|
||||
SceneTemplate sceneTemplate = new SceneTemplate();
|
||||
sceneTemplate.SceneId = 0;
|
||||
sceneTemplate.ScenePackageId = sceneScriptPackageId;
|
||||
sceneTemplate.PlaybackFlags = playbackflags;
|
||||
sceneTemplate.ScriptId = 0;
|
||||
|
||||
return PlaySceneByTemplate(sceneTemplate, position);
|
||||
}
|
||||
|
||||
void CancelScene(uint sceneInstanceID, bool removeFromMap = true)
|
||||
{
|
||||
if (removeFromMap)
|
||||
RemoveSceneInstanceId(sceneInstanceID);
|
||||
|
||||
CancelScene cancelScene = new CancelScene();
|
||||
cancelScene.SceneInstanceID = sceneInstanceID;
|
||||
GetPlayer().SendPacket(cancelScene);
|
||||
}
|
||||
|
||||
public void OnSceneTrigger(uint sceneInstanceID, string triggerName)
|
||||
{
|
||||
if (!HasScene(sceneInstanceID))
|
||||
return;
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugTrigger, sceneInstanceID, triggerName);
|
||||
|
||||
SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID);
|
||||
Global.ScriptMgr.OnSceneTrigger(GetPlayer(), sceneInstanceID, sceneTemplate, triggerName);
|
||||
}
|
||||
|
||||
public void OnSceneCancel(uint sceneInstanceID)
|
||||
{
|
||||
if (!HasScene(sceneInstanceID))
|
||||
return;
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugCancel, sceneInstanceID);
|
||||
|
||||
SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID);
|
||||
|
||||
// Must be done before removing aura
|
||||
RemoveSceneInstanceId(sceneInstanceID);
|
||||
|
||||
if (sceneTemplate.SceneId != 0)
|
||||
RemoveAurasDueToSceneId(sceneTemplate.SceneId);
|
||||
|
||||
Global.ScriptMgr.OnSceneCancel(GetPlayer(), sceneInstanceID, sceneTemplate);
|
||||
|
||||
if (sceneTemplate.PlaybackFlags.HasAnyFlag(SceneFlags.CancelAtEnd))
|
||||
CancelScene(sceneInstanceID, false);
|
||||
}
|
||||
|
||||
public void OnSceneComplete(uint sceneInstanceID)
|
||||
{
|
||||
if (!HasScene(sceneInstanceID))
|
||||
return;
|
||||
|
||||
if (_isDebuggingScenes)
|
||||
GetPlayer().SendSysMessage(CypherStrings.CommandSceneDebugComplete, sceneInstanceID);
|
||||
|
||||
SceneTemplate sceneTemplate = GetSceneTemplateFromInstanceId(sceneInstanceID);
|
||||
|
||||
// Must be done before removing aura
|
||||
RemoveSceneInstanceId(sceneInstanceID);
|
||||
|
||||
if (sceneTemplate.SceneId != 0)
|
||||
RemoveAurasDueToSceneId(sceneTemplate.SceneId);
|
||||
|
||||
Global.ScriptMgr.OnSceneComplete(GetPlayer(), sceneInstanceID, sceneTemplate);
|
||||
|
||||
if (sceneTemplate.PlaybackFlags.HasAnyFlag(SceneFlags.CancelAtEnd))
|
||||
CancelScene(sceneInstanceID, false);
|
||||
}
|
||||
|
||||
bool HasScene(uint sceneInstanceID, uint sceneScriptPackageId = 0)
|
||||
{
|
||||
var sceneTempalte = _scenesByInstance.LookupByKey(sceneInstanceID);
|
||||
|
||||
if (sceneTempalte != null)
|
||||
return sceneScriptPackageId == 0 || sceneScriptPackageId == sceneTempalte.ScenePackageId;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddInstanceIdToSceneMap(uint sceneInstanceID, SceneTemplate sceneTemplate)
|
||||
{
|
||||
_scenesByInstance[sceneInstanceID] = sceneTemplate;
|
||||
}
|
||||
|
||||
public void CancelSceneBySceneId(uint sceneId)
|
||||
{
|
||||
List<uint> instancesIds = new List<uint>();
|
||||
|
||||
foreach (var pair in _scenesByInstance)
|
||||
if (pair.Value.SceneId == sceneId)
|
||||
instancesIds.Add(pair.Key);
|
||||
|
||||
foreach (uint sceneInstanceID in instancesIds)
|
||||
CancelScene(sceneInstanceID);
|
||||
}
|
||||
|
||||
public void CancelSceneByPackageId(uint sceneScriptPackageId)
|
||||
{
|
||||
List<uint> instancesIds = new List<uint>();
|
||||
|
||||
foreach (var sceneTemplate in _scenesByInstance)
|
||||
if (sceneTemplate.Value.ScenePackageId == sceneScriptPackageId)
|
||||
instancesIds.Add(sceneTemplate.Key);
|
||||
|
||||
foreach (uint sceneInstanceID in instancesIds)
|
||||
CancelScene(sceneInstanceID);
|
||||
}
|
||||
|
||||
void RemoveSceneInstanceId(uint sceneInstanceID)
|
||||
{
|
||||
_scenesByInstance.Remove(sceneInstanceID);
|
||||
}
|
||||
|
||||
void RemoveAurasDueToSceneId(uint sceneId)
|
||||
{
|
||||
var scenePlayAuras = GetPlayer().GetAuraEffectsByType(AuraType.PlayScene);
|
||||
foreach (var scenePlayAura in scenePlayAuras)
|
||||
{
|
||||
if (scenePlayAura.GetMiscValue() == sceneId)
|
||||
{
|
||||
GetPlayer().RemoveAura(scenePlayAura.GetBase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SceneTemplate GetSceneTemplateFromInstanceId(uint sceneInstanceID)
|
||||
{
|
||||
return _scenesByInstance.LookupByKey(sceneInstanceID);
|
||||
}
|
||||
|
||||
public uint GetActiveSceneCount(uint sceneScriptPackageId = 0)
|
||||
{
|
||||
uint activeSceneCount = 0;
|
||||
|
||||
foreach (var sceneTemplate in _scenesByInstance.Values)
|
||||
if (sceneScriptPackageId == 0 || sceneTemplate.ScenePackageId == sceneScriptPackageId)
|
||||
++activeSceneCount;
|
||||
|
||||
return activeSceneCount;
|
||||
}
|
||||
|
||||
Player GetPlayer() { return _player; }
|
||||
|
||||
void RecreateScene(uint sceneScriptPackageId, SceneFlags playbackflags = SceneFlags.Unk16, Position position = null)
|
||||
{
|
||||
CancelSceneByPackageId(sceneScriptPackageId);
|
||||
PlaySceneByPackageId(sceneScriptPackageId, playbackflags, position);
|
||||
}
|
||||
|
||||
public Dictionary<uint, SceneTemplate> GetSceneTemplateByInstanceMap() { return _scenesByInstance; }
|
||||
|
||||
uint GetNewStandaloneSceneInstanceID() { return ++_standaloneSceneInstanceID; }
|
||||
|
||||
public void ToggleDebugSceneMode() { _isDebuggingScenes = !_isDebuggingScenes; }
|
||||
public bool IsInDebugSceneMode() { return _isDebuggingScenes; }
|
||||
|
||||
Player _player;
|
||||
Dictionary<uint, SceneTemplate> _scenesByInstance = new Dictionary<uint, SceneTemplate>();
|
||||
uint _standaloneSceneInstanceID;
|
||||
bool _isDebuggingScenes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 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.Network;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class SocialManager : Singleton<SocialManager>
|
||||
{
|
||||
SocialManager() { }
|
||||
|
||||
public const int FriendLimit = 50;
|
||||
public const int IgnoreLimit = 50;
|
||||
|
||||
public void GetFriendInfo(Player player, ObjectGuid friendGUID, FriendInfo friendInfo)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
friendInfo.Status = FriendStatus.Offline;
|
||||
friendInfo.Area = 0;
|
||||
friendInfo.Level = 0;
|
||||
friendInfo.Class = 0;
|
||||
|
||||
Player target = Global.ObjAccessor.FindPlayer(friendGUID);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
var playerFriendInfo = player.GetSocial()._playerSocialMap.LookupByKey(friendGUID);
|
||||
if (playerFriendInfo != null)
|
||||
friendInfo.Note = playerFriendInfo.Note;
|
||||
|
||||
// PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters
|
||||
// MODERATOR, GAME MASTER, ADMINISTRATOR can see all
|
||||
|
||||
if (!player.GetSession().HasPermission(RBACPermissions.WhoSeeAllSecLevels) &&
|
||||
target.GetSession().GetSecurity() > (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInWhoList))
|
||||
return;
|
||||
|
||||
// player can see member of other team only if CONFIG_ALLOW_TWO_SIDE_WHO_LIST
|
||||
if (target.GetTeam() != player.GetTeam() && !player.GetSession().HasPermission(RBACPermissions.TwoSideWhoList))
|
||||
return;
|
||||
|
||||
if (target.IsVisibleGloballyFor(player))
|
||||
{
|
||||
if (target.isDND())
|
||||
friendInfo.Status = FriendStatus.DND;
|
||||
else if (target.isAFK())
|
||||
friendInfo.Status = FriendStatus.AFK;
|
||||
else
|
||||
friendInfo.Status = FriendStatus.Online;
|
||||
|
||||
friendInfo.Area = target.GetZoneId();
|
||||
friendInfo.Level = target.getLevel();
|
||||
friendInfo.Class = target.GetClass();
|
||||
}
|
||||
}
|
||||
|
||||
public void SendFriendStatus(Player player, FriendsResult result, ObjectGuid friendGuid, bool broadcast = false)
|
||||
{
|
||||
FriendInfo fi = new FriendInfo();
|
||||
GetFriendInfo(player, friendGuid, fi);
|
||||
|
||||
FriendStatusPkt friendStatus = new FriendStatusPkt();
|
||||
friendStatus.Initialize(friendGuid, result, fi);
|
||||
|
||||
if (broadcast)
|
||||
BroadcastToFriendListers(player, friendStatus);
|
||||
else
|
||||
player.SendPacket(friendStatus);
|
||||
}
|
||||
|
||||
void BroadcastToFriendListers(Player player, ServerPacket packet)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
AccountTypes gmSecLevel = (AccountTypes)WorldConfig.GetIntValue(WorldCfg.GmLevelInWhoList);
|
||||
foreach (var pair in _socialMap)
|
||||
{
|
||||
var info = pair.Value._playerSocialMap.LookupByKey(player.GetGUID());
|
||||
if (info != null && info.Flags.HasAnyFlag(SocialFlag.Friend))
|
||||
{
|
||||
Player target = Global.ObjAccessor.FindPlayer(pair.Key);
|
||||
if (!target || !target.IsInWorld)
|
||||
continue;
|
||||
|
||||
WorldSession session = target.GetSession();
|
||||
if (!session.HasPermission(RBACPermissions.WhoSeeAllSecLevels) && player.GetSession().GetSecurity() > gmSecLevel)
|
||||
continue;
|
||||
|
||||
if (target.GetTeam() != player.GetTeam() && !session.HasPermission(RBACPermissions.TwoSideWhoList))
|
||||
continue;
|
||||
|
||||
if (player.IsVisibleGloballyFor(target))
|
||||
session.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerSocial LoadFromDB(SQLResult result, ObjectGuid guid)
|
||||
{
|
||||
PlayerSocial social = new PlayerSocial();
|
||||
social.SetPlayerGUID(guid);
|
||||
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
ObjectGuid friendGuid = ObjectGuid.Create(HighGuid.Player, result.Read<ulong>(0));
|
||||
ObjectGuid friendAccountGuid = ObjectGuid.Create(HighGuid.WowAccount, result.Read<uint>(1));
|
||||
SocialFlag flags = (SocialFlag)result.Read<byte>(2);
|
||||
|
||||
social._playerSocialMap[friendGuid] = new FriendInfo(friendAccountGuid, flags, result.Read<string>(3));
|
||||
}
|
||||
while (result.NextRow());
|
||||
}
|
||||
|
||||
_socialMap[guid] = social;
|
||||
|
||||
return social;
|
||||
}
|
||||
|
||||
public void RemovePlayerSocial(ObjectGuid guid) { _socialMap.Remove(guid); }
|
||||
|
||||
Dictionary<ObjectGuid, PlayerSocial> _socialMap = new Dictionary<ObjectGuid, PlayerSocial>();
|
||||
}
|
||||
|
||||
public class PlayerSocial
|
||||
{
|
||||
uint GetNumberOfSocialsWithFlag(SocialFlag flag)
|
||||
{
|
||||
uint counter = 0;
|
||||
foreach (var pair in _playerSocialMap)
|
||||
if (pair.Value.Flags.HasAnyFlag(flag))
|
||||
++counter;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
public bool AddToSocialList(ObjectGuid friendGuid, SocialFlag flag)
|
||||
{
|
||||
// check client limits
|
||||
if (GetNumberOfSocialsWithFlag(flag) >= (((flag & SocialFlag.Friend) != 0) ? SocialManager.FriendLimit : SocialManager.IgnoreLimit))
|
||||
return false;
|
||||
|
||||
var friendInfo = _playerSocialMap.LookupByKey(friendGuid);
|
||||
if (friendInfo != null)
|
||||
{
|
||||
friendInfo.Flags |= flag;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS);
|
||||
stmt.AddValue(0, friendInfo.Flags);
|
||||
stmt.AddValue(1, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(2, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
FriendInfo fi = new FriendInfo();
|
||||
fi.Flags |= flag;
|
||||
_playerSocialMap[friendGuid] = fi;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHARACTER_SOCIAL);
|
||||
stmt.AddValue(0, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(1, friendGuid.GetCounter());
|
||||
stmt.AddValue(2, flag);
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveFromSocialList(ObjectGuid friendGuid, SocialFlag flag)
|
||||
{
|
||||
var friendInfo = _playerSocialMap.LookupByKey(friendGuid);
|
||||
if (friendInfo == null) // not exist
|
||||
return;
|
||||
|
||||
friendInfo.Flags &= ~flag;
|
||||
|
||||
if (friendInfo.Flags == 0)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHARACTER_SOCIAL);
|
||||
stmt.AddValue(0, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(1, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
|
||||
_playerSocialMap.Remove(friendGuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_FLAGS);
|
||||
stmt.AddValue(0, friendInfo.Flags);
|
||||
stmt.AddValue(1, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(2, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFriendNote(ObjectGuid friendGuid, string note)
|
||||
{
|
||||
if (!_playerSocialMap.ContainsKey(friendGuid)) // not exist
|
||||
return;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHARACTER_SOCIAL_NOTE);
|
||||
stmt.AddValue(0, note);
|
||||
stmt.AddValue(1, GetPlayerGUID().GetCounter());
|
||||
stmt.AddValue(2, friendGuid.GetCounter());
|
||||
DB.Characters.Execute(stmt);
|
||||
|
||||
_playerSocialMap[friendGuid].Note = note;
|
||||
}
|
||||
|
||||
public void SendSocialList(Player player, SocialFlag flags)
|
||||
{
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
ContactList contactList = new ContactList();
|
||||
contactList.Flags = flags;
|
||||
|
||||
foreach (var v in _playerSocialMap)
|
||||
{
|
||||
if (!v.Value.Flags.HasAnyFlag(flags))
|
||||
continue;
|
||||
|
||||
Global.SocialMgr.GetFriendInfo(player, v.Key, v.Value);
|
||||
|
||||
contactList.Contacts.Add(new ContactInfo(v.Key, v.Value));
|
||||
|
||||
// client's friends list and ignore list limit
|
||||
if (contactList.Contacts.Count >= (((flags & SocialFlag.Friend) != 0) ? SocialManager.FriendLimit : SocialManager.IgnoreLimit))
|
||||
break;
|
||||
}
|
||||
|
||||
player.SendPacket(contactList);
|
||||
}
|
||||
|
||||
bool _HasContact(ObjectGuid guid, SocialFlag flags)
|
||||
{
|
||||
var friendInfo = _playerSocialMap.LookupByKey(guid);
|
||||
if (friendInfo != null)
|
||||
return friendInfo.Flags.HasAnyFlag(flags);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasFriend(ObjectGuid friendGuid)
|
||||
{
|
||||
return _HasContact(friendGuid, SocialFlag.Friend);
|
||||
}
|
||||
|
||||
public bool HasIgnore(ObjectGuid ignoreGuid)
|
||||
{
|
||||
return _HasContact(ignoreGuid, SocialFlag.Ignored);
|
||||
}
|
||||
|
||||
ObjectGuid GetPlayerGUID() { return m_playerGUID; }
|
||||
|
||||
public void SetPlayerGUID(ObjectGuid guid) { m_playerGUID = guid; }
|
||||
|
||||
public Dictionary<ObjectGuid, FriendInfo> _playerSocialMap = new Dictionary<ObjectGuid, FriendInfo>();
|
||||
ObjectGuid m_playerGUID;
|
||||
}
|
||||
|
||||
public class FriendInfo
|
||||
{
|
||||
public FriendInfo()
|
||||
{
|
||||
Status = FriendStatus.Offline;
|
||||
Note = "";
|
||||
}
|
||||
|
||||
public FriendInfo(ObjectGuid accountGuid, SocialFlag flags, string note)
|
||||
{
|
||||
WowAccountGuid = accountGuid;
|
||||
Status = FriendStatus.Offline;
|
||||
Flags = flags;
|
||||
Note = note;
|
||||
}
|
||||
|
||||
public ObjectGuid WowAccountGuid;
|
||||
public FriendStatus Status;
|
||||
public SocialFlag Flags;
|
||||
public uint Area;
|
||||
public uint Level;
|
||||
public Class Class;
|
||||
public string Note;
|
||||
}
|
||||
|
||||
public enum FriendStatus
|
||||
{
|
||||
Offline = 0x00,
|
||||
Online = 0x01,
|
||||
AFK = 0x02,
|
||||
DND = 0x04,
|
||||
RAF = 0x08
|
||||
}
|
||||
|
||||
public enum SocialFlag
|
||||
{
|
||||
Friend = 0x01,
|
||||
Ignored = 0x02,
|
||||
Muted = 0x04, // guessed
|
||||
Unk = 0x08, // Unknown - does not appear to be RaF
|
||||
All = Friend | Ignored | Muted
|
||||
}
|
||||
|
||||
public enum FriendsResult
|
||||
{
|
||||
DbError = 0x00,
|
||||
ListFull = 0x01,
|
||||
Online = 0x02,
|
||||
Offline = 0x03,
|
||||
NotFound = 0x04,
|
||||
Removed = 0x05,
|
||||
AddedOnline = 0x06,
|
||||
AddedOffline = 0x07,
|
||||
Already = 0x08,
|
||||
Self = 0x09,
|
||||
Enemy = 0x0a,
|
||||
IgnoreFull = 0x0b,
|
||||
IgnoreSelf = 0x0c,
|
||||
IgnoreNotFound = 0x0d,
|
||||
IgnoreAlready = 0x0e,
|
||||
IgnoreAdded = 0x0f,
|
||||
IgnoreRemoved = 0x10,
|
||||
IgnoreAmbiguous = 0x11, // That Name Is Ambiguous, Type More Of The Player'S Server Name
|
||||
MuteFull = 0x12,
|
||||
MuteSelf = 0x13,
|
||||
MuteNotFound = 0x14,
|
||||
MuteAlready = 0x15,
|
||||
MuteAdded = 0x16,
|
||||
MuteRemoved = 0x17,
|
||||
MuteAmbiguous = 0x18, // That Name Is Ambiguous, Type More Of The Player'S Server Name
|
||||
Unk1 = 0x19, // no message at client
|
||||
Unk2 = 0x1A,
|
||||
Unk3 = 0x1B,
|
||||
Unknown = 0x1C // Unknown friend response from server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.Network.Packets;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class TradeData
|
||||
{
|
||||
public TradeData(Player player, Player trader)
|
||||
{
|
||||
m_player = player;
|
||||
m_trader = trader;
|
||||
m_clientStateIndex = 1;
|
||||
m_serverStateIndex = 1;
|
||||
}
|
||||
|
||||
public TradeData GetTraderData()
|
||||
{
|
||||
return m_trader.GetTradeData();
|
||||
}
|
||||
|
||||
public Item GetItem(TradeSlots slot)
|
||||
{
|
||||
return !m_items[(int)slot].IsEmpty() ? m_player.GetItemByGuid(m_items[(int)slot]) : null;
|
||||
}
|
||||
|
||||
public bool HasItem(ObjectGuid itemGuid)
|
||||
{
|
||||
for (byte i = 0; i < (byte)TradeSlots.Count; ++i)
|
||||
if (m_items[i] == itemGuid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid)
|
||||
{
|
||||
for (TradeSlots i = 0; i < TradeSlots.Count; ++i)
|
||||
if (m_items[(int)i] == itemGuid)
|
||||
return i;
|
||||
|
||||
return TradeSlots.Invalid;
|
||||
}
|
||||
|
||||
public Item GetSpellCastItem()
|
||||
{
|
||||
return !m_spellCastItem.IsEmpty() ? m_player.GetItemByGuid(m_spellCastItem) : null;
|
||||
}
|
||||
|
||||
public void SetItem(TradeSlots slot, Item item, bool update = false)
|
||||
{
|
||||
ObjectGuid itemGuid = item ? item.GetGUID() : ObjectGuid.Empty;
|
||||
|
||||
if (m_items[(int)slot] == itemGuid && !update)
|
||||
return;
|
||||
|
||||
m_items[(int)slot] = itemGuid;
|
||||
|
||||
SetAccepted(false);
|
||||
GetTraderData().SetAccepted(false);
|
||||
|
||||
UpdateServerStateIndex();
|
||||
|
||||
Update();
|
||||
|
||||
// need remove possible trader spell applied to changed item
|
||||
if (slot == TradeSlots.NonTraded)
|
||||
GetTraderData().SetSpell(0);
|
||||
|
||||
// need remove possible player spell applied (possible move reagent)
|
||||
SetSpell(0);
|
||||
}
|
||||
|
||||
public uint GetSpell() { return m_spell; }
|
||||
|
||||
public void SetSpell(uint spell_id, Item castItem = null)
|
||||
{
|
||||
ObjectGuid itemGuid = castItem ? castItem.GetGUID() : ObjectGuid.Empty;
|
||||
|
||||
if (m_spell == spell_id && m_spellCastItem == itemGuid)
|
||||
return;
|
||||
|
||||
m_spell = spell_id;
|
||||
m_spellCastItem = itemGuid;
|
||||
|
||||
SetAccepted(false);
|
||||
GetTraderData().SetAccepted(false);
|
||||
|
||||
UpdateServerStateIndex();
|
||||
|
||||
Update(true); // send spell info to item owner
|
||||
Update(false); // send spell info to caster self
|
||||
}
|
||||
|
||||
public void SetMoney(ulong money)
|
||||
{
|
||||
if (m_money == money)
|
||||
return;
|
||||
|
||||
if (!m_player.HasEnoughMoney(money))
|
||||
{
|
||||
TradeStatusPkt info = new TradeStatusPkt();
|
||||
info.Status = TradeStatus.Failed;
|
||||
info.BagResult = InventoryResult.NotEnoughMoney;
|
||||
m_player.GetSession().SendTradeStatus(info);
|
||||
return;
|
||||
}
|
||||
m_money = money;
|
||||
|
||||
SetAccepted(false);
|
||||
GetTraderData().SetAccepted(false);
|
||||
|
||||
UpdateServerStateIndex();
|
||||
|
||||
Update(true);
|
||||
}
|
||||
|
||||
void Update(bool forTarget = true)
|
||||
{
|
||||
if (forTarget)
|
||||
m_trader.GetSession().SendUpdateTrade(true); // player state for trader
|
||||
else
|
||||
m_player.GetSession().SendUpdateTrade(false); // player state for player
|
||||
}
|
||||
|
||||
public void SetAccepted(bool state, bool crosssend = false)
|
||||
{
|
||||
m_accepted = state;
|
||||
|
||||
if (!state)
|
||||
{
|
||||
TradeStatusPkt info = new TradeStatusPkt();
|
||||
info.Status = TradeStatus.Unaccepted;
|
||||
if (crosssend)
|
||||
m_trader.GetSession().SendTradeStatus(info);
|
||||
else
|
||||
m_player.GetSession().SendTradeStatus(info);
|
||||
}
|
||||
}
|
||||
|
||||
public Player GetTrader() { return m_trader; }
|
||||
|
||||
public bool HasSpellCastItem() { return !m_spellCastItem.IsEmpty(); }
|
||||
|
||||
public ulong GetMoney() { return m_money; }
|
||||
|
||||
public bool IsAccepted() { return m_accepted; }
|
||||
|
||||
public bool IsInAcceptProcess() { return m_acceptProccess; }
|
||||
|
||||
public void SetInAcceptProcess(bool state) { m_acceptProccess = state; }
|
||||
|
||||
public uint GetClientStateIndex() { return m_clientStateIndex; }
|
||||
public void UpdateClientStateIndex() { ++m_clientStateIndex; }
|
||||
|
||||
public uint GetServerStateIndex() { return m_serverStateIndex; }
|
||||
public void UpdateServerStateIndex() { m_serverStateIndex = RandomHelper.Rand32(); }
|
||||
|
||||
Player m_player;
|
||||
Player m_trader;
|
||||
bool m_accepted;
|
||||
bool m_acceptProccess;
|
||||
ulong m_money;
|
||||
uint m_spell;
|
||||
ObjectGuid m_spellCastItem;
|
||||
ObjectGuid[] m_items = new ObjectGuid[(int)TradeSlots.Count];
|
||||
uint m_clientStateIndex;
|
||||
uint m_serverStateIndex;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user