7379 lines
319 KiB
C#
7379 lines
319 KiB
C#
/*
|
|
* Copyright (C) 2012-2018 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.Achievements;
|
|
using Game.AI;
|
|
using Game.Arenas;
|
|
using Game.BattleFields;
|
|
using Game.BattleGrounds;
|
|
using Game.Chat;
|
|
using Game.DataStorage;
|
|
using Game.Garrisons;
|
|
using Game.Groups;
|
|
using Game.Guilds;
|
|
using Game.Loots;
|
|
using Game.Mails;
|
|
using Game.Maps;
|
|
using Game.Misc;
|
|
using Game.Network;
|
|
using Game.Network.Packets;
|
|
using Game.PvP;
|
|
using Game.Spells;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Linq;
|
|
|
|
namespace Game.Entities
|
|
{
|
|
public partial class Player : Unit
|
|
{
|
|
public Player(WorldSession session) : base(true)
|
|
{
|
|
objectTypeMask |= TypeMask.Player;
|
|
objectTypeId = TypeId.Player;
|
|
|
|
valuesCount = (int)PlayerFields.End;
|
|
_dynamicValuesCount = (int)PlayerDynamicFields.End;
|
|
Session = session;
|
|
|
|
// players always accept
|
|
if (!GetSession().HasPermission(RBACPermissions.CanFilterWhispers))
|
|
SetAcceptWhispers(true);
|
|
|
|
m_zoneUpdateId = 0xffffffff;
|
|
m_nextSave = WorldConfig.GetUIntValue(WorldCfg.IntervalSave);
|
|
|
|
SetGroupInvite(null);
|
|
|
|
atLoginFlags = AtLoginFlags.None;
|
|
PlayerTalkClass = new PlayerMenu(session);
|
|
m_currentBuybackSlot = InventorySlots.BuyBackStart;
|
|
|
|
for (byte i = 0; i < (int)MirrorTimerType.Max; i++)
|
|
m_MirrorTimer[i] = -1;
|
|
|
|
m_logintime = Time.UnixTime;
|
|
m_Last_tick = m_logintime;
|
|
|
|
for (byte i = 0; i < (int)Difficulty.Max; ++i)
|
|
m_boundInstances[i] = new Dictionary<uint, InstanceBind>();
|
|
|
|
m_dungeonDifficulty = Difficulty.Normal;
|
|
m_raidDifficulty = Difficulty.NormalRaid;
|
|
m_legacyRaidDifficulty = Difficulty.Raid10N;
|
|
m_prevMapDifficulty = Difficulty.NormalRaid;
|
|
m_InstanceValid = true;
|
|
|
|
_specializationInfo = new SpecializationInfo();
|
|
|
|
for (byte i = 0; i < (byte)BaseModGroup.End; ++i)
|
|
m_auraBaseMod[i] = new float[] { 0.0f, 1.0f };
|
|
|
|
for (var i = 0; i < (int)SpellModOp.Max; ++i)
|
|
{
|
|
m_spellMods[i] = new List<SpellModifier>[(int)SpellModType.End];
|
|
|
|
for (var c = 0; c < (int)SpellModType.End; ++c)
|
|
m_spellMods[i][c] = new List<SpellModifier>();
|
|
}
|
|
|
|
// Honor System
|
|
m_lastHonorUpdateTime = Time.UnixTime;
|
|
|
|
m_unitMovedByMe = this;
|
|
m_playerMovingMe = this;
|
|
seerView = this;
|
|
|
|
m_isActive = true;
|
|
m_ControlledByPlayer = true;
|
|
|
|
Global.WorldMgr.IncreasePlayerCount();
|
|
|
|
_cinematicMgr = new CinematicManager(this);
|
|
|
|
m_achievementSys = new PlayerAchievementMgr(this);
|
|
reputationMgr = new ReputationMgr(this);
|
|
m_questObjectiveCriteriaMgr = new QuestObjectiveCriteriaManager(this);
|
|
m_sceneMgr = new SceneMgr(this);
|
|
|
|
m_bgBattlegroundQueueID[0] = new BgBattlegroundQueueID_Rec();
|
|
m_bgBattlegroundQueueID[1] = new BgBattlegroundQueueID_Rec();
|
|
|
|
m_bgData = new BGData();
|
|
|
|
_restMgr = new RestMgr(this);
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
// Note: buy back item already deleted from DB when player was saved
|
|
for (byte i = 0; i < (int)PlayerSlots.Count; ++i)
|
|
{
|
|
if (m_items[i] != null)
|
|
m_items[i].Dispose();
|
|
}
|
|
|
|
m_spells.Clear();
|
|
_specializationInfo = null;
|
|
m_mail.Clear();
|
|
|
|
foreach (var item in mMitems.Values)
|
|
item.Dispose();
|
|
|
|
PlayerTalkClass.ClearMenus();
|
|
ItemSetEff.Clear();
|
|
|
|
_declinedname = null;
|
|
m_runes = null;
|
|
m_achievementSys = null;
|
|
reputationMgr = null;
|
|
|
|
_cinematicMgr.Dispose();
|
|
|
|
for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i)
|
|
_voidStorageItems[i] = null;
|
|
|
|
ClearResurrectRequestData();
|
|
|
|
Global.WorldMgr.DecreasePlayerCount();
|
|
|
|
base.Dispose();
|
|
}
|
|
|
|
//Core
|
|
public bool Create(ulong guidlow, CharacterCreateInfo createInfo)
|
|
{
|
|
_Create(ObjectGuid.Create(HighGuid.Player, guidlow));
|
|
|
|
SetName(createInfo.Name);
|
|
|
|
PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(createInfo.RaceId, createInfo.ClassId);
|
|
if (info == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "PlayerCreate: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with an invalid race/class pair ({2}/{3}) - refusing to do so.",
|
|
GetSession().GetAccountId(), GetName(), createInfo.RaceId, createInfo.ClassId);
|
|
return false;
|
|
}
|
|
|
|
Relocate(info.PositionX, info.PositionY, info.PositionZ, info.Orientation);
|
|
|
|
var cEntry = CliDB.ChrClassesStorage.LookupByKey(createInfo.ClassId);
|
|
if (cEntry == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "PlayerCreate: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with an invalid character class ({2}) - refusing to do so (wrong DBC-files?)",
|
|
GetSession().GetAccountId(), GetName(), createInfo.ClassId);
|
|
return false;
|
|
}
|
|
|
|
if (!ValidateAppearance(createInfo.RaceId, createInfo.ClassId, createInfo.Sex, createInfo.HairStyle, createInfo.HairColor, createInfo.Face, createInfo.FacialHairStyle, createInfo.Skin, createInfo.CustomDisplay, true))
|
|
{
|
|
Log.outError(LogFilter.Player, "Player.Create: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with invalid appearance attributes - refusing to do so",
|
|
GetSession().GetAccountId(), GetName());
|
|
return false;
|
|
}
|
|
|
|
SetMap(Global.MapMgr.CreateMap(info.MapId, this));
|
|
|
|
int powertype = (int)cEntry.PowerType;
|
|
|
|
SetObjectScale(1.0f);
|
|
|
|
SetFactionForRace(createInfo.RaceId);
|
|
|
|
if (!IsValidGender(createInfo.Sex))
|
|
{
|
|
Log.outError(LogFilter.Player, "Player:Create: Possible hacking-attempt: Account {0} tried creating a character named '{1}' with an invalid gender ({2}) - refusing to do so",
|
|
GetSession().GetAccountId(), GetName(), createInfo.Sex);
|
|
return false;
|
|
}
|
|
|
|
SetByteValue(UnitFields.Bytes0, 0, (byte)createInfo.RaceId);
|
|
SetByteValue(UnitFields.Bytes0, 1, (byte)createInfo.ClassId);
|
|
SetByteValue(UnitFields.Bytes0, 3, (byte)createInfo.Sex);
|
|
SetUInt32Value(UnitFields.DisplayPower, (uint)powertype);
|
|
InitDisplayIds();
|
|
if ((RealmType)WorldConfig.GetIntValue(WorldCfg.GameType) == RealmType.PVP || (RealmType)WorldConfig.GetIntValue(WorldCfg.GameType) == RealmType.RPPVP)
|
|
{
|
|
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP);
|
|
SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable);
|
|
}
|
|
|
|
SetFlag(UnitFields.Flags2, UnitFlags2.RegeneratePower);
|
|
SetFloatValue(UnitFields.HoverHeight, 1.0f); // default for players in 3.0.3
|
|
|
|
SetUInt32Value(PlayerFields.WatchedFactionIndex, 0xFFFFFFFF); // -1 is default value
|
|
|
|
SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId, createInfo.Skin);
|
|
SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId, createInfo.Face);
|
|
SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId, createInfo.HairStyle);
|
|
SetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId, createInfo.HairColor);
|
|
SetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle, createInfo.FacialHairStyle);
|
|
for (byte i = 0; i < PlayerConst.CustomDisplaySize; ++i)
|
|
SetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i), createInfo.CustomDisplay[i]);
|
|
SetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateXp, (uint)((GetSession().IsARecruiter() || GetSession().GetRecruiterId() != 0) ? PlayerRestState.RAFLinked : PlayerRestState.NotRAFLinked));
|
|
SetUInt32Value(PlayerFields.RestInfo + PlayerFieldOffsets.RestStateHonor, (uint)PlayerRestState.NotRAFLinked);
|
|
SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender, (byte)createInfo.Sex);
|
|
SetByteValue(PlayerFields.Bytes4, PlayerFieldOffsets.Bytes4OffsetArenaFaction, 0);
|
|
|
|
SetGuidValue(ObjectFields.Data, ObjectGuid.Empty);
|
|
SetUInt32Value(PlayerFields.GuildRank, 0);
|
|
SetGuildLevel(0);
|
|
SetUInt32Value(PlayerFields.GuildTimestamp, 0);
|
|
|
|
for (int i = 0; i < PlayerConst.KnowTitlesSize; ++i)
|
|
SetUInt64Value(PlayerFields.KnownTitles + i, 0); // 0=disabled
|
|
SetUInt32Value(PlayerFields.ChosenTitle, 0);
|
|
|
|
SetUInt32Value(PlayerFields.Kills, 0);
|
|
SetUInt32Value(PlayerFields.LifetimeHonorableKills, 0);
|
|
|
|
// set starting level
|
|
uint start_level = WorldConfig.GetUIntValue(WorldCfg.StartPlayerLevel);
|
|
if (GetClass() == Class.Deathknight)
|
|
start_level= WorldConfig.GetUIntValue(WorldCfg.StartDeathKnightPlayerLevel);
|
|
else if (GetClass() == Class.DemonHunter)
|
|
start_level = WorldConfig.GetUIntValue(WorldCfg.StartDemonHunterPlayerLevel);
|
|
|
|
if (createInfo.TemplateSet.HasValue)
|
|
{
|
|
if (GetSession().HasPermission(RBACPermissions.UseCharacterTemplates))
|
|
{
|
|
CharacterTemplate charTemplate = Global.CharacterTemplateDataStorage.GetCharacterTemplate(createInfo.TemplateSet.Value);
|
|
if (charTemplate != null)
|
|
{
|
|
if (charTemplate.Level > start_level)
|
|
start_level = charTemplate.Level;
|
|
}
|
|
}
|
|
else
|
|
Log.outWarn(LogFilter.Cheat, "Account: {0} (IP: {1}) tried to use a character template without given permission. Possible cheating attempt.", GetSession().GetAccountId(), GetSession().GetRemoteAddress());
|
|
}
|
|
|
|
if (GetSession().HasPermission(RBACPermissions.UseStartGmLevel))
|
|
{
|
|
uint gm_level = WorldConfig.GetUIntValue(WorldCfg.StartGmLevel);
|
|
if (gm_level > start_level)
|
|
start_level = gm_level;
|
|
}
|
|
|
|
SetUInt32Value(UnitFields.Level, start_level);
|
|
|
|
InitRunes();
|
|
|
|
SetUInt64Value(PlayerFields.Coinage, WorldConfig.GetUIntValue(WorldCfg.StartPlayerMoney));
|
|
SetCurrency(CurrencyTypes.ApexisCrystals, WorldConfig.GetUIntValue(WorldCfg.CurrencyStartApexisCrystals));
|
|
SetCurrency(CurrencyTypes.JusticePoints, WorldConfig.GetUIntValue(WorldCfg.CurrencyStartJusticePoints));
|
|
|
|
// start with every map explored
|
|
if (WorldConfig.GetBoolValue(WorldCfg.StartAllExplored))
|
|
{
|
|
for (ushort i = 0; i < PlayerConst.ExploredZonesSize; i++)
|
|
SetFlag(PlayerFields.ExploredZones1 + i, 0xFFFFFFFF);
|
|
}
|
|
|
|
//Reputations if "StartAllReputation" is enabled, -- TODO: Fix this in a better way
|
|
if (WorldConfig.GetBoolValue(WorldCfg.StartAllRep))
|
|
{
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(942), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(935), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(936), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(1011), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(970), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(967), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(989), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(932), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(934), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(1038), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(1077), 42999);
|
|
|
|
// Factions depending on team, like cities and some more stuff
|
|
switch (GetTeam())
|
|
{
|
|
case Team.Alliance:
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(72), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(47), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(69), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(930), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(730), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(978), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(54), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(946), 42999);
|
|
break;
|
|
case Team.Horde:
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(76), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(68), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(81), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(911), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(729), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(941), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(530), 42999);
|
|
GetReputationMgr().SetReputation(CliDB.FactionStorage.LookupByKey(947), 42999);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Played time
|
|
m_Last_tick = Time.UnixTime;
|
|
m_PlayedTimeTotal = 0;
|
|
m_PlayedTimeLevel = 0;
|
|
|
|
// base stats and related field values
|
|
InitStatsForLevel();
|
|
InitTaxiNodesForLevel();
|
|
InitTalentForLevel();
|
|
InitPrimaryProfessions(); // to max set before any spell added
|
|
|
|
// apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods()
|
|
UpdateMaxHealth(); // Update max Health (for add bonus from stamina)
|
|
SetFullHealth();
|
|
if (GetPowerType() == PowerType.Mana)
|
|
SetFullPower(PowerType.Mana);
|
|
|
|
// original spells
|
|
LearnDefaultSkills();
|
|
LearnCustomSpells();
|
|
|
|
// Original action bar. Do not use Player.AddActionButton because we do not have skill spells loaded at this time
|
|
// but checks will still be performed later when loading character from db in Player._LoadActions
|
|
foreach (var action in info.action)
|
|
{
|
|
// create new button
|
|
ActionButton ab = new ActionButton();
|
|
|
|
// set data
|
|
ab.SetActionAndType(action.action, (ActionButtonType)action.type);
|
|
|
|
m_actionButtons[action.button] = ab;
|
|
}
|
|
|
|
// original items
|
|
CharStartOutfitRecord oEntry = Global.DB2Mgr.GetCharStartOutfitEntry((byte)createInfo.RaceId, (byte)createInfo.ClassId, (byte)createInfo.Sex);
|
|
if (oEntry != null)
|
|
{
|
|
for (int j = 0; j < ItemConst.MaxOutfitItems - 1; ++j)
|
|
{
|
|
if (oEntry.ItemID[j] <= 0)
|
|
continue;
|
|
|
|
uint itemId = (uint)oEntry.ItemID[j];
|
|
|
|
ItemTemplate iProto = Global.ObjectMgr.GetItemTemplate(itemId);
|
|
if (iProto == null)
|
|
continue;
|
|
|
|
// BuyCount by default
|
|
uint count = iProto.GetBuyCount();
|
|
|
|
// special amount for food/drink
|
|
if (iProto.GetClass() == ItemClass.Consumable && iProto.GetSubClass() == (uint)ItemSubClassConsumable.FoodDrink)
|
|
{
|
|
if (!iProto.Effects.Empty())
|
|
{
|
|
switch (iProto.Effects[0].Category)
|
|
{
|
|
case 11: // food
|
|
count = (uint)(GetClass() == Class.Deathknight ? 10 : 4);
|
|
break;
|
|
case 59: // drink
|
|
count = 2;
|
|
break;
|
|
}
|
|
}
|
|
if (iProto.GetMaxStackSize() < count)
|
|
count = iProto.GetMaxStackSize();
|
|
}
|
|
StoreNewItemInBestSlots(itemId, count);
|
|
}
|
|
}
|
|
foreach (var item in info.item)
|
|
StoreNewItemInBestSlots(item.item_id, item.item_amount);
|
|
|
|
// bags and main-hand weapon must equipped at this moment
|
|
// now second pass for not equipped (offhand weapon/shield if it attempt equipped before main-hand weapon)
|
|
for (var i = InventorySlots.ItemStart; i < InventorySlots.ItemEnd; i++)
|
|
{
|
|
Item pItem = GetItemByPos(InventorySlots.Bag0, i);
|
|
if (pItem != null)
|
|
{
|
|
ushort eDest;
|
|
// equip offhand weapon/shield if it attempt equipped before main-hand weapon
|
|
InventoryResult msg = CanEquipItem(ItemConst.NullSlot, out eDest, pItem, false);
|
|
if (msg == InventoryResult.Ok)
|
|
{
|
|
RemoveItem(InventorySlots.Bag0, i, true);
|
|
EquipItem(eDest, pItem, true);
|
|
}
|
|
// move other items to more appropriate slots
|
|
else
|
|
{
|
|
List<ItemPosCount> sDest = new List<ItemPosCount>();
|
|
msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sDest, pItem, false);
|
|
if (msg == InventoryResult.Ok)
|
|
{
|
|
RemoveItem(InventorySlots.Bag0, i, true);
|
|
pItem = StoreItem(sDest, pItem, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// all item positions resolved
|
|
|
|
ChrSpecializationRecord defaultSpec = Global.DB2Mgr.GetDefaultChrSpecializationForClass(GetClass());
|
|
if (defaultSpec != null)
|
|
{
|
|
SetActiveTalentGroup(defaultSpec.OrderIndex);
|
|
SetPrimarySpecialization(defaultSpec.Id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
public override void Update(uint diff)
|
|
{
|
|
if (!IsInWorld)
|
|
return;
|
|
|
|
// undelivered mail
|
|
if (m_nextMailDelivereTime != 0 && m_nextMailDelivereTime <= Time.UnixTime)
|
|
{
|
|
SendNewMail();
|
|
++unReadMails;
|
|
|
|
// It will be recalculate at mailbox open (for unReadMails important non-0 until mailbox open, it also will be recalculated)
|
|
m_nextMailDelivereTime = 0;
|
|
}
|
|
// If this is set during update SetSpellModTakingSpell call is missing somewhere in the code
|
|
// Having this would prevent more aura charges to be dropped, so let's crash
|
|
if (m_spellModTakingSpell != null)
|
|
{
|
|
Log.outFatal(LogFilter.Spells, "Player has m_spellModTakingSpell {0} during update!", m_spellModTakingSpell.m_spellInfo.Id);
|
|
m_spellModTakingSpell = null;
|
|
}
|
|
|
|
// Update cinematic location, if 500ms have passed and we're doing a cinematic now.
|
|
_cinematicMgr.m_cinematicDiff += diff;
|
|
if (_cinematicMgr.m_activeCinematicCameraId != 0 && Time.GetMSTimeDiffToNow(_cinematicMgr.m_lastCinematicCheck) > 500)
|
|
{
|
|
_cinematicMgr.m_lastCinematicCheck = Time.GetMSTime();
|
|
_cinematicMgr.UpdateCinematicLocation(diff);
|
|
}
|
|
|
|
//used to implement delayed far teleports
|
|
SetCanDelayTeleport(true);
|
|
base.Update(diff);
|
|
SetCanDelayTeleport(false);
|
|
|
|
long now = Time.UnixTime;
|
|
|
|
UpdatePvPFlag(now);
|
|
|
|
UpdateContestedPvP(diff);
|
|
|
|
UpdateDuelFlag(now);
|
|
|
|
CheckDuelDistance(now);
|
|
|
|
UpdateAfkReport(now);
|
|
|
|
if (IsAIEnabled && GetAI() != null)
|
|
GetAI().UpdateAI(diff);
|
|
else if (NeedChangeAI)
|
|
{
|
|
UpdateCharmAI();
|
|
NeedChangeAI = false;
|
|
IsAIEnabled = GetAI() != null;
|
|
}
|
|
|
|
// Update items that have just a limited lifetime
|
|
if (now > m_Last_tick)
|
|
UpdateItemDuration((uint)(now - m_Last_tick));
|
|
|
|
// check every second
|
|
if (now > m_Last_tick + 1)
|
|
UpdateSoulboundTradeItems();
|
|
|
|
// If mute expired, remove it from the DB
|
|
if (GetSession().m_muteTime != 0 && GetSession().m_muteTime < now)
|
|
{
|
|
GetSession().m_muteTime = 0;
|
|
PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.UPD_MUTE_TIME);
|
|
stmt.AddValue(0, 0); // Set the mute time to 0
|
|
stmt.AddValue(1, "");
|
|
stmt.AddValue(2, "");
|
|
stmt.AddValue(3, GetSession().GetAccountId());
|
|
DB.Login.Execute(stmt);
|
|
}
|
|
|
|
if (!m_timedquests.Empty())
|
|
{
|
|
foreach (var id in m_timedquests)
|
|
{
|
|
QuestStatusData q_status = m_QuestStatus[id];
|
|
if (q_status.Timer <= diff)
|
|
FailQuest(id);
|
|
else
|
|
{
|
|
q_status.Timer -= diff;
|
|
m_QuestStatusSave[id] = QuestSaveType.Default;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_achievementSys.UpdateTimedCriteria(diff);
|
|
|
|
if (HasUnitState(UnitState.MeleeAttacking) && !HasUnitState(UnitState.Casting))
|
|
{
|
|
Unit victim = GetVictim();
|
|
if (victim != null)
|
|
{
|
|
// default combat reach 10
|
|
// TODO add weapon, skill check
|
|
|
|
if (isAttackReady(WeaponAttackType.BaseAttack))
|
|
{
|
|
if (!IsWithinMeleeRange(victim))
|
|
{
|
|
setAttackTimer(WeaponAttackType.BaseAttack, 100);
|
|
if (m_swingErrorMsg != 1) // send single time (client auto repeat)
|
|
{
|
|
SendAttackSwingNotInRange();
|
|
m_swingErrorMsg = 1;
|
|
}
|
|
}
|
|
//120 degrees of radiant range, if player is not in boundary radius
|
|
else if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * MathFunctions.PI / 3, victim))
|
|
{
|
|
setAttackTimer(WeaponAttackType.BaseAttack, 100);
|
|
if (m_swingErrorMsg != 2) // send single time (client auto repeat)
|
|
{
|
|
SendAttackSwingBadFacingAttack();
|
|
m_swingErrorMsg = 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_swingErrorMsg = 0; // reset swing error state
|
|
|
|
// prevent base and off attack in same time, delay attack at 0.2 sec
|
|
if (haveOffhandWeapon())
|
|
if (getAttackTimer(WeaponAttackType.OffAttack) < SharedConst.AttackDisplayDelay)
|
|
setAttackTimer(WeaponAttackType.OffAttack, SharedConst.AttackDisplayDelay);
|
|
|
|
// do attack
|
|
AttackerStateUpdate(victim, WeaponAttackType.BaseAttack);
|
|
resetAttackTimer(WeaponAttackType.BaseAttack);
|
|
}
|
|
}
|
|
|
|
if (!IsInFeralForm() && haveOffhandWeapon() && isAttackReady(WeaponAttackType.OffAttack))
|
|
{
|
|
if (!IsWithinMeleeRange(victim))
|
|
setAttackTimer(WeaponAttackType.OffAttack, 100);
|
|
else if (!IsWithinBoundaryRadius(victim) && !HasInArc(2 * MathFunctions.PI / 3, victim))
|
|
setAttackTimer(WeaponAttackType.BaseAttack, 100);
|
|
else
|
|
{
|
|
// prevent base and off attack in same time, delay attack at 0.2 sec
|
|
if (getAttackTimer(WeaponAttackType.BaseAttack) < SharedConst.AttackDisplayDelay)
|
|
setAttackTimer(WeaponAttackType.BaseAttack, SharedConst.AttackDisplayDelay);
|
|
|
|
// do attack
|
|
AttackerStateUpdate(victim, WeaponAttackType.OffAttack);
|
|
resetAttackTimer(WeaponAttackType.OffAttack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (HasFlag(PlayerFields.Flags, PlayerFlags.Resting))
|
|
_restMgr.Update(diff);
|
|
|
|
if (m_weaponChangeTimer > 0)
|
|
{
|
|
if (diff >= m_weaponChangeTimer)
|
|
m_weaponChangeTimer = 0;
|
|
else
|
|
m_weaponChangeTimer -= diff;
|
|
}
|
|
|
|
if (m_zoneUpdateTimer > 0)
|
|
{
|
|
if (diff >= m_zoneUpdateTimer)
|
|
{
|
|
// On zone update tick check if we are still in an inn if we are supposed to be in one
|
|
if (_restMgr.HasRestFlag(RestFlag.Tavern))
|
|
{
|
|
AreaTriggerRecord atEntry = CliDB.AreaTriggerStorage.LookupByKey(_restMgr.GetInnTriggerId());
|
|
if (atEntry == null || !IsInAreaTriggerRadius(atEntry))
|
|
_restMgr.RemoveRestFlag(RestFlag.Tavern);
|
|
}
|
|
|
|
uint newzone, newarea;
|
|
GetZoneAndAreaId(out newzone, out newarea);
|
|
|
|
if (m_zoneUpdateId != newzone)
|
|
UpdateZone(newzone, newarea); // also update area
|
|
else
|
|
{
|
|
// use area updates as well
|
|
// needed for free far all arenas for example
|
|
if (m_areaUpdateId != newarea)
|
|
UpdateArea(newarea);
|
|
|
|
m_zoneUpdateTimer = 1 * Time.InMilliseconds;
|
|
}
|
|
}
|
|
else
|
|
m_zoneUpdateTimer -= diff;
|
|
}
|
|
if (m_timeSyncTimer > 0 && !IsBeingTeleportedFar())
|
|
{
|
|
if (diff >= m_timeSyncTimer)
|
|
SendTimeSync();
|
|
else
|
|
m_timeSyncTimer -= diff;
|
|
}
|
|
|
|
if (IsAlive())
|
|
{
|
|
m_regenTimer += diff;
|
|
RegenerateAll();
|
|
}
|
|
|
|
if (m_deathState == DeathState.JustDied)
|
|
KillPlayer();
|
|
|
|
if (m_nextSave > 0)
|
|
{
|
|
if (diff >= m_nextSave)
|
|
{
|
|
// m_nextSave reset in SaveToDB call
|
|
Global.ScriptMgr.OnPlayerSave(this);
|
|
SaveToDB();
|
|
Log.outDebug(LogFilter.Player, "Player '{0}' (GUID: {1}) saved", GetName(), GetGUID().ToString());
|
|
}
|
|
else
|
|
m_nextSave -= diff;
|
|
}
|
|
|
|
//Handle Water/drowning
|
|
HandleDrowning(diff);
|
|
|
|
// Played time
|
|
if (now > m_Last_tick)
|
|
{
|
|
uint elapsed = (uint)(now - m_Last_tick);
|
|
m_PlayedTimeTotal += elapsed;
|
|
m_PlayedTimeLevel += elapsed;
|
|
m_Last_tick = now;
|
|
}
|
|
|
|
if (GetDrunkValue() != 0)
|
|
{
|
|
m_drunkTimer += diff;
|
|
if (m_drunkTimer > 9 * Time.InMilliseconds)
|
|
HandleSobering();
|
|
}
|
|
if (HasPendingBind())
|
|
{
|
|
if (_pendingBindTimer <= diff)
|
|
{
|
|
// Player left the instance
|
|
if (_pendingBindId == GetInstanceId())
|
|
BindToInstance();
|
|
SetPendingBind(0, 0);
|
|
}
|
|
else
|
|
_pendingBindTimer -= diff;
|
|
}
|
|
// not auto-free ghost from body in instances
|
|
if (m_deathTimer > 0 && !GetMap().Instanceable() && !HasAuraType(AuraType.PreventResurrection))
|
|
{
|
|
if (diff >= m_deathTimer)
|
|
{
|
|
m_deathTimer = 0;
|
|
BuildPlayerRepop();
|
|
RepopAtGraveyard();
|
|
}
|
|
else
|
|
m_deathTimer -= diff;
|
|
}
|
|
|
|
UpdateEnchantTime(diff);
|
|
UpdateHomebindTime(diff);
|
|
|
|
if (!_instanceResetTimes.Empty())
|
|
{
|
|
foreach (var instance in _instanceResetTimes.ToList())
|
|
{
|
|
if (instance.Value < now)
|
|
_instanceResetTimes.Remove(instance.Key);
|
|
}
|
|
}
|
|
|
|
// group update
|
|
SendUpdateToOutOfRangeGroupMembers();
|
|
|
|
Pet pet = GetPet();
|
|
if (pet != null && !pet.IsWithinDistInMap(this, GetMap().GetVisibilityRange()) && !pet.isPossessed())
|
|
RemovePet(pet, PetSaveMode.NotInSlot, true);
|
|
|
|
//we should execute delayed teleports only for alive(!) players
|
|
//because we don't want player's ghost teleported from graveyard
|
|
if (IsHasDelayedTeleport() && IsAlive())
|
|
TeleportTo(teleportDest, m_teleport_options);
|
|
}
|
|
|
|
public override void setDeathState(DeathState s)
|
|
{
|
|
uint ressSpellId = 0;
|
|
|
|
bool cur = IsAlive();
|
|
|
|
if (s == DeathState.JustDied)
|
|
{
|
|
if (!cur)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player.setDeathState: Attempted to kill a dead player '{0}' ({1})", GetName(), GetGUID().ToString());
|
|
return;
|
|
}
|
|
|
|
// drunken state is cleared on death
|
|
SetDrunkValue(0);
|
|
// lost combo points at any target (targeted combo points clear in Unit::setDeathState)
|
|
ClearComboPoints();
|
|
|
|
ClearResurrectRequestData();
|
|
|
|
//FIXME: is pet dismissed at dying or releasing spirit? if second, add setDeathState(DEAD) to HandleRepopRequestOpcode and define pet unsummon here with (s == DEAD)
|
|
RemovePet(null, PetSaveMode.NotInSlot, true);
|
|
|
|
// save value before aura remove in Unit::setDeathState
|
|
ressSpellId = GetUInt32Value(PlayerFields.SelfResSpell);
|
|
|
|
// passive spell
|
|
if (ressSpellId == 0)
|
|
ressSpellId = GetResurrectionSpellId();
|
|
UpdateCriteria(CriteriaTypes.DeathAtMap, 1);
|
|
UpdateCriteria(CriteriaTypes.Death, 1);
|
|
UpdateCriteria(CriteriaTypes.DeathInDungeon, 1);
|
|
ResetCriteria(CriteriaTypes.BgObjectiveCapture, (uint)CriteriaCondition.NoDeath);
|
|
ResetCriteria(CriteriaTypes.HonorableKill, (uint)CriteriaCondition.NoDeath);
|
|
ResetCriteria(CriteriaTypes.GetKillingBlows, (uint)CriteriaCondition.NoDeath);
|
|
}
|
|
|
|
base.setDeathState(s);
|
|
|
|
// restore resurrection spell id for player after aura remove
|
|
if (s == DeathState.JustDied && cur && ressSpellId != 0)
|
|
SetUInt32Value(PlayerFields.SelfResSpell, ressSpellId);
|
|
|
|
if (IsAlive() && !cur)
|
|
//clear aura case after resurrection by another way (spells will be applied before next death)
|
|
SetUInt32Value(PlayerFields.SelfResSpell, 0);
|
|
}
|
|
|
|
public override void DestroyForPlayer(Player target)
|
|
{
|
|
base.DestroyForPlayer(target);
|
|
|
|
for (byte i = 0; i < InventorySlots.BagEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].DestroyForPlayer(target);
|
|
}
|
|
|
|
if (target == this)
|
|
{
|
|
for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].DestroyForPlayer(target);
|
|
}
|
|
|
|
for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].DestroyForPlayer(target);
|
|
}
|
|
|
|
for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].DestroyForPlayer(target);
|
|
}
|
|
}
|
|
}
|
|
public override void CleanupsBeforeDelete(bool finalCleanup = true)
|
|
{
|
|
TradeCancel(false);
|
|
DuelComplete(DuelCompleteType.Interrupted);
|
|
|
|
base.CleanupsBeforeDelete(finalCleanup);
|
|
|
|
if (GetTransport() != null)
|
|
GetTransport().RemovePassenger(this);
|
|
|
|
// clean up player-instance binds, may unload some instance saves
|
|
for (byte i = 0; i < (int)Difficulty.Max; ++i)
|
|
foreach (var bound in m_boundInstances[i])
|
|
bound.Value.save.RemovePlayer(this);
|
|
}
|
|
|
|
public override void AddToWorld()
|
|
{
|
|
// Do not add/remove the player from the object storage
|
|
// It will crash when updating the ObjectAccessor
|
|
// The player should only be added when logging in
|
|
base.AddToWorld();
|
|
|
|
for (byte i = (int)PlayerSlots.Start; i < (int)PlayerSlots.End; ++i)
|
|
if (m_items[i] != null)
|
|
m_items[i].AddToWorld();
|
|
}
|
|
public override void RemoveFromWorld()
|
|
{
|
|
// cleanup
|
|
if (IsInWorld)
|
|
{
|
|
// Release charmed creatures, unsummon totems and remove pets/guardians
|
|
StopCastingCharm();
|
|
StopCastingBindSight();
|
|
UnsummonPetTemporaryIfAny();
|
|
Global.OutdoorPvPMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
|
|
Global.BattleFieldMgr.HandlePlayerLeaveZone(this, m_zoneUpdateId);
|
|
}
|
|
|
|
// Remove items from world before self - player must be found in Item.RemoveFromObjectUpdate
|
|
for (byte i = (int)PlayerSlots.Start; i < (int)PlayerSlots.End; ++i)
|
|
if (m_items[i] != null)
|
|
m_items[i].RemoveFromWorld();
|
|
|
|
// Do not add/remove the player from the object storage
|
|
// It will crash when updating the ObjectAccessor
|
|
// The player should only be removed when logging out
|
|
base.RemoveFromWorld();
|
|
|
|
WorldObject viewpoint = GetViewpoint();
|
|
if (viewpoint != null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player {0} has viewpoint {1} {2} when removed from world",
|
|
GetName(), viewpoint.GetEntry(), viewpoint.GetTypeId());
|
|
SetViewpoint(viewpoint, false);
|
|
}
|
|
}
|
|
|
|
void ScheduleDelayedOperation(PlayerDelayedOperations operation)
|
|
{
|
|
if (operation < PlayerDelayedOperations.End)
|
|
m_DelayedOperations |= operation;
|
|
}
|
|
public void ProcessDelayedOperations()
|
|
{
|
|
if (m_DelayedOperations == 0)
|
|
return;
|
|
|
|
if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.ResurrectPlayer))
|
|
ResurrectUsingRequestDataImpl();
|
|
|
|
if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.SavePlayer))
|
|
SaveToDB();
|
|
|
|
if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.SpellCastDeserter))
|
|
CastSpell(this, 26013, true); // Deserter
|
|
|
|
if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.BGMountRestore))
|
|
{
|
|
if (m_bgData.mountSpell != 0)
|
|
{
|
|
CastSpell(this, m_bgData.mountSpell, true);
|
|
m_bgData.mountSpell = 0;
|
|
}
|
|
}
|
|
|
|
if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.BGTaxiRestore))
|
|
{
|
|
if (m_bgData.HasTaxiPath())
|
|
{
|
|
m_taxi.AddTaxiDestination(m_bgData.taxiPath[0]);
|
|
m_taxi.AddTaxiDestination(m_bgData.taxiPath[1]);
|
|
m_bgData.ClearTaxiPath();
|
|
|
|
ContinueTaxiFlight();
|
|
}
|
|
}
|
|
|
|
if (m_DelayedOperations.HasAnyFlag(PlayerDelayedOperations.BGGroupRestore))
|
|
{
|
|
Group g = GetGroup();
|
|
if (g != null)
|
|
g.SendUpdateToPlayer(GetGUID());
|
|
}
|
|
|
|
//we have executed ALL delayed ops, so clear the flag
|
|
m_DelayedOperations = 0;
|
|
}
|
|
|
|
public override bool IsLoading()
|
|
{
|
|
return GetSession().PlayerLoading();
|
|
}
|
|
|
|
new PlayerAI GetAI() { return (PlayerAI)i_AI; }
|
|
|
|
//Network
|
|
public void SendPacket(ServerPacket data)
|
|
{
|
|
Session.SendPacket(data);
|
|
}
|
|
|
|
//Time
|
|
void ResetTimeSync()
|
|
{
|
|
m_timeSyncTimer = 0;
|
|
m_timeSyncClient = 0;
|
|
m_timeSyncServer = Time.GetMSTime();
|
|
}
|
|
void SendTimeSync()
|
|
{
|
|
m_timeSyncQueue.Push(m_movementCounter++);
|
|
|
|
TimeSyncRequest packet = new TimeSyncRequest();
|
|
packet.SequenceIndex = m_timeSyncQueue.Last();
|
|
SendPacket(packet);
|
|
|
|
// Schedule next sync in 10 sec
|
|
m_timeSyncTimer = 10000;
|
|
m_timeSyncServer = Time.GetMSTime();
|
|
|
|
if (m_timeSyncQueue.Count > 3)
|
|
Log.outError(LogFilter.Network, "Not received CMSG_TIME_SYNC_RESP for over 30 seconds from player {0} ({1}), possible cheater", GetGUID().ToString(), GetName());
|
|
}
|
|
|
|
public DeclinedName GetDeclinedNames() { return _declinedname; }
|
|
|
|
public void CreateGarrison(uint garrSiteId)
|
|
{
|
|
_garrison = new Garrison(this);
|
|
if (!_garrison.Create(garrSiteId))
|
|
_garrison = null;
|
|
}
|
|
|
|
void DeleteGarrison()
|
|
{
|
|
if (_garrison != null)
|
|
{
|
|
_garrison.Delete();
|
|
_garrison = null;
|
|
}
|
|
}
|
|
|
|
public Garrison GetGarrison() { return _garrison; }
|
|
|
|
public SceneMgr GetSceneMgr() { return m_sceneMgr; }
|
|
|
|
public RestMgr GetRestMgr() { return _restMgr; }
|
|
|
|
public bool IsAdvancedCombatLoggingEnabled() { return _advancedCombatLoggingEnabled; }
|
|
public void SetAdvancedCombatLogging(bool enabled) { _advancedCombatLoggingEnabled = enabled; }
|
|
|
|
//Taxi
|
|
public void InitTaxiNodesForLevel() { m_taxi.InitTaxiNodesForLevel(GetRace(), GetClass(), getLevel()); }
|
|
|
|
//Cheat Commands
|
|
public bool GetCommandStatus(PlayerCommandStates command) { return (_activeCheats & command) != 0; }
|
|
public void SetCommandStatusOn(PlayerCommandStates command) { _activeCheats |= command; }
|
|
public void SetCommandStatusOff(PlayerCommandStates command) { _activeCheats &= ~command; }
|
|
|
|
//Pet - Summons - Vehicles
|
|
|
|
// last used pet number (for BG's)
|
|
public uint GetLastPetNumber() { return m_lastpetnumber; }
|
|
public void SetLastPetNumber(uint petnumber) { m_lastpetnumber = petnumber; }
|
|
public void LoadPet()
|
|
{
|
|
//fixme: the pet should still be loaded if the player is not in world
|
|
// just not added to the map
|
|
if (IsInWorld)
|
|
{
|
|
Pet pet = new Pet(this);
|
|
pet.LoadPetFromDB(this, 0, 0, true);
|
|
}
|
|
}
|
|
public uint GetTemporaryUnsummonedPetNumber() { return m_temporaryUnsummonedPetNumber; }
|
|
public void SetTemporaryUnsummonedPetNumber(uint petnumber) { m_temporaryUnsummonedPetNumber = petnumber; }
|
|
public void UnsummonPetTemporaryIfAny()
|
|
{
|
|
Pet pet = GetPet();
|
|
if (!pet)
|
|
return;
|
|
|
|
if (m_temporaryUnsummonedPetNumber == 0 && pet.isControlled() && !pet.isTemporarySummoned())
|
|
{
|
|
m_temporaryUnsummonedPetNumber = pet.GetCharmInfo().GetPetNumber();
|
|
m_oldpetspell = pet.GetUInt32Value(UnitFields.CreatedBySpell);
|
|
}
|
|
|
|
RemovePet(pet, PetSaveMode.AsCurrent);
|
|
}
|
|
public void ResummonPetTemporaryUnSummonedIfAny()
|
|
{
|
|
if (m_temporaryUnsummonedPetNumber == 0)
|
|
return;
|
|
|
|
// not resummon in not appropriate state
|
|
if (IsPetNeedBeTemporaryUnsummoned())
|
|
return;
|
|
|
|
if (!GetPetGUID().IsEmpty())
|
|
return;
|
|
|
|
Pet NewPet = new Pet(this);
|
|
NewPet.LoadPetFromDB(this, 0, m_temporaryUnsummonedPetNumber, true);
|
|
|
|
m_temporaryUnsummonedPetNumber = 0;
|
|
}
|
|
|
|
public bool IsPetNeedBeTemporaryUnsummoned()
|
|
{
|
|
return !IsInWorld || !IsAlive() || IsMounted();
|
|
}
|
|
|
|
public void SendRemoveControlBar()
|
|
{
|
|
SendPacket(new PetSpells());
|
|
}
|
|
|
|
public void StopCastingCharm()
|
|
{
|
|
Unit charm = GetCharm();
|
|
if (!charm)
|
|
return;
|
|
|
|
if (charm.IsTypeId(TypeId.Unit))
|
|
{
|
|
if (charm.ToCreature().HasUnitTypeMask(UnitTypeMask.Puppet))
|
|
((Puppet)charm).UnSummon();
|
|
else if (charm.IsVehicle())
|
|
ExitVehicle();
|
|
}
|
|
if (!GetCharmGUID().IsEmpty())
|
|
charm.RemoveCharmAuras();
|
|
|
|
if (!GetCharmGUID().IsEmpty())
|
|
{
|
|
Log.outFatal(LogFilter.Player, "Player {0} (GUID: {1} is not able to uncharm unit (GUID: {2} Entry: {3}, Type: {4})", GetName(), GetGUID(), GetCharmGUID(), charm.GetEntry(), charm.GetTypeId());
|
|
if (!charm.GetCharmerGUID().IsEmpty())
|
|
{
|
|
Log.outFatal(LogFilter.Player, "Charmed unit has charmer guid {0}", charm.GetCharmerGUID());
|
|
Contract.Assert(false);
|
|
}
|
|
|
|
SetCharm(charm, false);
|
|
}
|
|
}
|
|
public void CharmSpellInitialize()
|
|
{
|
|
Unit charm = GetFirstControlled();
|
|
if (!charm)
|
|
return;
|
|
|
|
CharmInfo charmInfo = charm.GetCharmInfo();
|
|
if (charmInfo == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player:CharmSpellInitialize(): the player's charm ({0}) has no charminfo!", charm.GetGUID());
|
|
return;
|
|
}
|
|
|
|
PetSpells petSpells = new PetSpells();
|
|
petSpells.PetGUID = charm.GetGUID();
|
|
|
|
if (charm.IsTypeId(TypeId.Unit))
|
|
{
|
|
petSpells.ReactState = charm.ToCreature().GetReactState();
|
|
petSpells.CommandState = charmInfo.GetCommandState();
|
|
}
|
|
|
|
for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
|
petSpells.ActionButtons[i] = charmInfo.GetActionBarEntry(i).packedData;
|
|
|
|
for (byte i = 0; i < SharedConst.MaxSpellCharm; ++i)
|
|
{
|
|
var cspell = charmInfo.GetCharmSpell(i);
|
|
if (cspell.GetAction() != 0)
|
|
petSpells.Actions.Add(cspell.packedData);
|
|
}
|
|
|
|
// Cooldowns
|
|
if (!charm.IsTypeId(TypeId.Player))
|
|
charm.GetSpellHistory().WritePacket(petSpells);
|
|
|
|
SendPacket(petSpells);
|
|
}
|
|
public void PossessSpellInitialize()
|
|
{
|
|
Unit charm = GetCharm();
|
|
if (!charm)
|
|
return;
|
|
|
|
CharmInfo charmInfo = charm.GetCharmInfo();
|
|
if (charmInfo == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player:PossessSpellInitialize(): charm ({0}) has no charminfo!", charm.GetGUID());
|
|
return;
|
|
}
|
|
|
|
PetSpells petSpellsPacket = new PetSpells();
|
|
petSpellsPacket.PetGUID = charm.GetGUID();
|
|
|
|
for (byte i = 0; i < SharedConst.ActionBarIndexMax; ++i)
|
|
petSpellsPacket.ActionButtons[i] = charmInfo.GetActionBarEntry(i).packedData;
|
|
|
|
// Cooldowns
|
|
charm.GetSpellHistory().WritePacket(petSpellsPacket);
|
|
|
|
SendPacket(petSpellsPacket);
|
|
}
|
|
public void VehicleSpellInitialize()
|
|
{
|
|
Creature vehicle = GetVehicleCreatureBase();
|
|
if (!vehicle)
|
|
return;
|
|
|
|
PetSpells petSpells = new PetSpells();
|
|
petSpells.PetGUID = vehicle.GetGUID();
|
|
petSpells.CreatureFamily = 0; // Pet Family (0 for all vehicles)
|
|
petSpells.Specialization = 0;
|
|
petSpells.TimeLimit = vehicle.IsSummon() ? vehicle.ToTempSummon().GetTimer() : 0;
|
|
petSpells.ReactState = vehicle.GetReactState();
|
|
petSpells.CommandState = CommandStates.Follow;
|
|
petSpells.Flag = 0x8;
|
|
|
|
for (uint i = 0; i < SharedConst.MaxSpellControlBar; ++i)
|
|
petSpells.ActionButtons[i] = UnitActionBarEntry.MAKE_UNIT_ACTION_BUTTON(0, i + 8);
|
|
|
|
for (uint i = 0; i < SharedConst.MaxCreatureSpells; ++i)
|
|
{
|
|
uint spellId = vehicle.m_spells[i];
|
|
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
|
|
if (spellInfo == null)
|
|
continue;
|
|
|
|
if (!Global.ConditionMgr.IsObjectMeetingVehicleSpellConditions(vehicle.GetEntry(), spellId, this, vehicle))
|
|
{
|
|
Log.outDebug(LogFilter.Condition, "VehicleSpellInitialize: conditions not met for Vehicle entry {0} spell {1}", vehicle.ToCreature().GetEntry(), spellId);
|
|
continue;
|
|
}
|
|
|
|
if (spellInfo.IsPassive())
|
|
vehicle.CastSpell(vehicle, spellId, true);
|
|
|
|
petSpells.ActionButtons[i] = UnitActionBarEntry.MAKE_UNIT_ACTION_BUTTON(spellId, i + 8);
|
|
}
|
|
|
|
// Cooldowns
|
|
vehicle.GetSpellHistory().WritePacket(petSpells);
|
|
|
|
SendPacket(petSpells);
|
|
}
|
|
|
|
//Currency - Money
|
|
void SetCurrency(CurrencyTypes id, uint count, bool printLog = true)
|
|
{
|
|
var playerCurrency = _currencyStorage.LookupByKey(id);
|
|
if (playerCurrency == null)
|
|
{
|
|
PlayerCurrency cur = new PlayerCurrency();
|
|
cur.state = PlayerCurrencyState.New;
|
|
cur.Quantity = count;
|
|
cur.WeeklyQuantity = 0;
|
|
cur.TrackedQuantity = 0;
|
|
cur.Flags = 0;
|
|
_currencyStorage[(uint)id] = cur;
|
|
}
|
|
}
|
|
public uint GetCurrency(uint id)
|
|
{
|
|
var playerCurrency = _currencyStorage.LookupByKey(id);
|
|
if (playerCurrency == null)
|
|
return 0;
|
|
|
|
return playerCurrency.Quantity;
|
|
}
|
|
public void ModifyCurrency(CurrencyTypes id, int count, bool printLog = true, bool ignoreMultipliers = false)
|
|
{
|
|
if (count == 0)
|
|
return;
|
|
|
|
CurrencyTypesRecord currency = CliDB.CurrencyTypesStorage.LookupByKey(id);
|
|
Contract.Assert(currency != null);
|
|
|
|
if (!ignoreMultipliers)
|
|
count *= (int)GetTotalAuraMultiplierByMiscValue(AuraType.ModCurrencyGain, (int)id);
|
|
|
|
uint oldTotalCount = 0;
|
|
uint oldWeekCount = 0;
|
|
uint oldTrackedCount = 0;
|
|
|
|
var playerCurrency = _currencyStorage.LookupByKey(id);
|
|
if (playerCurrency == null)
|
|
{
|
|
PlayerCurrency cur = new PlayerCurrency();
|
|
cur.state = PlayerCurrencyState.New;
|
|
cur.Quantity = 0;
|
|
cur.WeeklyQuantity = 0;
|
|
cur.TrackedQuantity = 0;
|
|
cur.Flags = 0;
|
|
_currencyStorage[(uint)id] = cur;
|
|
playerCurrency = _currencyStorage.LookupByKey(id);
|
|
}
|
|
else
|
|
{
|
|
oldTotalCount = playerCurrency.Quantity;
|
|
oldWeekCount = playerCurrency.WeeklyQuantity;
|
|
oldTrackedCount = playerCurrency.TrackedQuantity;
|
|
}
|
|
|
|
// count can't be more then weekCap if used (weekCap > 0)
|
|
uint weekCap = GetCurrencyWeekCap(currency);
|
|
if (weekCap != 0 && count > weekCap)
|
|
count = (int)weekCap;
|
|
|
|
// count can't be more then totalCap if used (totalCap > 0)
|
|
uint totalCap = GetCurrencyTotalCap(currency);
|
|
if (totalCap != 0 && count > totalCap)
|
|
count = (int)totalCap;
|
|
|
|
int newTrackedCount = (int)(oldTrackedCount) + (count > 0 ? count : 0);
|
|
if (newTrackedCount < 0)
|
|
newTrackedCount = 0;
|
|
|
|
int newTotalCount = (int)oldTotalCount + count;
|
|
if (newTotalCount < 0)
|
|
newTotalCount = 0;
|
|
|
|
int newWeekCount = (int)oldWeekCount + (count > 0 ? count : 0);
|
|
if (newWeekCount < 0)
|
|
newWeekCount = 0;
|
|
|
|
// if we get more then weekCap just set to limit
|
|
if (weekCap != 0 && weekCap < newWeekCount)
|
|
{
|
|
newWeekCount = (int)weekCap;
|
|
// weekCap - oldWeekCount always >= 0 as we set limit before!
|
|
newTotalCount = (int)(oldTotalCount + (weekCap - oldWeekCount));
|
|
}
|
|
|
|
// if we get more then totalCap set to maximum;
|
|
if (totalCap != 0 && totalCap < newTotalCount)
|
|
{
|
|
newTotalCount = (int)totalCap;
|
|
newWeekCount = (int)weekCap;
|
|
}
|
|
|
|
if (newTotalCount != oldTotalCount)
|
|
{
|
|
if (playerCurrency.state != PlayerCurrencyState.New)
|
|
playerCurrency.state = PlayerCurrencyState.Changed;
|
|
|
|
playerCurrency.Quantity = (uint)newTotalCount;
|
|
playerCurrency.WeeklyQuantity = (uint)newWeekCount;
|
|
playerCurrency.TrackedQuantity = (uint)newTrackedCount;
|
|
|
|
if (count > 0)
|
|
UpdateCriteria(CriteriaTypes.Currency, (uint)id, (uint)count);
|
|
|
|
CurrencyChanged((uint)id, count);
|
|
|
|
_currencyStorage[(uint)id] = playerCurrency;
|
|
|
|
SetCurrency packet = new SetCurrency();
|
|
packet.Type = (uint)id;
|
|
packet.Quantity = newTotalCount;
|
|
packet.SuppressChatLog = !printLog;
|
|
packet.WeeklyQuantity.Set(newWeekCount);
|
|
packet.TrackedQuantity.Set(newTrackedCount);
|
|
packet.Flags = playerCurrency.Flags;
|
|
|
|
SendPacket(packet);
|
|
}
|
|
}
|
|
public bool HasCurrency(uint id, uint count)
|
|
{
|
|
var playerCurrency = _currencyStorage.LookupByKey(id);
|
|
return playerCurrency != null && playerCurrency.Quantity >= count;
|
|
}
|
|
public uint GetCurrencyWeekCap(CurrencyTypes id)
|
|
{
|
|
CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey((uint)id);
|
|
if (entry == null)
|
|
return 0;
|
|
|
|
return GetCurrencyWeekCap(entry);
|
|
}
|
|
public uint GetCurrencyWeekCap(CurrencyTypesRecord currency)
|
|
{
|
|
return currency.MaxEarnablePerWeek;
|
|
}
|
|
uint GetCurrencyTotalCap(CurrencyTypesRecord currency)
|
|
{
|
|
uint cap = currency.MaxQty;
|
|
|
|
switch ((CurrencyTypes)currency.Id)
|
|
{
|
|
case CurrencyTypes.ApexisCrystals:
|
|
{
|
|
uint apexiscap = WorldConfig.GetUIntValue(WorldCfg.CurrencyMaxApexisCrystals);
|
|
if (apexiscap > 0)
|
|
cap = apexiscap;
|
|
break;
|
|
}
|
|
case CurrencyTypes.JusticePoints:
|
|
{
|
|
uint justicecap = WorldConfig.GetUIntValue(WorldCfg.CurrencyMaxJusticePoints);
|
|
if (justicecap > 0)
|
|
cap = justicecap;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return cap;
|
|
}
|
|
uint GetCurrencyOnWeek(CurrencyTypes id)
|
|
{
|
|
var playerCurrency = _currencyStorage.LookupByKey(id);
|
|
if (playerCurrency == null)
|
|
return 0;
|
|
|
|
return playerCurrency.WeeklyQuantity;
|
|
}
|
|
|
|
//Action Buttons - CUF Profile
|
|
public void SaveCUFProfile(byte id, CUFProfile profile) { _CUFProfiles[id] = profile; }
|
|
public CUFProfile GetCUFProfile(byte id) { return _CUFProfiles[id]; }
|
|
public byte GetCUFProfilesCount()
|
|
{
|
|
return (byte)_CUFProfiles.Count(p => p != null);
|
|
}
|
|
|
|
bool IsActionButtonDataValid(byte button, uint action, uint type)
|
|
{
|
|
if (button >= PlayerConst.MaxActionButtons)
|
|
{
|
|
Log.outError(LogFilter.Player, "Action {0} not added into button {1} for player {2} (GUID: {3}): button must be < {4}", action, button, GetName(), GetGUID(), PlayerConst.MaxActionButtons);
|
|
return false;
|
|
}
|
|
|
|
if (action >= PlayerConst.MaxActionButtonActionValue)
|
|
{
|
|
Log.outError(LogFilter.Player, "Action {0} not added into button {1} for player {2} (GUID: {3}): action must be < {4}", action, button, GetName(), GetGUID(), PlayerConst.MaxActionButtonActionValue);
|
|
return false;
|
|
}
|
|
|
|
switch ((ActionButtonType)type)
|
|
{
|
|
case ActionButtonType.Spell:
|
|
if (Global.SpellMgr.GetSpellInfo(action) == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Spell action {0} not added into button {1} for player {2} (GUID: {3}): spell not exist", action, button, GetName(), GetGUID());
|
|
return false;
|
|
}
|
|
|
|
if (!HasSpell(action))
|
|
{
|
|
Log.outError(LogFilter.Player, "Spell action {0} not added into button {1} for player {2} (GUID: {3}): player don't known this spell", action, button, GetName(), GetGUID());
|
|
return false;
|
|
}
|
|
break;
|
|
case ActionButtonType.Item:
|
|
if (Global.ObjectMgr.GetItemTemplate(action) == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Item action {0} not added into button {1} for player {2} (GUID: {3}): item not exist", action, button, GetName(), GetGUID());
|
|
return false;
|
|
}
|
|
break;
|
|
case ActionButtonType.Mount:
|
|
var mount = CliDB.MountStorage.LookupByKey(action);
|
|
if (mount == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Mount action {0} not added into button {1} for player {2} ({3}): mount does not exist", action, button, GetName(), GetGUID().ToString());
|
|
return false;
|
|
}
|
|
|
|
if (!HasSpell(mount.SpellId))
|
|
{
|
|
Log.outError(LogFilter.Player, "Mount action {0} not added into button {1} for player {2} ({3}): Player does not know this mount", action, button, GetName(), GetGUID().ToString());
|
|
return false;
|
|
}
|
|
break;
|
|
case ActionButtonType.C:
|
|
case ActionButtonType.CMacro:
|
|
case ActionButtonType.Macro:
|
|
case ActionButtonType.Eqset:
|
|
break;
|
|
default:
|
|
Log.outError(LogFilter.Player, "Unknown action type {0}", type);
|
|
return false; // other cases not checked at this moment
|
|
}
|
|
|
|
return true;
|
|
}
|
|
public ActionButton AddActionButton(byte button, uint action, uint type)
|
|
{
|
|
if (!IsActionButtonDataValid(button, action, type))
|
|
return null;
|
|
|
|
// it create new button (NEW state) if need or return existed
|
|
if (!m_actionButtons.ContainsKey(button))
|
|
m_actionButtons[button] = new ActionButton();
|
|
|
|
var ab = m_actionButtons[button];
|
|
|
|
// set data and update to CHANGED if not NEW
|
|
ab.SetActionAndType(action, (ActionButtonType)type);
|
|
|
|
Log.outDebug(LogFilter.Player, "Player '{0}' Added Action '{1}' (type {2}) to Button '{3}'", GetGUID().ToString(), action, type, button);
|
|
return ab;
|
|
}
|
|
public void RemoveActionButton(byte _button)
|
|
{
|
|
var button = m_actionButtons.LookupByKey(_button);
|
|
if (button == null || button.uState == ActionButtonUpdateState.Deleted)
|
|
return;
|
|
|
|
if (button.uState == ActionButtonUpdateState.New)
|
|
m_actionButtons.Remove(_button); // new and not saved
|
|
else
|
|
button.uState = ActionButtonUpdateState.Deleted; // saved, will deleted at next save
|
|
|
|
Log.outDebug(LogFilter.Player, "Action Button '{0}' Removed from Player '{1}'", button, GetGUID().ToString());
|
|
}
|
|
public ActionButton GetActionButton(byte _button)
|
|
{
|
|
var button = m_actionButtons.LookupByKey(_button);
|
|
if (button == null || button.uState == ActionButtonUpdateState.Deleted)
|
|
return null;
|
|
|
|
return button;
|
|
}
|
|
void SendInitialActionButtons() { SendActionButtons(0); }
|
|
void SendActionButtons(uint state)
|
|
{
|
|
UpdateActionButtons packet = new UpdateActionButtons();
|
|
|
|
foreach (var pair in m_actionButtons)
|
|
{
|
|
if (pair.Value.uState != ActionButtonUpdateState.Deleted && pair.Key < packet.ActionButtons.Length)
|
|
packet.ActionButtons[pair.Key] = pair.Value.packedData;
|
|
}
|
|
|
|
packet.Reason = (byte)state;
|
|
SendPacket(packet);
|
|
}
|
|
|
|
//Repitation
|
|
public int CalculateReputationGain(ReputationSource source, uint creatureOrQuestLevel, int rep, int faction, bool noQuestBonus = false)
|
|
{
|
|
float percent = 100.0f;
|
|
|
|
float repMod = noQuestBonus ? 0.0f : GetTotalAuraModifier(AuraType.ModReputationGain);
|
|
|
|
// faction specific auras only seem to apply to kills
|
|
if (source == ReputationSource.Kill)
|
|
repMod += GetTotalAuraModifierByMiscValue(AuraType.ModFactionReputationGain, faction);
|
|
|
|
percent += rep > 0 ? repMod : -repMod;
|
|
|
|
float rate;
|
|
switch (source)
|
|
{
|
|
case ReputationSource.Kill:
|
|
rate = WorldConfig.GetFloatValue(WorldCfg.RateReputationLowLevelKill);
|
|
break;
|
|
case ReputationSource.Quest:
|
|
case ReputationSource.DailyQuest:
|
|
case ReputationSource.WeeklyQuest:
|
|
case ReputationSource.MonthlyQuest:
|
|
case ReputationSource.RepeatableQuest:
|
|
rate = WorldConfig.GetFloatValue(WorldCfg.RateReputationLowLevelQuest);
|
|
break;
|
|
case ReputationSource.Spell:
|
|
default:
|
|
rate = 1.0f;
|
|
break;
|
|
}
|
|
|
|
if (rate != 1.0f && creatureOrQuestLevel < Formulas.GetGrayLevel(getLevel()))
|
|
percent *= rate;
|
|
|
|
if (percent <= 0.0f)
|
|
return 0;
|
|
|
|
// Multiply result with the faction specific rate
|
|
RepRewardRate repData = Global.ObjectMgr.GetRepRewardRate((uint)faction);
|
|
if (repData != null)
|
|
{
|
|
float repRate = 0.0f;
|
|
switch (source)
|
|
{
|
|
case ReputationSource.Kill:
|
|
repRate = repData.creatureRate;
|
|
break;
|
|
case ReputationSource.Quest:
|
|
repRate = repData.questRate;
|
|
break;
|
|
case ReputationSource.DailyQuest:
|
|
repRate = repData.questDailyRate;
|
|
break;
|
|
case ReputationSource.WeeklyQuest:
|
|
repRate = repData.questWeeklyRate;
|
|
break;
|
|
case ReputationSource.MonthlyQuest:
|
|
repRate = repData.questMonthlyRate;
|
|
break;
|
|
case ReputationSource.RepeatableQuest:
|
|
repRate = repData.questRepeatableRate;
|
|
break;
|
|
case ReputationSource.Spell:
|
|
repRate = repData.spellRate;
|
|
break;
|
|
}
|
|
|
|
// for custom, a rate of 0.0 will totally disable reputation gain for this faction/type
|
|
if (repRate <= 0.0f)
|
|
return 0;
|
|
|
|
percent *= repRate;
|
|
}
|
|
|
|
if (source != ReputationSource.Spell && GetsRecruitAFriendBonus(false))
|
|
percent *= 1.0f + WorldConfig.GetFloatValue(WorldCfg.RateReputationRecruitAFriendBonus);
|
|
|
|
return MathFunctions.CalculatePct(rep, percent);
|
|
}
|
|
// Calculates how many reputation points player gains in victim's enemy factions
|
|
public void RewardReputation(Unit victim, float rate)
|
|
{
|
|
if (!victim || victim.IsTypeId(TypeId.Player))
|
|
return;
|
|
|
|
if (victim.ToCreature().IsReputationGainDisabled())
|
|
return;
|
|
|
|
ReputationOnKillEntry Rep = Global.ObjectMgr.GetReputationOnKilEntry(victim.ToCreature().GetCreatureTemplate().Entry);
|
|
if (Rep == null)
|
|
return;
|
|
|
|
uint ChampioningFaction = 0;
|
|
|
|
if (GetChampioningFaction() != 0)
|
|
{
|
|
// support for: Championing - http://www.wowwiki.com/Championing
|
|
Map map = GetMap();
|
|
if (map.IsNonRaidDungeon())
|
|
{
|
|
LFGDungeonsRecord dungeon = Global.DB2Mgr.GetLfgDungeon(map.GetId(), map.GetDifficultyID());
|
|
if (dungeon != null)
|
|
if (dungeon.TargetLevel == 80)
|
|
ChampioningFaction = GetChampioningFaction();
|
|
}
|
|
}
|
|
|
|
Team team = GetTeam();
|
|
|
|
if (Rep.RepFaction1 != 0 && (!Rep.TeamDependent || team == Team.Alliance))
|
|
{
|
|
int donerep1 = CalculateReputationGain(ReputationSource.Kill, victim.GetLevelForTarget(this), Rep.RepValue1, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1));
|
|
donerep1 = (int)(donerep1 * rate);
|
|
|
|
FactionRecord factionEntry1 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1);
|
|
ReputationRank current_reputation_rank1 = GetReputationMgr().GetRank(factionEntry1);
|
|
if (factionEntry1 != null && (uint)current_reputation_rank1 <= Rep.ReputationMaxCap1)
|
|
GetReputationMgr().ModifyReputation(factionEntry1, donerep1);
|
|
}
|
|
|
|
if (Rep.RepFaction2 != 0 && (!Rep.TeamDependent || team == Team.Horde))
|
|
{
|
|
int donerep2 = CalculateReputationGain(ReputationSource.Kill, victim.GetLevelForTarget(this), Rep.RepValue2, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2));
|
|
donerep2 = (int)(donerep2 * rate);
|
|
|
|
FactionRecord factionEntry2 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2);
|
|
ReputationRank current_reputation_rank2 = GetReputationMgr().GetRank(factionEntry2);
|
|
if (factionEntry2 != null && (uint)current_reputation_rank2 <= Rep.ReputationMaxCap2)
|
|
GetReputationMgr().ModifyReputation(factionEntry2, donerep2);
|
|
}
|
|
}
|
|
// Calculate how many reputation points player gain with the quest
|
|
void RewardReputation(Quest quest)
|
|
{
|
|
for (byte i = 0; i < SharedConst.QuestRewardReputationsCount; ++i)
|
|
{
|
|
if (quest.RewardFactionId[i] == 0)
|
|
continue;
|
|
|
|
FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(quest.RewardFactionId[i]);
|
|
if (factionEntry == null)
|
|
continue;
|
|
|
|
int rep = 0;
|
|
bool noQuestBonus = false;
|
|
|
|
if (quest.RewardFactionOverride[i] != 0)
|
|
{
|
|
rep = quest.RewardFactionOverride[i] / 100;
|
|
noQuestBonus = true;
|
|
}
|
|
else
|
|
{
|
|
uint row = (uint)((quest.RewardFactionValue[i] < 0) ? 1 : 0) + 1;
|
|
QuestFactionRewardRecord questFactionRewEntry = CliDB.QuestFactionRewardStorage.LookupByKey(row);
|
|
if (questFactionRewEntry != null)
|
|
{
|
|
uint field = (uint)Math.Abs(quest.RewardFactionValue[i]);
|
|
rep = questFactionRewEntry.QuestRewFactionValue[field];
|
|
}
|
|
}
|
|
|
|
if (rep == 0)
|
|
continue;
|
|
|
|
if (quest.RewardFactionCapIn[i] != 0 && rep > 0 && (uint)GetReputationMgr().GetRank(factionEntry) >= quest.RewardFactionCapIn[i])
|
|
continue;
|
|
|
|
if (quest.IsDaily())
|
|
rep = CalculateReputationGain(ReputationSource.DailyQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus);
|
|
else if (quest.IsWeekly())
|
|
rep = CalculateReputationGain(ReputationSource.WeeklyQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus);
|
|
else if (quest.IsMonthly())
|
|
rep = CalculateReputationGain(ReputationSource.MonthlyQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus);
|
|
else if (quest.IsRepeatable())
|
|
rep = CalculateReputationGain(ReputationSource.RepeatableQuest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus);
|
|
else
|
|
rep = CalculateReputationGain(ReputationSource.Quest, (uint)GetQuestLevel(quest), rep, (int)quest.RewardFactionId[i], noQuestBonus);
|
|
|
|
bool noSpillover = Convert.ToBoolean(quest.RewardReputationMask & (1 << i));
|
|
GetReputationMgr().ModifyReputation(factionEntry, rep, noSpillover);
|
|
}
|
|
}
|
|
|
|
//Movement
|
|
bool IsCanDelayTeleport() { return m_bCanDelayTeleport; }
|
|
void SetCanDelayTeleport(bool setting) { m_bCanDelayTeleport = setting; }
|
|
bool IsHasDelayedTeleport() { return m_bHasDelayedTeleport; }
|
|
void SetDelayedTeleportFlag(bool setting) { m_bHasDelayedTeleport = setting; }
|
|
public bool TeleportTo(WorldLocation loc, TeleportToOptions options = 0)
|
|
{
|
|
return TeleportTo(loc.GetMapId(), loc.posX, loc.posY, loc.posZ, loc.Orientation, options);
|
|
}
|
|
public bool TeleportTo(uint mapid, float x, float y, float z, float orientation, TeleportToOptions options = 0)
|
|
{
|
|
if (!GridDefines.IsValidMapCoord(mapid, x, y, z, orientation))
|
|
{
|
|
Log.outError(LogFilter.Maps, "TeleportTo: invalid map ({0}) or invalid coordinates (X: {1}, Y: {2}, Z: {3}, O: {4}) given when teleporting player (GUID: {5}, name: {6}, map: {7}, {8}).",
|
|
mapid, x, y, z, orientation, GetGUID().ToString(), GetName(), GetMapId(), GetPosition().ToString());
|
|
return false;
|
|
}
|
|
|
|
if (!GetSession().HasPermission(RBACPermissions.SkipCheckDisableMap) && Global.DisableMgr.IsDisabledFor(DisableType.Map, mapid, this))
|
|
{
|
|
Log.outError(LogFilter.Maps, "Player (GUID: {0}, name: {1}) tried to enter a forbidden map {2}", GetGUID().ToString(), GetName(), mapid);
|
|
SendTransferAborted(mapid, TransferAbortReason.MapNotAllowed);
|
|
return false;
|
|
}
|
|
|
|
// preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
|
|
Pet pet = GetPet();
|
|
|
|
MapRecord mEntry = CliDB.MapStorage.LookupByKey(mapid);
|
|
|
|
// don't let enter Battlegrounds without assigned Battlegroundid (for example through areatrigger)...
|
|
// don't let gm level > 1 either
|
|
if (!InBattleground() && mEntry.IsBattlegroundOrArena())
|
|
return false;
|
|
|
|
// client without expansion support
|
|
if (GetSession().GetExpansion() < mEntry.Expansion())
|
|
{
|
|
Log.outDebug(LogFilter.Maps, "Player {0} using client without required expansion tried teleport to non accessible map {1}", GetName(), mapid);
|
|
|
|
Transport _transport = GetTransport();
|
|
if (_transport)
|
|
{
|
|
_transport.RemovePassenger(this);
|
|
RepopAtGraveyard(); // teleport to near graveyard if on transport, looks blizz like :)
|
|
}
|
|
|
|
SendTransferAborted(mapid, TransferAbortReason.InsufExpanLvl, (byte)mEntry.Expansion());
|
|
return false; // normal client can't teleport to this map...
|
|
}
|
|
else
|
|
Log.outDebug(LogFilter.Maps, "Player {0} is being teleported to map {1}", GetName(), mapid);
|
|
|
|
if (m_vehicle != null)
|
|
ExitVehicle();
|
|
|
|
// reset movement flags at teleport, because player will continue move with these flags after teleport
|
|
SetUnitMovementFlags(GetUnitMovementFlags() & MovementFlag.MaskHasPlayerStatusOpcode);
|
|
m_movementInfo.ResetJump();
|
|
DisableSpline();
|
|
|
|
Transport transport = GetTransport();
|
|
if (transport)
|
|
{
|
|
if (!options.HasAnyFlag(TeleportToOptions.NotLeaveTransport))
|
|
transport.RemovePassenger(this);
|
|
}
|
|
|
|
// The player was ported to another map and loses the duel immediately.
|
|
// We have to perform this check before the teleport, otherwise the
|
|
// ObjectAccessor won't find the flag.
|
|
if (duel != null && GetMapId() != mapid && GetMap().GetGameObject(GetGuidValue(PlayerFields.DuelArbiter)))
|
|
DuelComplete(DuelCompleteType.Fled);
|
|
|
|
if (GetMapId() == mapid)
|
|
{
|
|
//lets reset far teleport flag if it wasn't reset during chained teleports
|
|
SetSemaphoreTeleportFar(false);
|
|
//setup delayed teleport flag
|
|
SetDelayedTeleportFlag(IsCanDelayTeleport());
|
|
//if teleport spell is casted in Unit.Update() func
|
|
//then we need to delay it until update process will be finished
|
|
if (IsHasDelayedTeleport())
|
|
{
|
|
SetSemaphoreTeleportNear(true);
|
|
//lets save teleport destination for player
|
|
teleportDest = new WorldLocation(mapid, x, y, z, orientation);
|
|
m_teleport_options = options;
|
|
return true;
|
|
}
|
|
|
|
if (!options.HasAnyFlag(TeleportToOptions.NotUnSummonPet))
|
|
{
|
|
//same map, only remove pet if out of range for new position
|
|
if (pet && !pet.IsWithinDist3d(x, y, z, GetMap().GetVisibilityRange()))
|
|
UnsummonPetTemporaryIfAny();
|
|
}
|
|
|
|
if (!options.HasAnyFlag(TeleportToOptions.NotLeaveCombat))
|
|
CombatStop();
|
|
|
|
// this will be used instead of the current location in SaveToDB
|
|
teleportDest = new WorldLocation(mapid, x, y, z, orientation);
|
|
m_teleport_options = options;
|
|
SetFallInformation(0, z);
|
|
|
|
// code for finish transfer called in WorldSession.HandleMovementOpcodes()
|
|
// at client packet CMSG_MOVE_TELEPORT_ACK
|
|
SetSemaphoreTeleportNear(true);
|
|
// near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing
|
|
if (!GetSession().PlayerLogout())
|
|
SendTeleportPacket(teleportDest);
|
|
}
|
|
else
|
|
{
|
|
if (GetClass() == Class.Deathknight && GetMapId() == 609 && !IsGameMaster() && !HasSpell(50977))
|
|
return false;
|
|
|
|
// far teleport to another map
|
|
Map oldmap = IsInWorld ? GetMap() : null;
|
|
// check if we can enter before stopping combat / removing pet / totems / interrupting spells
|
|
|
|
// Check enter rights before map getting to avoid creating instance copy for player
|
|
// this check not dependent from map instance copy and same for all instance copies of selected map
|
|
if (Global.MapMgr.PlayerCannotEnter(mapid, this, false) != 0)
|
|
return false;
|
|
|
|
// Seamless teleport can happen only if cosmetic maps match
|
|
if (!oldmap || (oldmap.GetEntry().CosmeticParentMapID != mapid && GetMapId() != mEntry.CosmeticParentMapID &&
|
|
!((oldmap.GetEntry().CosmeticParentMapID != -1) ^ (oldmap.GetEntry().CosmeticParentMapID != mEntry.CosmeticParentMapID))))
|
|
options &= ~TeleportToOptions.Seamless;
|
|
|
|
//lets reset near teleport flag if it wasn't reset during chained teleports
|
|
SetSemaphoreTeleportNear(false);
|
|
//setup delayed teleport flag
|
|
SetDelayedTeleportFlag(IsCanDelayTeleport());
|
|
//if teleport spell is casted in Unit.Update() func
|
|
//then we need to delay it until update process will be finished
|
|
if (IsHasDelayedTeleport())
|
|
{
|
|
SetSemaphoreTeleportFar(true);
|
|
//lets save teleport destination for player
|
|
teleportDest = new WorldLocation(mapid, x, y, z, orientation);
|
|
m_teleport_options = options;
|
|
return true;
|
|
}
|
|
|
|
SetSelection(ObjectGuid.Empty);
|
|
|
|
CombatStop();
|
|
|
|
ResetContestedPvP();
|
|
|
|
// remove player from Battlegroundon far teleport (when changing maps)
|
|
Battleground bg = GetBattleground();
|
|
if (bg)
|
|
{
|
|
// Note: at Battlegroundjoin Battlegroundid set before teleport
|
|
// and we already will found "current" Battleground
|
|
// just need check that this is targeted map or leave
|
|
if (bg.GetMapId() != mapid)
|
|
LeaveBattleground(false); // don't teleport to entry point
|
|
}
|
|
|
|
// remove arena spell coldowns/buffs now to also remove pet's cooldowns before it's temporarily unsummoned
|
|
if (mEntry.IsBattleArena())
|
|
{
|
|
RemoveArenaSpellCooldowns(true);
|
|
RemoveArenaAuras();
|
|
if (pet)
|
|
pet.RemoveArenaAuras();
|
|
}
|
|
|
|
// remove pet on map change
|
|
if (pet)
|
|
UnsummonPetTemporaryIfAny();
|
|
|
|
// remove all areatriggers entities
|
|
RemoveAllAreaTriggers();
|
|
|
|
// remove all dyn objects
|
|
RemoveAllDynObjects();
|
|
|
|
// stop spellcasting
|
|
// not attempt interrupt teleportation spell at caster teleport
|
|
if (!options.HasAnyFlag(TeleportToOptions.Spell))
|
|
if (IsNonMeleeSpellCast(true))
|
|
InterruptNonMeleeSpells(true);
|
|
|
|
//remove auras before removing from map...
|
|
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ChangeMap | SpellAuraInterruptFlags.Move | SpellAuraInterruptFlags.Turning);
|
|
|
|
if (!GetSession().PlayerLogout() && !options.HasAnyFlag(TeleportToOptions.Seamless))
|
|
{
|
|
// send transfer packets
|
|
TransferPending transferPending = new TransferPending();
|
|
transferPending.MapID = (int)mapid;
|
|
transferPending.OldMapPosition = GetPosition();
|
|
|
|
transport = GetTransport();
|
|
if (transport)
|
|
{
|
|
transferPending.Ship.HasValue = true;
|
|
transferPending.Ship.Value.ID = transport.GetEntry();
|
|
transferPending.Ship.Value.OriginMapID = (int)GetMapId();
|
|
}
|
|
|
|
SendPacket(transferPending);
|
|
}
|
|
|
|
// remove from old map now
|
|
if (oldmap != null)
|
|
oldmap.RemovePlayerFromMap(this, false);
|
|
|
|
teleportDest = new WorldLocation(mapid, x, y, z, orientation);
|
|
m_teleport_options = options;
|
|
SetFallInformation(0, z);
|
|
// if the player is saved before worldportack (at logout for example)
|
|
// this will be used instead of the current location in SaveToDB
|
|
|
|
if (!GetSession().PlayerLogout())
|
|
{
|
|
SuspendToken suspendToken = new SuspendToken();
|
|
suspendToken.SequenceIndex = m_movementCounter; // not incrementing
|
|
suspendToken.Reason = options.HasAnyFlag(TeleportToOptions.Seamless) ? 2 : 1u;
|
|
SendPacket(suspendToken);
|
|
}
|
|
|
|
// move packet sent by client always after far teleport
|
|
// code for finish transfer to new map called in WorldSession.HandleMoveWorldportAckOpcode at client packet
|
|
SetSemaphoreTeleportFar(true);
|
|
|
|
}
|
|
return true;
|
|
}
|
|
public bool TeleportToBGEntryPoint()
|
|
{
|
|
if (m_bgData.joinPos.GetMapId() == 0xFFFFFFFF)
|
|
return false;
|
|
|
|
ScheduleDelayedOperation(PlayerDelayedOperations.BGMountRestore);
|
|
ScheduleDelayedOperation(PlayerDelayedOperations.BGTaxiRestore);
|
|
ScheduleDelayedOperation(PlayerDelayedOperations.BGGroupRestore);
|
|
return TeleportTo(m_bgData.joinPos);
|
|
}
|
|
public WorldLocation GetStartPosition()
|
|
{
|
|
PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass());
|
|
uint mapId = info.MapId;
|
|
if (GetClass() == Class.Deathknight && HasSpell(50977))
|
|
mapId = 0;
|
|
return new WorldLocation(mapId, info.PositionX, info.PositionY, info.PositionZ, 0);
|
|
}
|
|
public override bool IsUnderWater()
|
|
{
|
|
return IsInWater() &&
|
|
GetPositionZ() < (GetMap().GetWaterLevel(GetPositionX(), GetPositionY()) - 2);
|
|
}
|
|
public override bool IsInWater()
|
|
{
|
|
return m_isInWater;
|
|
}
|
|
public void SetInWater(bool apply)
|
|
{
|
|
if (m_isInWater == apply)
|
|
return;
|
|
|
|
//define player in water by opcodes
|
|
//move player's guid into HateOfflineList of those mobs
|
|
//which can't swim and move guid back into ThreatList when
|
|
//on surface.
|
|
/// @todo exist also swimming mobs, and function must be symmetric to enter/leave water
|
|
m_isInWater = apply;
|
|
|
|
// remove auras that need water/land
|
|
RemoveAurasWithInterruptFlags((apply ? SpellAuraInterruptFlags.NotAbovewater : SpellAuraInterruptFlags.NotUnderwater));
|
|
|
|
getHostileRefManager().updateThreatTables();
|
|
}
|
|
public void ValidateMovementInfo(MovementInfo mi)
|
|
{
|
|
var RemoveViolatingFlags = new Action<bool, MovementFlag>((check, maskToRemove) =>
|
|
{
|
|
if (check)
|
|
{
|
|
Log.outDebug(LogFilter.Unit, "Player.ValidateMovementInfo: Violation of MovementFlags found ({0}). MovementFlags: {1}, MovementFlags2: {2} for player {3}. Mask {4} will be removed.",
|
|
check, mi.GetMovementFlags(), mi.GetMovementFlags2(), GetGUID().ToString(), maskToRemove);
|
|
mi.RemoveMovementFlag(maskToRemove);
|
|
}
|
|
});
|
|
|
|
if (!GetVehicleBase() || !GetVehicle().GetVehicleInfo().Flags.HasAnyFlag(VehicleFlags.FixedPosition))
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Root), MovementFlag.Root);
|
|
|
|
/*! This must be a packet spoofing attempt. MOVEMENTFLAG_ROOT sent from the client is not valid
|
|
in conjunction with any of the moving movement flags such as MOVEMENTFLAG_FORWARD.
|
|
It will freeze clients that receive this player's movement info.
|
|
*/
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Root) && mi.HasMovementFlag(MovementFlag.MaskMoving), MovementFlag.MaskMoving);
|
|
|
|
//! Cannot hover without SPELL_AURA_HOVER
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Hover) && !HasAuraType(AuraType.Hover),
|
|
MovementFlag.Hover);
|
|
|
|
//! Cannot ascend and descend at the same time
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Ascending) && mi.HasMovementFlag(MovementFlag.Descending),
|
|
MovementFlag.Ascending | MovementFlag.Descending);
|
|
|
|
//! Cannot move left and right at the same time
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Left) && mi.HasMovementFlag(MovementFlag.Right),
|
|
MovementFlag.Left | MovementFlag.Right);
|
|
|
|
//! Cannot strafe left and right at the same time
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.StrafeLeft) && mi.HasMovementFlag(MovementFlag.StrafeRight),
|
|
MovementFlag.StrafeLeft | MovementFlag.StrafeRight);
|
|
|
|
//! Cannot pitch up and down at the same time
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.PitchUp) && mi.HasMovementFlag(MovementFlag.PitchDown),
|
|
MovementFlag.PitchUp | MovementFlag.PitchDown);
|
|
|
|
//! Cannot move forwards and backwards at the same time
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Forward) && mi.HasMovementFlag(MovementFlag.Backward),
|
|
MovementFlag.Forward | MovementFlag.Backward);
|
|
|
|
//! Cannot walk on water without SPELL_AURA_WATER_WALK except for ghosts
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.WaterWalk) &&
|
|
!HasAuraType(AuraType.WaterWalk) && !HasAuraType(AuraType.Ghost), MovementFlag.WaterWalk);
|
|
|
|
//! Cannot feather fall without SPELL_AURA_FEATHER_FALL
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.FallingSlow) && !HasAuraType(AuraType.FeatherFall),
|
|
MovementFlag.FallingSlow);
|
|
|
|
/*! Cannot fly if no fly auras present. Exception is being a GM.
|
|
Note that we check for account level instead of Player.IsGameMaster() because in some
|
|
situations it may be feasable to use .gm fly on as a GM without having .gm on,
|
|
e.g. aerial combat.
|
|
*/
|
|
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.Flying | MovementFlag.CanFly) && GetSession().GetSecurity() == AccountTypes.Player &&
|
|
!m_unitMovedByMe.HasAuraType(AuraType.Fly) &&
|
|
!m_unitMovedByMe.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed),
|
|
MovementFlag.Flying | MovementFlag.CanFly);
|
|
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.DisableGravity | MovementFlag.CanFly) && mi.HasMovementFlag(MovementFlag.Falling),
|
|
MovementFlag.Falling);
|
|
|
|
RemoveViolatingFlags(mi.HasMovementFlag(MovementFlag.SplineElevation) && MathFunctions.fuzzyEq(mi.SplineElevation, 0.0f), MovementFlag.SplineElevation);
|
|
|
|
// Client first checks if spline elevation != 0, then verifies flag presence
|
|
if (MathFunctions.fuzzyNe(mi.SplineElevation, 0.0f))
|
|
mi.AddMovementFlag(MovementFlag.SplineElevation);
|
|
}
|
|
public void HandleFall(MovementInfo movementInfo)
|
|
{
|
|
// calculate total z distance of the fall
|
|
float z_diff = m_lastFallZ - movementInfo.Pos.posZ;
|
|
Log.outDebug(LogFilter.Server, "zDiff = {0}", z_diff);
|
|
|
|
//Players with low fall distance, Feather Fall or physical immunity (charges used) are ignored
|
|
// 14.57 can be calculated by resolving damageperc formula below to 0
|
|
if (z_diff >= 14.57f && !IsDead() && !IsGameMaster() &&
|
|
!HasAuraType(AuraType.Hover) && !HasAuraType(AuraType.FeatherFall) &&
|
|
!HasAuraType(AuraType.Fly) && !IsImmunedToDamage(SpellSchoolMask.Normal))
|
|
{
|
|
//Safe fall, fall height reduction
|
|
int safe_fall = GetTotalAuraModifier(AuraType.SafeFall);
|
|
|
|
float damageperc = 0.018f * (z_diff - safe_fall) - 0.2426f;
|
|
|
|
if (damageperc > 0)
|
|
{
|
|
uint damage = (uint)(damageperc * GetMaxHealth() * WorldConfig.GetFloatValue(WorldCfg.RateDamageFall));
|
|
|
|
float height = movementInfo.Pos.posZ;
|
|
UpdateGroundPositionZ(movementInfo.Pos.posX, movementInfo.Pos.posY, ref height);
|
|
|
|
if (damage > 0)
|
|
{
|
|
//Prevent fall damage from being more than the player maximum health
|
|
if (damage > GetMaxHealth())
|
|
damage = (uint)GetMaxHealth();
|
|
|
|
// Gust of Wind
|
|
if (HasAura(43621))
|
|
damage = (uint)GetMaxHealth() / 2;
|
|
|
|
uint original_health = (uint)GetHealth();
|
|
uint final_damage = EnvironmentalDamage(EnviromentalDamage.Fall, damage);
|
|
|
|
// recheck alive, might have died of EnvironmentalDamage, avoid cases when player die in fact like Spirit of Redemption case
|
|
if (IsAlive() && final_damage < original_health)
|
|
UpdateCriteria(CriteriaTypes.FallWithoutDying, (uint)z_diff * 100);
|
|
}
|
|
|
|
//Z given by moveinfo, LastZ, FallTime, WaterZ, MapZ, Damage, Safefall reduction
|
|
Log.outDebug(LogFilter.Player, "FALLDAMAGE z={0} sz={1} pZ{2} FallTime={3} mZ={4} damage={5} SF={6}",
|
|
movementInfo.Pos.posZ, height, GetPositionZ(), movementInfo.jump.fallTime, height, damage, safe_fall);
|
|
}
|
|
}
|
|
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Landing); // No fly zone - Parachute
|
|
}
|
|
public void UpdateFallInformationIfNeed(MovementInfo minfo, ClientOpcodes opcode)
|
|
{
|
|
if (m_lastFallTime >= m_movementInfo.jump.fallTime || m_lastFallZ <= m_movementInfo.Pos.posZ || opcode == ClientOpcodes.MoveFallLand)
|
|
SetFallInformation(m_movementInfo.jump.fallTime, m_movementInfo.Pos.posZ);
|
|
}
|
|
|
|
public bool HasSummonPending()
|
|
{
|
|
return m_summon_expire >= Time.UnixTime;
|
|
}
|
|
|
|
public void SendSummonRequestFrom(Unit summoner)
|
|
{
|
|
if (!summoner)
|
|
return;
|
|
|
|
// Player already has active summon request
|
|
if (HasSummonPending())
|
|
return;
|
|
|
|
// Evil Twin (ignore player summon, but hide this for summoner)
|
|
if (HasAura(23445))
|
|
return;
|
|
|
|
m_summon_expire = Time.UnixTime + PlayerConst.MaxPlayerSummonDelay;
|
|
m_summon_location = new WorldLocation(summoner);
|
|
|
|
SummonRequest summonRequest = new SummonRequest();
|
|
summonRequest.SummonerGUID = summoner.GetGUID();
|
|
summonRequest.SummonerVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress();
|
|
summonRequest.AreaID = (int)summoner.GetZoneId();
|
|
SendPacket(summonRequest);
|
|
}
|
|
public bool IsInAreaTriggerRadius(AreaTriggerRecord trigger)
|
|
{
|
|
if (trigger == null || GetMapId() != trigger.MapID)
|
|
return false;
|
|
|
|
if (trigger.Radius > 0.0f)
|
|
{
|
|
// if we have radius check it
|
|
float dist = GetDistance(trigger.Pos.X, trigger.Pos.Y, trigger.Pos.Z);
|
|
if (dist > trigger.Radius)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Position center = new Position(trigger.Pos.X, trigger.Pos.Y, trigger.Pos.Z, trigger.BoxYaw);
|
|
if (!IsWithinBox(center, trigger.BoxLength / 2.0f, trigger.BoxWidth / 2.0f, trigger.BoxHeight / 2.0f))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void SummonIfPossible(bool agree)
|
|
{
|
|
if (!agree)
|
|
{
|
|
m_summon_expire = 0;
|
|
return;
|
|
}
|
|
|
|
// expire and auto declined
|
|
if (m_summon_expire < Time.UnixTime)
|
|
return;
|
|
|
|
// stop taxi flight at summon
|
|
if (IsInFlight())
|
|
{
|
|
GetMotionMaster().MovementExpired();
|
|
CleanupAfterTaxiFlight();
|
|
}
|
|
|
|
// drop flag at summon
|
|
// this code can be reached only when GM is summoning player who carries flag, because player should be immune to summoning spells when he carries flag
|
|
Battleground bg = GetBattleground();
|
|
if (bg)
|
|
bg.EventPlayerDroppedFlag(this);
|
|
|
|
m_summon_expire = 0;
|
|
|
|
UpdateCriteria(CriteriaTypes.AcceptedSummonings, 1);
|
|
|
|
m_summon_location.SetOrientation(GetOrientation());
|
|
TeleportTo(m_summon_location);
|
|
}
|
|
|
|
//GM
|
|
public bool isAcceptWhispers() { return m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.AcceptWhispers); }
|
|
public void SetAcceptWhispers(bool on)
|
|
{
|
|
if (on)
|
|
m_ExtraFlags |= PlayerExtraFlags.AcceptWhispers;
|
|
else
|
|
m_ExtraFlags &= ~PlayerExtraFlags.AcceptWhispers;
|
|
}
|
|
public bool IsGameMaster() { return Convert.ToBoolean(m_ExtraFlags & PlayerExtraFlags.GMOn); }
|
|
public bool CanBeGameMaster() { return GetSession().HasPermission(RBACPermissions.CommandGm); }
|
|
public void SetGameMaster(bool on)
|
|
{
|
|
if (on)
|
|
{
|
|
m_ExtraFlags |= PlayerExtraFlags.GMOn;
|
|
SetFaction(35);
|
|
SetFlag(PlayerFields.Flags, PlayerFlags.GM);
|
|
SetFlag(UnitFields.Flags2, UnitFlags2.AllowCheatSpells);
|
|
|
|
Pet pet = GetPet();
|
|
if (pet != null)
|
|
{
|
|
pet.SetFaction(35);
|
|
pet.getHostileRefManager().setOnlineOfflineState(false);
|
|
}
|
|
|
|
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
|
ResetContestedPvP();
|
|
|
|
getHostileRefManager().setOnlineOfflineState(false);
|
|
CombatStopWithPets();
|
|
|
|
m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.GM, GetSession().GetSecurity());
|
|
}
|
|
else
|
|
{
|
|
m_ExtraFlags &= ~PlayerExtraFlags.GMOn;
|
|
SetFactionForRace(GetRace());
|
|
RemoveFlag(PlayerFields.Flags, PlayerFlags.GM);
|
|
RemoveFlag(UnitFields.Flags2, UnitFlags2.AllowCheatSpells);
|
|
|
|
Pet pet = GetPet();
|
|
if (pet != null)
|
|
{
|
|
pet.SetFaction(getFaction());
|
|
pet.getHostileRefManager().setOnlineOfflineState(true);
|
|
}
|
|
|
|
// restore FFA PvP Server state
|
|
if (Global.WorldMgr.IsFFAPvPRealm())
|
|
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp);
|
|
|
|
// restore FFA PvP area state, remove not allowed for GM mounts
|
|
UpdateArea(m_areaUpdateId);
|
|
|
|
getHostileRefManager().setOnlineOfflineState(true);
|
|
m_serverSideVisibilityDetect.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player);
|
|
}
|
|
|
|
UpdateObjectVisibility();
|
|
}
|
|
public bool isGMChat() { return m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.GMChat); }
|
|
public void SetGMChat(bool on)
|
|
{
|
|
if (on)
|
|
m_ExtraFlags |= PlayerExtraFlags.GMChat;
|
|
else
|
|
m_ExtraFlags &= ~PlayerExtraFlags.GMChat;
|
|
}
|
|
public bool isTaxiCheater() { return m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.TaxiCheat); }
|
|
public void SetTaxiCheater(bool on)
|
|
{
|
|
if (on)
|
|
m_ExtraFlags |= PlayerExtraFlags.TaxiCheat;
|
|
else
|
|
m_ExtraFlags &= ~PlayerExtraFlags.TaxiCheat;
|
|
}
|
|
public bool isGMVisible() { return !m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.GMInvisible); }
|
|
public void SetGMVisible(bool on)
|
|
{
|
|
if (on)
|
|
{
|
|
m_ExtraFlags &= ~PlayerExtraFlags.GMInvisible; //remove flag
|
|
m_serverSideVisibility.SetValue(ServerSideVisibilityType.GM, AccountTypes.Player);
|
|
}
|
|
else
|
|
{
|
|
m_ExtraFlags |= PlayerExtraFlags.GMInvisible; //add flag
|
|
|
|
SetAcceptWhispers(false);
|
|
SetGameMaster(true);
|
|
|
|
m_serverSideVisibility.SetValue(ServerSideVisibilityType.GM, GetSession().GetSecurity());
|
|
}
|
|
|
|
foreach (Channel channel in m_channels)
|
|
channel.SetInvisible(this, !on);
|
|
}
|
|
|
|
//Chat - Text - Channel
|
|
public void PrepareGossipMenu(WorldObject source, uint menuId = 0, bool showQuests = false)
|
|
{
|
|
PlayerMenu menu = PlayerTalkClass;
|
|
menu.ClearMenus();
|
|
|
|
menu.GetGossipMenu().SetMenuId(menuId);
|
|
|
|
var menuItemBounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(menuId);
|
|
|
|
// if default menuId and no menu options exist for this, use options from default options
|
|
if (menuItemBounds.Empty() && menuId == GetDefaultGossipMenuForSource(source))
|
|
menuItemBounds = Global.ObjectMgr.GetGossipMenuItemsMapBounds(0);
|
|
|
|
NPCFlags npcflags = 0;
|
|
|
|
if (source.IsTypeId(TypeId.Unit))
|
|
{
|
|
npcflags = (NPCFlags)source.GetUInt64Value(UnitFields.NpcFlags);
|
|
if (Convert.ToBoolean(npcflags & NPCFlags.QuestGiver) && showQuests)
|
|
PrepareQuestMenu(source.GetGUID());
|
|
}
|
|
else if (source.IsTypeId(TypeId.GameObject))
|
|
if (source.ToGameObject().GetGoType() == GameObjectTypes.QuestGiver)
|
|
PrepareQuestMenu(source.GetGUID());
|
|
|
|
foreach (var menuItems in menuItemBounds)
|
|
{
|
|
if (!Global.ConditionMgr.IsObjectMeetToConditions(this, source, menuItems.Conditions))
|
|
continue;
|
|
|
|
bool canTalk = true;
|
|
GameObject go = source.ToGameObject();
|
|
Creature creature = source.ToCreature();
|
|
if (creature)
|
|
{
|
|
if (!menuItems.OptionNpcFlag.HasAnyFlag(npcflags))
|
|
continue;
|
|
|
|
switch (menuItems.OptionType)
|
|
{
|
|
case GossipOption.Armorer:
|
|
canTalk = false; // added in special mode
|
|
break;
|
|
case GossipOption.Spirithealer:
|
|
if (!IsDead())
|
|
canTalk = false;
|
|
break;
|
|
case GossipOption.Vendor:
|
|
VendorItemData vendorItems = creature.GetVendorItems();
|
|
if (vendorItems == null || vendorItems.Empty())
|
|
{
|
|
Log.outError(LogFilter.Sql, "Creature (GUID: {0}, Entry: {1}) have UNIT_NPC_FLAG_VENDOR but have empty trading item list.", creature.GetGUID().ToString(), creature.GetEntry());
|
|
canTalk = false;
|
|
}
|
|
break;
|
|
case GossipOption.Learndualspec:
|
|
canTalk = false;
|
|
break;
|
|
case GossipOption.Unlearntalents:
|
|
if (!creature.CanResetTalents(this))
|
|
canTalk = false;
|
|
break;
|
|
case GossipOption.Taxivendor:
|
|
if (GetSession().SendLearnNewTaxiNode(creature))
|
|
return;
|
|
break;
|
|
case GossipOption.Battlefield:
|
|
if (!creature.isCanInteractWithBattleMaster(this, false))
|
|
canTalk = false;
|
|
break;
|
|
case GossipOption.Stablepet:
|
|
if (GetClass() != Class.Hunter)
|
|
canTalk = false;
|
|
break;
|
|
case GossipOption.Questgiver:
|
|
canTalk = false;
|
|
break;
|
|
case GossipOption.Trainer:
|
|
case GossipOption.Gossip:
|
|
case GossipOption.Spiritguide:
|
|
case GossipOption.Innkeeper:
|
|
case GossipOption.Banker:
|
|
case GossipOption.Petitioner:
|
|
case GossipOption.Tabarddesigner:
|
|
case GossipOption.Auctioneer:
|
|
break; // no checks
|
|
case GossipOption.Outdoorpvp:
|
|
if (!Global.OutdoorPvPMgr.CanTalkTo(this, creature, menuItems))
|
|
canTalk = false;
|
|
break;
|
|
default:
|
|
Log.outError(LogFilter.Sql, "Creature entry {0} have unknown gossip option {1} for menu {2}", creature.GetEntry(), menuItems.OptionType, menuItems.MenuId);
|
|
canTalk = false;
|
|
break;
|
|
}
|
|
}
|
|
else if (go != null)
|
|
{
|
|
switch (menuItems.OptionType)
|
|
{
|
|
case GossipOption.Gossip:
|
|
if (go.GetGoType() != GameObjectTypes.QuestGiver && go.GetGoType() != GameObjectTypes.Goober)
|
|
canTalk = false;
|
|
break;
|
|
default:
|
|
canTalk = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (canTalk)
|
|
{
|
|
string strOptionText = "";
|
|
string strBoxText = "";
|
|
BroadcastTextRecord optionBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(menuItems.OptionBroadcastTextId);
|
|
BroadcastTextRecord boxBroadcastText = CliDB.BroadcastTextStorage.LookupByKey(menuItems.BoxBroadcastTextId);
|
|
LocaleConstant locale = GetSession().GetSessionDbLocaleIndex();
|
|
|
|
if (optionBroadcastText != null)
|
|
strOptionText = Global.DB2Mgr.GetBroadcastTextValue(optionBroadcastText, locale, GetGender());
|
|
else
|
|
strOptionText = menuItems.OptionText;
|
|
|
|
if (boxBroadcastText != null)
|
|
strBoxText = Global.DB2Mgr.GetBroadcastTextValue(boxBroadcastText, locale, GetGender());
|
|
else
|
|
strBoxText = menuItems.BoxText;
|
|
|
|
if (locale != LocaleConstant.enUS)
|
|
{
|
|
if (optionBroadcastText == null)
|
|
{
|
|
// Find localizations from database.
|
|
GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItems.OptionId);
|
|
if (gossipMenuLocale != null)
|
|
ObjectManager.GetLocaleString(gossipMenuLocale.OptionText, locale, ref strOptionText);
|
|
}
|
|
|
|
if (boxBroadcastText == null)
|
|
{
|
|
// Find localizations from database.
|
|
GossipMenuItemsLocale gossipMenuLocale = Global.ObjectMgr.GetGossipMenuItemsLocale(menuId, menuItems.OptionId);
|
|
if (gossipMenuLocale != null)
|
|
ObjectManager.GetLocaleString(gossipMenuLocale.BoxText, locale, ref strBoxText);
|
|
}
|
|
}
|
|
|
|
menu.GetGossipMenu().AddMenuItem((int)menuItems.OptionId, menuItems.OptionIcon, strOptionText, 0, (uint)menuItems.OptionType, strBoxText, menuItems.BoxMoney, menuItems.BoxCoded);
|
|
menu.GetGossipMenu().AddGossipMenuItemData(menuItems.OptionId, menuItems.ActionMenuId, menuItems.ActionPoiId, menuItems.TrainerId);
|
|
}
|
|
}
|
|
}
|
|
public void SendPreparedGossip(WorldObject source)
|
|
{
|
|
if (!source)
|
|
return;
|
|
|
|
if (source.IsTypeId(TypeId.Unit) || source.IsTypeId(TypeId.GameObject))
|
|
{
|
|
if (PlayerTalkClass.GetGossipMenu().IsEmpty() && !PlayerTalkClass.GetQuestMenu().IsEmpty())
|
|
{
|
|
SendPreparedQuest(source.GetGUID());
|
|
return;
|
|
}
|
|
}
|
|
|
|
// in case non empty gossip menu (that not included quests list size) show it
|
|
// (quest entries from quest menu will be included in list)
|
|
|
|
uint textId = GetGossipTextId(source);
|
|
uint menuId = PlayerTalkClass.GetGossipMenu().GetMenuId();
|
|
if (menuId != 0)
|
|
textId = GetGossipTextId(menuId, source);
|
|
|
|
PlayerTalkClass.SendGossipMenu(textId, source.GetGUID());
|
|
}
|
|
public void OnGossipSelect(WorldObject source, uint gossipListId, uint menuId)
|
|
{
|
|
GossipMenu gossipMenu = PlayerTalkClass.GetGossipMenu();
|
|
|
|
// if not same, then something funky is going on
|
|
if (menuId != gossipMenu.GetMenuId())
|
|
return;
|
|
|
|
GossipMenuItem item = gossipMenu.GetItem(gossipListId);
|
|
if (item == null)
|
|
return;
|
|
|
|
uint gossipOptionId = item.OptionType;
|
|
ObjectGuid guid = source.GetGUID();
|
|
|
|
if (source.IsTypeId(TypeId.GameObject))
|
|
{
|
|
if (gossipOptionId > (int)GossipOption.Questgiver)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player guid {0} request invalid gossip option for GameObject entry {1}", GetGUID().ToString(), source.GetEntry());
|
|
return;
|
|
}
|
|
}
|
|
|
|
GossipMenuItemData menuItemData = gossipMenu.GetItemData(gossipListId);
|
|
if (menuItemData == null)
|
|
return;
|
|
|
|
int cost = (int)item.BoxMoney;
|
|
if (!HasEnoughMoney(cost))
|
|
{
|
|
SendBuyError(BuyResult.NotEnoughtMoney, null, 0);
|
|
PlayerTalkClass.SendCloseGossip();
|
|
return;
|
|
}
|
|
|
|
switch ((GossipOption)gossipOptionId)
|
|
{
|
|
case GossipOption.Gossip:
|
|
{
|
|
if (menuItemData.GossipActionPoi != 0)
|
|
PlayerTalkClass.SendPointOfInterest(menuItemData.GossipActionPoi);
|
|
|
|
if (menuItemData.GossipActionMenuId != 0)
|
|
{
|
|
PrepareGossipMenu(source, menuItemData.GossipActionMenuId);
|
|
SendPreparedGossip(source);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GossipOption.Outdoorpvp:
|
|
Global.OutdoorPvPMgr.HandleGossipOption(this, source.ToCreature(), gossipListId);
|
|
break;
|
|
case GossipOption.Spirithealer:
|
|
if (IsDead())
|
|
source.ToCreature().CastSpell(source.ToCreature(), 17251, true, null, null, GetGUID());
|
|
break;
|
|
case GossipOption.Questgiver:
|
|
PrepareQuestMenu(guid);
|
|
SendPreparedQuest(guid);
|
|
break;
|
|
case GossipOption.Vendor:
|
|
case GossipOption.Armorer:
|
|
GetSession().SendListInventory(guid);
|
|
break;
|
|
case GossipOption.Stablepet:
|
|
GetSession().SendStablePet(guid);
|
|
break;
|
|
case GossipOption.Trainer:
|
|
GetSession().SendTrainerList(source.ToCreature(), menuItemData.TrainerId);
|
|
break;
|
|
case GossipOption.Learndualspec:
|
|
break;
|
|
case GossipOption.Unlearntalents:
|
|
PlayerTalkClass.SendCloseGossip();
|
|
SendRespecWipeConfirm(guid, GetNextResetTalentsCost());
|
|
break;
|
|
case GossipOption.Taxivendor:
|
|
GetSession().SendTaxiMenu(source.ToCreature());
|
|
break;
|
|
case GossipOption.Innkeeper:
|
|
PlayerTalkClass.SendCloseGossip();
|
|
SetBindPoint(guid);
|
|
break;
|
|
case GossipOption.Banker:
|
|
GetSession().SendShowBank(guid);
|
|
break;
|
|
case GossipOption.Petitioner:
|
|
PlayerTalkClass.SendCloseGossip();
|
|
GetSession().SendPetitionShowList(guid);
|
|
break;
|
|
case GossipOption.Tabarddesigner:
|
|
PlayerTalkClass.SendCloseGossip();
|
|
GetSession().SendTabardVendorActivate(guid);
|
|
break;
|
|
case GossipOption.Auctioneer:
|
|
GetSession().SendAuctionHello(guid, source.ToCreature());
|
|
break;
|
|
case GossipOption.Spiritguide:
|
|
PrepareGossipMenu(source);
|
|
SendPreparedGossip(source);
|
|
break;
|
|
case GossipOption.Battlefield:
|
|
{
|
|
BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.GetBattleMasterBG(source.GetEntry());
|
|
|
|
if (bgTypeId == BattlegroundTypeId.None)
|
|
{
|
|
Log.outError(LogFilter.Player, "a user (guid {0}) requested Battlegroundlist from a npc who is no battlemaster", GetGUID().ToString());
|
|
return;
|
|
}
|
|
|
|
Global.BattlegroundMgr.SendBattlegroundList(this, guid, bgTypeId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ModifyMoney(-cost);
|
|
}
|
|
public uint GetGossipTextId(WorldObject source)
|
|
{
|
|
if (source == null)
|
|
return SharedConst.DefaultGossipMessage;
|
|
|
|
return GetGossipTextId(GetDefaultGossipMenuForSource(source), source);
|
|
}
|
|
uint GetGossipTextId(uint menuId, WorldObject source)
|
|
{
|
|
uint textId = SharedConst.DefaultGossipMessage;
|
|
|
|
if (menuId == 0)
|
|
return textId;
|
|
|
|
var menuBounds = Global.ObjectMgr.GetGossipMenusMapBounds(menuId);
|
|
|
|
foreach (var menu in menuBounds)
|
|
{
|
|
if (Global.ConditionMgr.IsObjectMeetToConditions(this, source, menu.Conditions))
|
|
textId = menu.TextId;
|
|
}
|
|
|
|
return textId;
|
|
}
|
|
uint GetDefaultGossipMenuForSource(WorldObject source)
|
|
{
|
|
switch (source.GetTypeId())
|
|
{
|
|
case TypeId.Unit:
|
|
return source.ToCreature().GetCreatureTemplate().GossipMenuId;
|
|
case TypeId.GameObject:
|
|
return source.ToGameObject().GetGoInfo().GetGossipMenuId();
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public bool CanJoinConstantChannelInZone(ChatChannelsRecord channel, AreaTableRecord zone)
|
|
{
|
|
if (channel.Flags.HasAnyFlag(ChannelDBCFlags.ZoneDep) && zone.Flags[0].HasAnyFlag(AreaFlags.ArenaInstance))
|
|
return false;
|
|
|
|
if (channel.Flags.HasAnyFlag(ChannelDBCFlags.CityOnly) && !zone.Flags[0].HasAnyFlag(AreaFlags.Capital))
|
|
return false;
|
|
|
|
if (channel.Flags.HasAnyFlag(ChannelDBCFlags.GuildReq) && GetGuildId() != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
public void JoinedChannel(Channel c)
|
|
{
|
|
m_channels.Add(c);
|
|
}
|
|
public void LeftChannel(Channel c)
|
|
{
|
|
m_channels.Remove(c);
|
|
}
|
|
public void CleanupChannels()
|
|
{
|
|
while (!m_channels.Empty())
|
|
{
|
|
Channel ch = m_channels.FirstOrDefault();
|
|
m_channels.RemoveAt(0); // remove from player's channel list
|
|
ch.LeaveChannel(this, false); // not send to client, not remove from player's channel list
|
|
|
|
// delete channel if empty
|
|
ChannelManager cMgr = ChannelManager.ForTeam(GetTeam());
|
|
if (cMgr != null)
|
|
{
|
|
if (ch.IsConstant())
|
|
cMgr.LeftChannel(ch.GetChannelId(), ch.GetZoneEntry());
|
|
else
|
|
cMgr.LeftChannel(ch.GetName());
|
|
}
|
|
}
|
|
Log.outDebug(LogFilter.ChatSystem, "Player {0}: channels cleaned up!", GetName());
|
|
}
|
|
void UpdateLocalChannels(uint newZone)
|
|
{
|
|
if (GetSession().PlayerLoading() && !IsBeingTeleportedFar())
|
|
return; // The client handles it automatically after loading, but not after teleporting
|
|
|
|
AreaTableRecord current_zone = CliDB.AreaTableStorage.LookupByKey(newZone);
|
|
if (current_zone == null)
|
|
return;
|
|
|
|
ChannelManager cMgr = ChannelManager.ForTeam(GetTeam());
|
|
if (cMgr == null)
|
|
return;
|
|
|
|
foreach (var channelEntry in CliDB.ChatChannelsStorage.Values)
|
|
{
|
|
Channel usedChannel = null;
|
|
foreach (var channel in m_channels)
|
|
{
|
|
if (channel.GetChannelId() == channelEntry.Id)
|
|
{
|
|
usedChannel = channel;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Channel removeChannel = null;
|
|
Channel joinChannel = null;
|
|
bool sendRemove = true;
|
|
|
|
if (CanJoinConstantChannelInZone(channelEntry, current_zone))
|
|
{
|
|
if (!channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.Global))
|
|
{
|
|
if (channelEntry.Flags.HasAnyFlag(ChannelDBCFlags.CityOnly) && usedChannel != null)
|
|
continue; // Already on the channel, as city channel names are not changing
|
|
|
|
joinChannel = cMgr.GetJoinChannel(channelEntry.Id, "", current_zone);
|
|
if (usedChannel != null)
|
|
{
|
|
if (joinChannel != usedChannel)
|
|
{
|
|
removeChannel = usedChannel;
|
|
sendRemove = false; // Do not send leave channel, it already replaced at client
|
|
}
|
|
else
|
|
joinChannel = null;
|
|
}
|
|
}
|
|
else
|
|
joinChannel = cMgr.GetJoinChannel(channelEntry.Id, "");
|
|
}
|
|
else
|
|
removeChannel = usedChannel;
|
|
|
|
if (joinChannel != null)
|
|
joinChannel.JoinChannel(this, ""); // Changed Channel: ... or Joined Channel: ...
|
|
|
|
if (removeChannel != null)
|
|
{
|
|
removeChannel.LeaveChannel(this, sendRemove); // Leave old channel
|
|
|
|
LeftChannel(removeChannel); // Remove from player's channel list
|
|
cMgr.LeftChannel(removeChannel.GetChannelId(), removeChannel.GetZoneEntry()); // Delete if empty
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<Channel> GetJoinedChannels() { return m_channels; }
|
|
|
|
//Mail
|
|
public void AddMail(Mail mail) { m_mail.Insert(0, mail); }
|
|
public void RemoveMail(uint id)
|
|
{
|
|
foreach (var mail in m_mail)
|
|
{
|
|
if (mail.messageID == id)
|
|
{
|
|
//do not delete item, because Player.removeMail() is called when returning mail to sender.
|
|
m_mail.Remove(mail);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
public void SendMailResult(uint mailId, MailResponseType mailAction, MailResponseResult mailError, InventoryResult equipError = 0, uint item_guid = 0, uint item_count = 0)
|
|
{
|
|
MailCommandResult result = new MailCommandResult();
|
|
result.MailID = mailId;
|
|
result.Command = (uint)mailAction;
|
|
result.ErrorCode = (uint)mailError;
|
|
|
|
if (mailError == MailResponseResult.EquipError)
|
|
result.BagResult = (uint)equipError;
|
|
else if (mailAction == MailResponseType.ItemTaken)
|
|
{
|
|
result.AttachID = item_guid;
|
|
result.QtyInInventory = item_count;
|
|
}
|
|
|
|
SendPacket(result);
|
|
}
|
|
void SendNewMail()
|
|
{
|
|
SendPacket(new NotifyRecievedMail());
|
|
}
|
|
public void UpdateNextMailTimeAndUnreads()
|
|
{
|
|
// calculate next delivery time (min. from non-delivered mails
|
|
// and recalculate unReadMail
|
|
long cTime = Time.UnixTime;
|
|
m_nextMailDelivereTime = 0;
|
|
unReadMails = 0;
|
|
foreach (var mail in m_mail)
|
|
{
|
|
if (mail.deliver_time > cTime)
|
|
{
|
|
if (m_nextMailDelivereTime == 0 || m_nextMailDelivereTime > mail.deliver_time)
|
|
m_nextMailDelivereTime = mail.deliver_time;
|
|
}
|
|
else if ((mail.checkMask & MailCheckMask.Read) == 0)
|
|
++unReadMails;
|
|
}
|
|
}
|
|
public void AddNewMailDeliverTime(long deliver_time)
|
|
{
|
|
if (deliver_time <= Time.UnixTime) // ready now
|
|
{
|
|
++unReadMails;
|
|
SendNewMail();
|
|
}
|
|
else // not ready and no have ready mails
|
|
{
|
|
if (m_nextMailDelivereTime == 0 || m_nextMailDelivereTime > deliver_time)
|
|
m_nextMailDelivereTime = deliver_time;
|
|
}
|
|
}
|
|
public bool IsMailsLoaded() { return m_mailsLoaded; }
|
|
public void AddMItem(Item it)
|
|
{
|
|
mMitems[it.GetGUID().GetCounter()] = it;
|
|
}
|
|
public bool RemoveMItem(ulong id)
|
|
{
|
|
return mMitems.Remove(id);
|
|
}
|
|
public Item GetMItem(ulong id) { return mMitems.LookupByKey(id); }
|
|
public Mail GetMail(uint id) { return m_mail.Find(p => p.messageID == id); }
|
|
public List<Mail> GetMails() { return m_mail; }
|
|
|
|
//Binds
|
|
public bool HasPendingBind() { return _pendingBindId > 0; }
|
|
void UpdateHomebindTime(uint time)
|
|
{
|
|
// GMs never get homebind timer online
|
|
if (m_InstanceValid || IsGameMaster())
|
|
{
|
|
if (m_HomebindTimer != 0) // instance valid, but timer not reset
|
|
SendRaidGroupOnlyMessage(RaidGroupReason.None, 0);
|
|
|
|
// instance is valid, reset homebind timer
|
|
m_HomebindTimer = 0;
|
|
}
|
|
else if (m_HomebindTimer > 0)
|
|
{
|
|
if (time >= m_HomebindTimer)
|
|
{
|
|
// teleport to nearest graveyard
|
|
RepopAtGraveyard();
|
|
}
|
|
else
|
|
m_HomebindTimer -= time;
|
|
}
|
|
else
|
|
{
|
|
// instance is invalid, start homebind timer
|
|
m_HomebindTimer = 60000;
|
|
// send message to player
|
|
SendRaidGroupOnlyMessage(RaidGroupReason.RequirementsUnmatch, (int)m_HomebindTimer);
|
|
Log.outDebug(LogFilter.Maps, "PLAYER: Player '{0}' (GUID: {1}) will be teleported to homebind in 60 seconds", GetName(), GetGUID().ToString());
|
|
}
|
|
}
|
|
public void SetHomebind(WorldLocation loc, uint areaId)
|
|
{
|
|
homebind = loc;
|
|
homebindAreaId = areaId;
|
|
|
|
// update sql homebind
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_PLAYER_HOMEBIND);
|
|
stmt.AddValue(0, homebind.GetMapId());
|
|
stmt.AddValue(1, homebindAreaId);
|
|
stmt.AddValue(2, homebind.posX);
|
|
stmt.AddValue(3, homebind.posY);
|
|
stmt.AddValue(4, homebind.posZ);
|
|
stmt.AddValue(5, GetGUID().GetCounter());
|
|
DB.Characters.Execute(stmt);
|
|
}
|
|
void SetBindPoint(ObjectGuid guid)
|
|
{
|
|
BinderConfirm packet = new BinderConfirm(guid);
|
|
SendPacket(packet);
|
|
}
|
|
public void SendBindPointUpdate()
|
|
{
|
|
BindPointUpdate packet = new BindPointUpdate();
|
|
packet.BindPosition.X = homebind.GetPositionX();
|
|
packet.BindPosition.Y = homebind.GetPositionY();
|
|
packet.BindPosition.Z = homebind.GetPositionZ();
|
|
packet.BindMapID = homebind.GetMapId();
|
|
packet.BindAreaID = homebindAreaId;
|
|
SendPacket(packet);
|
|
}
|
|
|
|
//Misc
|
|
public uint GetTotalPlayedTime() { return m_PlayedTimeTotal; }
|
|
public uint GetLevelPlayedTime() { return m_PlayedTimeLevel; }
|
|
|
|
public CinematicManager GetCinematicMgr() { return _cinematicMgr; }
|
|
|
|
public void SendUpdateWorldState(uint variable, uint value, bool hidden = false)
|
|
{
|
|
UpdateWorldState worldstate = new UpdateWorldState();
|
|
worldstate.VariableID = variable;
|
|
worldstate.Value = (int)value;
|
|
worldstate.Hidden = hidden;
|
|
SendPacket(worldstate);
|
|
}
|
|
|
|
void SendInitWorldStates(uint zoneid, uint areaid)
|
|
{
|
|
// data depends on zoneid/mapid...
|
|
Battleground bg = GetBattleground();
|
|
uint mapid = GetMapId();
|
|
OutdoorPvP pvp = Global.OutdoorPvPMgr.GetOutdoorPvPToZoneId(zoneid);
|
|
InstanceScript instance = GetInstanceScript();
|
|
BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(zoneid);
|
|
|
|
InitWorldStates packet = new InitWorldStates();
|
|
packet.MapID = mapid;
|
|
packet.AreaID = zoneid;
|
|
packet.SubareaID = areaid;
|
|
packet.AddState(2264, 0); // 1
|
|
packet.AddState(2263, 0); // 2
|
|
packet.AddState(2262, 0); // 3
|
|
packet.AddState(2261, 0); // 4
|
|
packet.AddState(2260, 0); // 5
|
|
packet.AddState(2259, 0); // 6
|
|
|
|
packet.AddState(3191, WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress) ? WorldConfig.GetIntValue(WorldCfg.ArenaSeasonId) : 0); // 7 Current Season - Arena season in progress
|
|
// 0 - End of season
|
|
packet.AddState(3901, WorldConfig.GetIntValue(WorldCfg.ArenaSeasonId) - (WorldConfig.GetBoolValue(WorldCfg.ArenaSeasonInProgress) ? 1 : 0)); // 8 PreviousSeason
|
|
|
|
if (mapid == 530) // Outland
|
|
{
|
|
packet.AddState(2495, 0); // 7
|
|
packet.AddState(2493, 0xF); // 8
|
|
packet.AddState(2491, 0xF); // 9
|
|
}
|
|
|
|
// insert <field> <value>
|
|
switch (zoneid)
|
|
{
|
|
case 1: // Dun Morogh
|
|
case 11: // Wetlands
|
|
case 12: // Elwynn Forest
|
|
case 38: // Loch Modan
|
|
case 40: // Westfall
|
|
case 51: // Searing Gorge
|
|
case 1519: // Stormwind City
|
|
case 1537: // Ironforge
|
|
case 2257: // Deeprun Tram
|
|
case 3703: // Shattrath City});
|
|
break;
|
|
case 1377: // Silithus
|
|
if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.Silithus)
|
|
pvp.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
// states are always shown
|
|
packet.AddState(2313, 0x0); // 7 ally silityst gathered
|
|
packet.AddState(2314, 0x0); // 8 horde silityst gathered
|
|
packet.AddState(2317, 0x0); // 9 max silithyst
|
|
}
|
|
// dunno about these... aq opening event maybe?
|
|
packet.AddState(2322, 0x0); // 10 sandworm N
|
|
packet.AddState(2323, 0x0); // 11 sandworm S
|
|
packet.AddState(2324, 0x0); // 12 sandworm SW
|
|
packet.AddState(2325, 0x0); // 13 sandworm E
|
|
break;
|
|
case 2597: // Alterac Valley
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.AV)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x7ae, 0x1); // 7 snowfall n
|
|
packet.AddState(0x532, 0x1); // 8 frostwolfhut hc
|
|
packet.AddState(0x531, 0x0); // 9 frostwolfhut ac
|
|
packet.AddState(0x52e, 0x0); // 10 stormpike firstaid a_a
|
|
packet.AddState(0x571, 0x0); // 11 east frostwolf tower horde assaulted -unused
|
|
packet.AddState(0x570, 0x0); // 12 west frostwolf tower horde assaulted - unused
|
|
packet.AddState(0x567, 0x1); // 13 frostwolfe c
|
|
packet.AddState(0x566, 0x1); // 14 frostwolfw c
|
|
packet.AddState(0x550, 0x1); // 15 irondeep (N) ally
|
|
packet.AddState(0x544, 0x0); // 16 ice grave a_a
|
|
packet.AddState(0x536, 0x0); // 17 stormpike grave h_c
|
|
packet.AddState(0x535, 0x1); // 18 stormpike grave a_c
|
|
packet.AddState(0x518, 0x0); // 19 stoneheart grave a_a
|
|
packet.AddState(0x517, 0x0); // 20 stoneheart grave h_a
|
|
packet.AddState(0x574, 0x0); // 21 1396 unk
|
|
packet.AddState(0x573, 0x0); // 22 iceblood tower horde assaulted -unused
|
|
packet.AddState(0x572, 0x0); // 23 towerpoint horde assaulted - unused
|
|
packet.AddState(0x56f, 0x0); // 24 1391 unk
|
|
packet.AddState(0x56e, 0x0); // 25 iceblood a
|
|
packet.AddState(0x56d, 0x0); // 26 towerp a
|
|
packet.AddState(0x56c, 0x0); // 27 frostwolfe a
|
|
packet.AddState(0x56b, 0x0); // 28 froswolfw a
|
|
packet.AddState(0x56a, 0x1); // 29 1386 unk
|
|
packet.AddState(0x569, 0x1); // 30 iceblood c
|
|
packet.AddState(0x568, 0x1); // 31 towerp c
|
|
packet.AddState(0x565, 0x0); // 32 stoneh tower a
|
|
packet.AddState(0x564, 0x0); // 33 icewing tower a
|
|
packet.AddState(0x563, 0x0); // 34 dunn a
|
|
packet.AddState(0x562, 0x0); // 35 duns a
|
|
packet.AddState(0x561, 0x0); // 36 stoneheart bunker alliance assaulted - unused
|
|
packet.AddState(0x560, 0x0); // 37 icewing bunker alliance assaulted - unused
|
|
packet.AddState(0x55f, 0x0); // 38 dunbaldar south alliance assaulted - unused
|
|
packet.AddState(0x55e, 0x0); // 39 dunbaldar north alliance assaulted - unused
|
|
packet.AddState(0x55d, 0x0); // 40 stone tower d
|
|
packet.AddState(0x3c6, 0x0); // 41 966 unk
|
|
packet.AddState(0x3c4, 0x0); // 42 964 unk
|
|
packet.AddState(0x3c2, 0x0); // 43 962 unk
|
|
packet.AddState(0x516, 0x1); // 44 stoneheart grave a_c
|
|
packet.AddState(0x515, 0x0); // 45 stonheart grave h_c
|
|
packet.AddState(0x3b6, 0x0); // 46 950 unk
|
|
packet.AddState(0x55c, 0x0); // 47 icewing tower d
|
|
packet.AddState(0x55b, 0x0); // 48 dunn d
|
|
packet.AddState(0x55a, 0x0); // 49 duns d
|
|
packet.AddState(0x559, 0x0); // 50 1369 unk
|
|
packet.AddState(0x558, 0x0); // 51 iceblood d
|
|
packet.AddState(0x557, 0x0); // 52 towerp d
|
|
packet.AddState(0x556, 0x0); // 53 frostwolfe d
|
|
packet.AddState(0x555, 0x0); // 54 frostwolfw d
|
|
packet.AddState(0x554, 0x1); // 55 stoneh tower c
|
|
packet.AddState(0x553, 0x1); // 56 icewing tower c
|
|
packet.AddState(0x552, 0x1); // 57 dunn c
|
|
packet.AddState(0x551, 0x1); // 58 duns c
|
|
packet.AddState(0x54f, 0x0); // 59 irondeep (N) horde
|
|
packet.AddState(0x54e, 0x0); // 60 irondeep (N) ally
|
|
packet.AddState(0x54d, 0x1); // 61 mine (S) neutral
|
|
packet.AddState(0x54c, 0x0); // 62 mine (S) horde
|
|
packet.AddState(0x54b, 0x0); // 63 mine (S) ally
|
|
packet.AddState(0x545, 0x0); // 64 iceblood h_a
|
|
packet.AddState(0x543, 0x1); // 65 iceblod h_c
|
|
packet.AddState(0x542, 0x0); // 66 iceblood a_c
|
|
packet.AddState(0x540, 0x0); // 67 snowfall h_a
|
|
packet.AddState(0x53f, 0x0); // 68 snowfall a_a
|
|
packet.AddState(0x53e, 0x0); // 69 snowfall h_c
|
|
packet.AddState(0x53d, 0x0); // 70 snowfall a_c
|
|
packet.AddState(0x53c, 0x0); // 71 frostwolf g h_a
|
|
packet.AddState(0x53b, 0x0); // 72 frostwolf g a_a
|
|
packet.AddState(0x53a, 0x1); // 73 frostwolf g h_c
|
|
packet.AddState(0x539, 0x0); // 74 frostwolf g a_c
|
|
packet.AddState(0x538, 0x0); // 75 stormpike grave h_a
|
|
packet.AddState(0x537, 0x0); // 76 stormpike grave a_a
|
|
packet.AddState(0x534, 0x0); // 77 frostwolf hut h_a
|
|
packet.AddState(0x533, 0x0); // 78 frostwolf hut a_a
|
|
packet.AddState(0x530, 0x0); // 79 stormpike first aid h_a
|
|
packet.AddState(0x52f, 0x0); // 80 stormpike first aid h_c
|
|
packet.AddState(0x52d, 0x1); // 81 stormpike first aid a_c
|
|
}
|
|
break;
|
|
case 3277: // Warsong Gulch
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.WS)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x62d, 0x0); // 7 1581 alliance flag captures
|
|
packet.AddState(0x62e, 0x0); // 8 1582 horde flag captures
|
|
packet.AddState(0x609, 0x0); // 9 1545 unk, set to 1 on alliance flag pickup...
|
|
packet.AddState(0x60a, 0x0); // 10 1546 unk, set to 1 on horde flag pickup, after drop it's -1
|
|
packet.AddState(0x60b, 0x2); // 11 1547 unk
|
|
packet.AddState(0x641, 0x3); // 12 1601 unk (max flag captures?)
|
|
packet.AddState(0x922, 0x1); // 13 2338 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
|
|
packet.AddState(0x923, 0x1); // 14 2339 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
|
|
}
|
|
break;
|
|
case 3358: // Arathi Basin
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.AB)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x6e7, 0x0); // 7 1767 stables alliance
|
|
packet.AddState(0x6e8, 0x0); // 8 1768 stables horde
|
|
packet.AddState(0x6e9, 0x0); // 9 1769 unk, ST?
|
|
packet.AddState(0x6ea, 0x0); // 10 1770 stables (show/hide)
|
|
packet.AddState(0x6ec, 0x0); // 11 1772 farm (0 - horde controlled, 1 - alliance controlled)
|
|
packet.AddState(0x6ed, 0x0); // 12 1773 farm (show/hide)
|
|
packet.AddState(0x6ee, 0x0); // 13 1774 farm color
|
|
packet.AddState(0x6ef, 0x0); // 14 1775 gold mine color, may be FM?
|
|
packet.AddState(0x6f0, 0x0); // 15 1776 alliance resources
|
|
packet.AddState(0x6f1, 0x0); // 16 1777 horde resources
|
|
packet.AddState(0x6f2, 0x0); // 17 1778 horde bases
|
|
packet.AddState(0x6f3, 0x0); // 18 1779 alliance bases
|
|
packet.AddState(0x6f4, 0x7d0); // 19 1780 max resources (2000)
|
|
packet.AddState(0x6f6, 0x0); // 20 1782 blacksmith color
|
|
packet.AddState(0x6f7, 0x0); // 21 1783 blacksmith (show/hide)
|
|
packet.AddState(0x6f8, 0x0); // 22 1784 unk, bs?
|
|
packet.AddState(0x6f9, 0x0); // 23 1785 unk, bs?
|
|
packet.AddState(0x6fb, 0x0); // 24 1787 gold mine (0 - horde contr, 1 - alliance contr)
|
|
packet.AddState(0x6fc, 0x0); // 25 1788 gold mine (0 - conflict, 1 - horde)
|
|
packet.AddState(0x6fd, 0x0); // 26 1789 gold mine (1 - show/0 - hide)
|
|
packet.AddState(0x6fe, 0x0); // 27 1790 gold mine color
|
|
packet.AddState(0x700, 0x0); // 28 1792 gold mine color, wtf?, may be LM?
|
|
packet.AddState(0x701, 0x0); // 29 1793 lumber mill color (0 - conflict, 1 - horde contr)
|
|
packet.AddState(0x702, 0x0); // 30 1794 lumber mill (show/hide)
|
|
packet.AddState(0x703, 0x0); // 31 1795 lumber mill color color
|
|
packet.AddState(0x732, 0x1); // 32 1842 stables (1 - uncontrolled)
|
|
packet.AddState(0x733, 0x1); // 33 1843 gold mine (1 - uncontrolled)
|
|
packet.AddState(0x734, 0x1); // 34 1844 lumber mill (1 - uncontrolled)
|
|
packet.AddState(0x735, 0x1); // 35 1845 farm (1 - uncontrolled)
|
|
packet.AddState(0x736, 0x1); // 36 1846 blacksmith (1 - uncontrolled)
|
|
packet.AddState(0x745, 0x2); // 37 1861 unk
|
|
packet.AddState(0x7a3, 0x708); // 38 1955 warning limit (1800)
|
|
}
|
|
break;
|
|
case 3820: // Eye of the Storm
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.EY)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0xac1, 0x0); // 7 2753 Horde Bases
|
|
packet.AddState(0xac0, 0x0); // 8 2752 Alliance Bases
|
|
packet.AddState(0xab6, 0x0); // 9 2742 Mage Tower - Horde conflict
|
|
packet.AddState(0xab5, 0x0); // 10 2741 Mage Tower - Alliance conflict
|
|
packet.AddState(0xab4, 0x0); // 11 2740 Fel Reaver - Horde conflict
|
|
packet.AddState(0xab3, 0x0); // 12 2739 Fel Reaver - Alliance conflict
|
|
packet.AddState(0xab2, 0x0); // 13 2738 Draenei - Alliance conflict
|
|
packet.AddState(0xab1, 0x0); // 14 2737 Draenei - Horde conflict
|
|
packet.AddState(0xab0, 0x0); // 15 2736 unk // 0 at start
|
|
packet.AddState(0xaaf, 0x0); // 16 2735 unk // 0 at start
|
|
packet.AddState(0xaad, 0x0); // 17 2733 Draenei - Horde control
|
|
packet.AddState(0xaac, 0x0); // 18 2732 Draenei - Alliance control
|
|
packet.AddState(0xaab, 0x1); // 19 2731 Draenei uncontrolled (1 - yes, 0 - no)
|
|
packet.AddState(0xaaa, 0x0); // 20 2730 Mage Tower - Alliance control
|
|
packet.AddState(0xaa9, 0x0); // 21 2729 Mage Tower - Horde control
|
|
packet.AddState(0xaa8, 0x1); // 22 2728 Mage Tower uncontrolled (1 - yes, 0 - no)
|
|
packet.AddState(0xaa7, 0x0); // 23 2727 Fel Reaver - Horde control
|
|
packet.AddState(0xaa6, 0x0); // 24 2726 Fel Reaver - Alliance control
|
|
packet.AddState(0xaa5, 0x1); // 25 2725 Fel Reaver uncontrolled (1 - yes, 0 - no)
|
|
packet.AddState(0xaa4, 0x0); // 26 2724 Boold Elf - Horde control
|
|
packet.AddState(0xaa3, 0x0); // 27 2723 Boold Elf - Alliance control
|
|
packet.AddState(0xaa2, 0x1); // 28 2722 Boold Elf uncontrolled (1 - yes, 0 - no)
|
|
packet.AddState(0xac5, 0x1); // 29 2757 Flag (1 - show, 0 - hide) - doesn't work exactly this way!
|
|
packet.AddState(0xad2, 0x1); // 30 2770 Horde top-stats (1 - show, 0 - hide) // 02 . horde picked up the flag
|
|
packet.AddState(0xad1, 0x1); // 31 2769 Alliance top-stats (1 - show, 0 - hide) // 02 . alliance picked up the flag
|
|
packet.AddState(0xabe, 0x0); // 32 2750 Horde resources
|
|
packet.AddState(0xabd, 0x0); // 33 2749 Alliance resources
|
|
packet.AddState(0xa05, 0x8e); // 34 2565 unk, constant?
|
|
packet.AddState(0xaa0, 0x0); // 35 2720 Capturing progress-bar (100 . empty (only grey), 0 . blue|red (no grey), default 0)
|
|
packet.AddState(0xa9f, 0x0); // 36 2719 Capturing progress-bar (0 - left, 100 - right)
|
|
packet.AddState(0xa9e, 0x0); // 37 2718 Capturing progress-bar (1 - show, 0 - hide)
|
|
packet.AddState(0xc0d, 0x17b); // 38 3085 unk
|
|
// and some more ... unknown
|
|
}
|
|
break;
|
|
// any of these needs change! the client remembers the prev setting!
|
|
// ON EVERY ZONE LEAVE, RESET THE OLD ZONE'S WORLD STATE, BUT AT LEAST THE UI STUFF!
|
|
case 3483: // Hellfire Peninsula
|
|
if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.HellfirePeninsula)
|
|
pvp.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x9ba, 0x1); // 10 // add ally tower main gui icon // maybe should be sent only on login?
|
|
packet.AddState(0x9b9, 0x1); // 11 // add horde tower main gui icon // maybe should be sent only on login?
|
|
packet.AddState(0x9b5, 0x0); // 12 // show neutral broken hill icon // 2485
|
|
packet.AddState(0x9b4, 0x1); // 13 // show icon above broken hill // 2484
|
|
packet.AddState(0x9b3, 0x0); // 14 // show ally broken hill icon // 2483
|
|
packet.AddState(0x9b2, 0x0); // 15 // show neutral overlook icon // 2482
|
|
packet.AddState(0x9b1, 0x1); // 16 // show the overlook arrow // 2481
|
|
packet.AddState(0x9b0, 0x0); // 17 // show ally overlook icon // 2480
|
|
packet.AddState(0x9ae, 0x0); // 18 // horde pvp objectives captured // 2478
|
|
packet.AddState(0x9ac, 0x0); // 19 // ally pvp objectives captured // 2476
|
|
packet.AddState(2475, 100); //: ally / horde slider grey area // show only in direct vicinity!
|
|
packet.AddState(2474, 50); //: ally / horde slider percentage, 100 for ally, 0 for horde // show only in direct vicinity!
|
|
packet.AddState(2473, 0); //: ally / horde slider display // show only in direct vicinity!
|
|
packet.AddState(0x9a8, 0x0); // 20 // show the neutral stadium icon // 2472
|
|
packet.AddState(0x9a7, 0x0); // 21 // show the ally stadium icon // 2471
|
|
packet.AddState(0x9a6, 0x1); // 22 // show the horde stadium icon // 2470
|
|
}
|
|
break;
|
|
case 3518: // Nagrand
|
|
if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.Nagrand)
|
|
pvp.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(2503, 0x0); // 10
|
|
packet.AddState(2502, 0x0); // 11
|
|
packet.AddState(2493, 0x0); // 12
|
|
packet.AddState(2491, 0x0); // 13
|
|
|
|
packet.AddState(2495, 0x0); // 14
|
|
packet.AddState(2494, 0x0); // 15
|
|
packet.AddState(2497, 0x0); // 16
|
|
|
|
packet.AddState(2762, 0x0); // 17
|
|
packet.AddState(2662, 0x0); // 18
|
|
packet.AddState(2663, 0x0); // 19
|
|
packet.AddState(2664, 0x0); // 20
|
|
|
|
packet.AddState(2760, 0x0); // 21
|
|
packet.AddState(2670, 0x0); // 22
|
|
packet.AddState(2668, 0x0); // 23
|
|
packet.AddState(2669, 0x0); // 24
|
|
|
|
packet.AddState(2761, 0x0); // 25
|
|
packet.AddState(2667, 0x0); // 26
|
|
packet.AddState(2665, 0x0); // 27
|
|
packet.AddState(2666, 0x0); // 28
|
|
|
|
packet.AddState(2763, 0x0); // 29
|
|
packet.AddState(2659, 0x0); // 30
|
|
packet.AddState(2660, 0x0); // 31
|
|
packet.AddState(2661, 0x0); // 32
|
|
|
|
packet.AddState(2671, 0x0); // 33
|
|
packet.AddState(2676, 0x0); // 34
|
|
packet.AddState(2677, 0x0); // 35
|
|
packet.AddState(2672, 0x0); // 36
|
|
packet.AddState(2673, 0x0); // 37
|
|
}
|
|
break;
|
|
case 3519: // Terokkar Forest
|
|
if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.TerokkarForest)
|
|
pvp.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0xa41, 0x0); // 10 // 2625 capture bar pos
|
|
packet.AddState(0xa40, 0x14); // 11 // 2624 capture bar neutral
|
|
packet.AddState(0xa3f, 0x0); // 12 // 2623 show capture bar
|
|
packet.AddState(0xa3e, 0x0); // 13 // 2622 horde towers controlled
|
|
packet.AddState(0xa3d, 0x5); // 14 // 2621 ally towers controlled
|
|
packet.AddState(0xa3c, 0x0); // 15 // 2620 show towers controlled
|
|
packet.AddState(0xa88, 0x0); // 16 // 2696 SE Neu
|
|
packet.AddState(0xa87, 0x0); // 17 // SE Horde
|
|
packet.AddState(0xa86, 0x0); // 18 // SE Ally
|
|
packet.AddState(0xa85, 0x0); // 19 //S Neu
|
|
packet.AddState(0xa84, 0x0); // 20 S Horde
|
|
packet.AddState(0xa83, 0x0); // 21 S Ally
|
|
packet.AddState(0xa82, 0x0); // 22 NE Neu
|
|
packet.AddState(0xa81, 0x0); // 23 NE Horde
|
|
packet.AddState(0xa80, 0x0); // 24 NE Ally
|
|
packet.AddState(0xa7e, 0x0); // 25 // 2686 N Neu
|
|
packet.AddState(0xa7d, 0x0); // 26 N Horde
|
|
packet.AddState(0xa7c, 0x0); // 27 N Ally
|
|
packet.AddState(0xa7b, 0x0); // 28 NW Ally
|
|
packet.AddState(0xa7a, 0x0); // 29 NW Horde
|
|
packet.AddState(0xa79, 0x0); // 30 NW Neutral
|
|
packet.AddState(0x9d0, 0x5); // 31 // 2512 locked time remaining seconds first digit
|
|
packet.AddState(0x9ce, 0x0); // 32 // 2510 locked time remaining seconds second digit
|
|
packet.AddState(0x9cd, 0x0); // 33 // 2509 locked time remaining minutes
|
|
packet.AddState(0x9cc, 0x0); // 34 // 2508 neutral locked time show
|
|
packet.AddState(0xad0, 0x0); // 35 // 2768 horde locked time show
|
|
packet.AddState(0xacf, 0x1); // 36 // 2767 ally locked time show
|
|
}
|
|
break;
|
|
case 3521: // Zangarmarsh
|
|
if (pvp != null && pvp.GetTypeId() == OutdoorPvPTypes.Zangarmarsh)
|
|
pvp.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x9e1, 0x0); // 10 //2529
|
|
packet.AddState(0x9e0, 0x0); // 11
|
|
packet.AddState(0x9df, 0x0); // 12
|
|
packet.AddState(0xa5d, 0x1); // 13 //2653
|
|
packet.AddState(0xa5c, 0x0); // 14 //2652 east beacon neutral
|
|
packet.AddState(0xa5b, 0x1); // 15 horde
|
|
packet.AddState(0xa5a, 0x0); // 16 ally
|
|
packet.AddState(0xa59, 0x1); // 17 // 2649 Twin spire graveyard horde 12???
|
|
packet.AddState(0xa58, 0x0); // 18 ally 14 ???
|
|
packet.AddState(0xa57, 0x0); // 19 neutral 7???
|
|
packet.AddState(0xa56, 0x0); // 20 // 2646 west beacon neutral
|
|
packet.AddState(0xa55, 0x1); // 21 horde
|
|
packet.AddState(0xa54, 0x0); // 22 ally
|
|
packet.AddState(0x9e7, 0x0); // 23 // 2535
|
|
packet.AddState(0x9e6, 0x0); // 24
|
|
packet.AddState(0x9e5, 0x0); // 25
|
|
packet.AddState(0xa00, 0x0); // 26 // 2560
|
|
packet.AddState(0x9ff, 0x1); // 27
|
|
packet.AddState(0x9fe, 0x0); // 28
|
|
packet.AddState(0x9fd, 0x0); // 29
|
|
packet.AddState(0x9fc, 0x1); // 30
|
|
packet.AddState(0x9fb, 0x0); // 31
|
|
packet.AddState(0xa62, 0x0); // 32 // 2658
|
|
packet.AddState(0xa61, 0x1); // 33
|
|
packet.AddState(0xa60, 0x1); // 34
|
|
packet.AddState(0xa5f, 0x0); // 35
|
|
}
|
|
break;
|
|
case 3698: // Nagrand Arena
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.NA)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0xa0f, 0x0); // 7
|
|
packet.AddState(0xa10, 0x0); // 8
|
|
packet.AddState(0xa11, 0x0); // 9 show
|
|
}
|
|
break;
|
|
case 3702: // Blade's Edge Arena
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.BE)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x9f0, 0x0); // 7 gold
|
|
packet.AddState(0x9f1, 0x0); // 8 green
|
|
packet.AddState(0x9f3, 0x0); // 9 show
|
|
}
|
|
break;
|
|
case 3968: // Ruins of Lordaeron
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.RL)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0xbb8, 0x0); // 7 gold
|
|
packet.AddState(0xbb9, 0x0); // 8 green
|
|
packet.AddState(0xbba, 0x0); // 9 show
|
|
}
|
|
break;
|
|
case 4378: // Dalaran Sewers
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.DS)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(3601, 0x0); // 7 gold
|
|
packet.AddState(3600, 0x0); // 8 green
|
|
packet.AddState(3610, 0x0); // 9 show
|
|
}
|
|
break;
|
|
case 4384: // Strand of the Ancients
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.SA)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
// 1-3 A defend, 4-6 H defend, 7-9 unk defend, 1 - ok, 2 - half destroyed, 3 - destroyed
|
|
packet.AddState(0xf09, 0x0); // 7 3849 Gate of Temple
|
|
packet.AddState(0xe36, 0x0); // 8 3638 Gate of Yellow Moon
|
|
packet.AddState(0xe27, 0x0); // 9 3623 Gate of Green Emerald
|
|
packet.AddState(0xe24, 0x0); // 10 3620 Gate of Blue Sapphire
|
|
packet.AddState(0xe21, 0x0); // 11 3617 Gate of Red Sun
|
|
packet.AddState(0xe1e, 0x0); // 12 3614 Gate of Purple Ametyst
|
|
|
|
packet.AddState(0xdf3, 0x0); // 13 3571 bonus timer (1 - on, 0 - off)
|
|
packet.AddState(0xded, 0x0); // 14 3565 Horde Attacker
|
|
packet.AddState(0xdec, 0x0); // 15 3564 Alliance Attacker
|
|
// End Round (timer), better explain this by example, eg. ends in 19:59 . A:BC
|
|
packet.AddState(0xde9, 0x0); // 16 3561 C
|
|
packet.AddState(0xde8, 0x0); // 17 3560 B
|
|
packet.AddState(0xde7, 0x0); // 18 3559 A
|
|
packet.AddState(0xe35, 0x0); // 19 3637 East g - Horde control
|
|
packet.AddState(0xe34, 0x0); // 20 3636 West g - Horde control
|
|
packet.AddState(0xe33, 0x0); // 21 3635 South g - Horde control
|
|
packet.AddState(0xe32, 0x0); // 22 3634 East g - Alliance control
|
|
packet.AddState(0xe31, 0x0); // 23 3633 West g - Alliance control
|
|
packet.AddState(0xe30, 0x0); // 24 3632 South g - Alliance control
|
|
packet.AddState(0xe2f, 0x0); // 25 3631 Chamber of Ancients - Horde control
|
|
packet.AddState(0xe2e, 0x0); // 26 3630 Chamber of Ancients - Alliance control
|
|
packet.AddState(0xe2d, 0x0); // 27 3629 Beach1 - Horde control
|
|
packet.AddState(0xe2c, 0x0); // 28 3628 Beach2 - Horde control
|
|
packet.AddState(0xe2b, 0x0); // 29 3627 Beach1 - Alliance control
|
|
packet.AddState(0xe2a, 0x0); // 30 3626 Beach2 - Alliance control
|
|
// and many unks...
|
|
}
|
|
break;
|
|
case 4406: // Ring of Valor
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.RV)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0xe10, 0x0); // 7 gold
|
|
packet.AddState(0xe11, 0x0); // 8 green
|
|
packet.AddState(0xe1a, 0x0); // 9 show
|
|
}
|
|
break;
|
|
case 4710:
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.IC)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(4221, 1); // 7 BG_IC_ALLIANCE_RENFORT_SET
|
|
packet.AddState(4222, 1); // 8 BG_IC_HORDE_RENFORT_SET
|
|
packet.AddState(4226, 300); // 9 BG_IC_ALLIANCE_RENFORT
|
|
packet.AddState(4227, 300); // 10 BG_IC_HORDE_RENFORT
|
|
packet.AddState(4322, 1); // 11 BG_IC_GATE_FRONT_H_WS_OPEN
|
|
packet.AddState(4321, 1); // 12 BG_IC_GATE_WEST_H_WS_OPEN
|
|
packet.AddState(4320, 1); // 13 BG_IC_GATE_EAST_H_WS_OPEN
|
|
packet.AddState(4323, 1); // 14 BG_IC_GATE_FRONT_A_WS_OPEN
|
|
packet.AddState(4324, 1); // 15 BG_IC_GATE_WEST_A_WS_OPEN
|
|
packet.AddState(4325, 1); // 16 BG_IC_GATE_EAST_A_WS_OPEN
|
|
packet.AddState(4317, 1); // 17 unknown
|
|
|
|
packet.AddState(4301, 1); // 18 BG_IC_DOCKS_UNCONTROLLED
|
|
packet.AddState(4296, 1); // 19 BG_IC_HANGAR_UNCONTROLLED
|
|
packet.AddState(4306, 1); // 20 BG_IC_QUARRY_UNCONTROLLED
|
|
packet.AddState(4311, 1); // 21 BG_IC_REFINERY_UNCONTROLLED
|
|
packet.AddState(4294, 1); // 22 BG_IC_WORKSHOP_UNCONTROLLED
|
|
packet.AddState(4243, 1); // 23 unknown
|
|
packet.AddState(4345, 1); // 24 unknown
|
|
}
|
|
break;
|
|
// The Ruby Sanctum
|
|
case 4987:
|
|
if (instance != null && mapid == 724)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(5049, 50); // 9 WORLDSTATE_CORPOREALITY_MATERIAL
|
|
packet.AddState(5050, 50); // 10 WORLDSTATE_CORPOREALITY_TWILIGHT
|
|
packet.AddState(5051, 0); // 11 WORLDSTATE_CORPOREALITY_TOGGLE
|
|
}
|
|
break;
|
|
// Icecrown Citadel
|
|
case 4812:
|
|
if (instance != null && mapid == 631)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(4903, 0); // 9 WORLDSTATE_SHOW_TIMER (Blood Quickening weekly)
|
|
packet.AddState(4904, 30); // 10 WORLDSTATE_EXECUTION_TIME
|
|
packet.AddState(4940, 0); // 11 WORLDSTATE_SHOW_ATTEMPTS
|
|
packet.AddState(4941, 50); // 12 WORLDSTATE_ATTEMPTS_REMAINING
|
|
packet.AddState(4942, 50); // 13 WORLDSTATE_ATTEMPTS_MAX
|
|
}
|
|
break;
|
|
// The Culling of Stratholme
|
|
case 4100:
|
|
if (instance != null && mapid == 595)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(3479, 0); // 9 WORLDSTATE_SHOW_CRATES
|
|
packet.AddState(3480, 0); // 10 WORLDSTATE_CRATES_REVEALED
|
|
packet.AddState(3504, 0); // 11 WORLDSTATE_WAVE_COUNT
|
|
packet.AddState(3931, 25); // 12 WORLDSTATE_TIME_GUARDIAN
|
|
packet.AddState(3932, 0); // 13 WORLDSTATE_TIME_GUARDIAN_SHOW
|
|
}
|
|
break;
|
|
// The Oculus
|
|
case 4228:
|
|
if (instance != null && mapid == 578)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(3524, 0); // 9 WORLD_STATE_CENTRIFUGE_CONSTRUCT_SHOW
|
|
packet.AddState(3486, 0); // 10 WORLD_STATE_CENTRIFUGE_CONSTRUCT_AMOUNT
|
|
}
|
|
break;
|
|
// Ulduar
|
|
case 4273:
|
|
if (instance != null && mapid == 603)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(4132, 0); // 9 WORLDSTATE_ALGALON_TIMER_ENABLED
|
|
packet.AddState(4131, 0); // 10 WORLDSTATE_ALGALON_DESPAWN_TIMER
|
|
}
|
|
break;
|
|
// Halls of Refection
|
|
case 4820:
|
|
if (instance != null && mapid == 668)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(4884, 0); // 9 WORLD_STATE_HOR_WAVES_ENABLED
|
|
packet.AddState(4882, 0); // 10 WORLD_STATE_HOR_WAVE_COUNT
|
|
}
|
|
break;
|
|
// Zul Aman
|
|
case 3805:
|
|
if (instance != null && mapid == 568)
|
|
instance.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(3104, 0); // 9 WORLD_STATE_ZULAMAN_TIMER_ENABLED
|
|
packet.AddState(3106, 0); // 10 WORLD_STATE_ZULAMAN_TIMER
|
|
}
|
|
break;
|
|
// Twin Peaks
|
|
case 5031:
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.TP)
|
|
bg.FillInitialWorldStates(packet);
|
|
else
|
|
{
|
|
packet.AddState(0x62d, 0x0); // 7 1581 alliance flag captures
|
|
packet.AddState(0x62e, 0x0); // 8 1582 horde flag captures
|
|
packet.AddState(0x609, 0x0); // 9 1545 unk
|
|
packet.AddState(0x60a, 0x0); // 10 1546 unk
|
|
packet.AddState(0x60b, 0x2); // 11 1547 unk
|
|
packet.AddState(0x641, 0x3); // 12 1601 unk
|
|
packet.AddState(0x922, 0x1); // 13 2338 horde (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
|
|
packet.AddState(0x923, 0x1); // 14 2339 alliance (0 - hide, 1 - flag ok, 2 - flag picked up (flashing), 3 - flag picked up (not flashing)
|
|
}
|
|
break;
|
|
// Battle for Gilneas
|
|
case 5449:
|
|
if (bg && bg.GetTypeID(true) == BattlegroundTypeId.BFG)
|
|
bg.FillInitialWorldStates(packet);
|
|
break;
|
|
// Wintergrasp
|
|
case 4197:
|
|
if (bf != null && bf.GetTypeId() == (uint)BattleFieldTypes.WinterGrasp)
|
|
bf.FillInitialWorldStates(packet);
|
|
goto default;
|
|
// No break here, intended.
|
|
default:
|
|
packet.AddState(0x914, 0x0); // 7
|
|
packet.AddState(0x913, 0x0); // 8
|
|
packet.AddState(0x912, 0x0); // 9
|
|
packet.AddState(0x915, 0x0); // 10
|
|
break;
|
|
}
|
|
|
|
SendPacket(packet);
|
|
SendBGWeekendWorldStates();
|
|
SendBattlefieldWorldStates();
|
|
}
|
|
|
|
public uint GetBarberShopCost(BarberShopStyleRecord newHairStyle, uint newHairColor, BarberShopStyleRecord newFacialHair, BarberShopStyleRecord newSkin, BarberShopStyleRecord newFace, Array<BarberShopStyleRecord> newCustomDisplay)
|
|
{
|
|
byte hairstyle = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId);
|
|
byte haircolor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId);
|
|
byte facialhair = GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle);
|
|
byte skincolor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId);
|
|
byte face = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId);
|
|
|
|
Array<byte> customDisplay = new Array<byte>(PlayerConst.CustomDisplaySize);
|
|
for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i)
|
|
customDisplay[i] = GetByteValue(PlayerFields.Bytes2, (byte)(PlayerFieldOffsets.Bytes2OffsetCustomDisplayOption + i));
|
|
|
|
if ((hairstyle == newHairStyle.Data) &&
|
|
(haircolor == newHairColor) &&
|
|
(facialhair == newFacialHair.Data) &&
|
|
(newSkin == null || (newSkin.Data == skincolor)) &&
|
|
(newFace == null || (newFace.Data == face)) &&
|
|
(newCustomDisplay[0] == null || (newCustomDisplay[0].Data == customDisplay[0])) &&
|
|
(newCustomDisplay[1] == null || (newCustomDisplay[1].Data == customDisplay[1])) &&
|
|
(newCustomDisplay[2] == null || (newCustomDisplay[2].Data == customDisplay[2])))
|
|
return 0;
|
|
|
|
GtBarberShopCostBaseRecord bsc = CliDB.BarberShopCostBaseGameTable.GetRow(getLevel());
|
|
if (bsc == null) // shouldn't happen
|
|
return 0xFFFFFFFF;
|
|
|
|
uint cost = 0;
|
|
if (hairstyle != newHairStyle.Data)
|
|
cost += (uint)(bsc.Cost * newHairStyle.CostModifier);
|
|
|
|
if ((haircolor != newHairColor) && (hairstyle == newHairStyle.Data))
|
|
cost += (uint)(bsc.Cost * 0.5f); // +1/2 of price
|
|
|
|
if (facialhair != newFacialHair.Data)
|
|
cost += (uint)(bsc.Cost * newFacialHair.CostModifier);
|
|
|
|
if (newSkin != null && skincolor != newSkin.Data)
|
|
cost += (uint)(bsc.Cost * newSkin.CostModifier);
|
|
|
|
if (newFace != null && face != newFace.Data)
|
|
cost += (uint)(bsc.Cost * newFace.CostModifier);
|
|
|
|
for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i)
|
|
if (newCustomDisplay[i] != null && customDisplay[i] != newCustomDisplay[i].Data)
|
|
cost += (uint)(bsc.Cost * newCustomDisplay[i].CostModifier);
|
|
|
|
return cost;
|
|
}
|
|
|
|
uint GetChampioningFaction() { return m_ChampioningFaction; }
|
|
public void SetChampioningFaction(uint faction) { m_ChampioningFaction = faction; }
|
|
public void setFactionForRace(Race race)
|
|
{
|
|
m_team = TeamForRace(race);
|
|
|
|
ChrRacesRecord rEntry = CliDB.ChrRacesStorage.LookupByKey(race);
|
|
SetFaction(rEntry != null ? (uint)rEntry.FactionID : 0);
|
|
}
|
|
|
|
public void SetResurrectRequestData(Unit caster, uint health, uint mana, uint appliedAura)
|
|
{
|
|
Contract.Assert(!IsResurrectRequested());
|
|
_resurrectionData = new ResurrectionData();
|
|
_resurrectionData.GUID = caster.GetGUID();
|
|
_resurrectionData.Location.WorldRelocate(caster);
|
|
_resurrectionData.Health = health;
|
|
_resurrectionData.Mana = mana;
|
|
_resurrectionData.Aura = appliedAura;
|
|
}
|
|
public void ClearResurrectRequestData()
|
|
{
|
|
_resurrectionData = null;
|
|
}
|
|
|
|
public bool IsRessurectRequestedBy(ObjectGuid guid)
|
|
{
|
|
if (!IsResurrectRequested())
|
|
return false;
|
|
|
|
return !_resurrectionData.GUID.IsEmpty() && _resurrectionData.GUID == guid;
|
|
}
|
|
|
|
public bool IsResurrectRequested() { return _resurrectionData != null; }
|
|
public void ResurrectUsingRequestData()
|
|
{
|
|
// Teleport before resurrecting by player, otherwise the player might get attacked from creatures near his corpse
|
|
TeleportTo(_resurrectionData.Location);
|
|
|
|
if (IsBeingTeleported())
|
|
{
|
|
ScheduleDelayedOperation(PlayerDelayedOperations.ResurrectPlayer);
|
|
return;
|
|
}
|
|
|
|
ResurrectUsingRequestDataImpl();
|
|
}
|
|
|
|
void ResurrectUsingRequestDataImpl()
|
|
{
|
|
ResurrectPlayer(0.0f, false);
|
|
|
|
SetHealth(_resurrectionData.Health);
|
|
SetPower(PowerType.Mana, (int)_resurrectionData.Mana);
|
|
|
|
SetPower(PowerType.Rage, 0);
|
|
SetFullPower(PowerType.Energy);
|
|
SetFullPower(PowerType.Focus);
|
|
SetPower(PowerType.LunarPower, 0);
|
|
|
|
uint aura = _resurrectionData.Aura;
|
|
if (aura != 0)
|
|
CastSpell(this, aura, true, null, null, _resurrectionData.GUID);
|
|
|
|
SpawnCorpseBones();
|
|
}
|
|
|
|
public void UpdateTriggerVisibility()
|
|
{
|
|
if (m_clientGUIDs.Empty())
|
|
return;
|
|
|
|
if (!IsInWorld)
|
|
return;
|
|
|
|
UpdateData udata = new UpdateData(GetMapId());
|
|
foreach (var guid in m_clientGUIDs)
|
|
{
|
|
if (guid.IsCreatureOrVehicle())
|
|
{
|
|
Creature creature = GetMap().GetCreature(guid);
|
|
// Update fields of triggers, transformed units or unselectable units (values dependent on GM state)
|
|
if (creature == null || (!creature.IsTrigger() && !creature.HasAuraType(AuraType.Transform) && !creature.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable)))
|
|
continue;
|
|
|
|
creature.SetFieldNotifyFlag(UpdateFieldFlags.Public);
|
|
creature.BuildValuesUpdateBlockForPlayer(udata, this);
|
|
creature.RemoveFieldNotifyFlag(UpdateFieldFlags.Public);
|
|
}
|
|
else if (guid.IsAnyTypeGameObject())
|
|
{
|
|
GameObject go = GetMap().GetGameObject(guid);
|
|
if (go == null)
|
|
continue;
|
|
|
|
go.SetFieldNotifyFlag(UpdateFieldFlags.Public);
|
|
go.BuildValuesUpdateBlockForPlayer(udata, this);
|
|
go.RemoveFieldNotifyFlag(UpdateFieldFlags.Public);
|
|
}
|
|
}
|
|
|
|
if (!udata.HasData())
|
|
return;
|
|
|
|
UpdateObject packet;
|
|
udata.BuildPacket(out packet);
|
|
SendPacket(packet);
|
|
}
|
|
|
|
public bool isAllowedToLoot(Creature creature)
|
|
{
|
|
if (!creature.IsDead() || !creature.IsDamageEnoughForLootingAndReward())
|
|
return false;
|
|
|
|
if (HasPendingBind())
|
|
return false;
|
|
|
|
Loot loot = creature.loot;
|
|
if (loot.isLooted()) // nothing to loot or everything looted.
|
|
return false;
|
|
if (!loot.hasItemForAll() && !loot.hasItemFor(this)) // no loot in creature for this player
|
|
return false;
|
|
|
|
if (loot.loot_type == LootType.Skinning)
|
|
return creature.GetSkinner() == GetGUID();
|
|
|
|
Group thisGroup = GetGroup();
|
|
if (!thisGroup)
|
|
return this == creature.GetLootRecipient();
|
|
else if (thisGroup != creature.GetLootRecipientGroup())
|
|
return false;
|
|
|
|
switch (thisGroup.GetLootMethod())
|
|
{
|
|
case LootMethod.PersonalLoot:// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot)
|
|
return false;
|
|
case LootMethod.MasterLoot:
|
|
case LootMethod.FreeForAll:
|
|
return true;
|
|
case LootMethod.GroupLoot:
|
|
// may only loot if the player is the loot roundrobin player
|
|
// or item over threshold (so roll(s) can be launched)
|
|
// or if there are free/quest/conditional item for the player
|
|
if (loot.roundRobinPlayer.IsEmpty() || loot.roundRobinPlayer == GetGUID())
|
|
return true;
|
|
|
|
if (loot.hasOverThresholdItem())
|
|
return true;
|
|
|
|
return loot.hasItemFor(this);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index)
|
|
{
|
|
SpellEffectInfo effect = spellInfo.GetEffect(GetMap().GetDifficultyID(), index);
|
|
if (effect == null || !effect.IsEffect())
|
|
return false;
|
|
|
|
// players are immune to taunt (the aura and the spell effect).
|
|
if (effect.IsAura(AuraType.ModTaunt))
|
|
return true;
|
|
if (effect.IsEffect(SpellEffectName.AttackMe))
|
|
return true;
|
|
|
|
return base.IsImmunedToSpellEffect(spellInfo, index);
|
|
}
|
|
|
|
void RegenerateAll()
|
|
{
|
|
m_regenTimerCount += m_regenTimer;
|
|
|
|
for (PowerType power = PowerType.Mana; power < PowerType.Max; power++)// = power + 1)
|
|
if (power != PowerType.Runes)
|
|
Regenerate(power);
|
|
|
|
// Runes act as cooldowns, and they don't need to send any data
|
|
if (GetClass() == Class.Deathknight)
|
|
{
|
|
uint regeneratedRunes = 0;
|
|
int regenIndex = 0;
|
|
while (regeneratedRunes < PlayerConst.MaxRechargingRunes && m_runes.CooldownOrder.Count > regenIndex)
|
|
{
|
|
byte runeToRegen = m_runes.CooldownOrder[regenIndex];
|
|
uint runeCooldown = GetRuneCooldown(runeToRegen);
|
|
if (runeCooldown > m_regenTimer)
|
|
{
|
|
SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer);
|
|
++regenIndex;
|
|
}
|
|
else
|
|
SetRuneCooldown(runeToRegen, 0);
|
|
|
|
++regeneratedRunes;
|
|
}
|
|
}
|
|
|
|
if (m_regenTimerCount >= 2000)
|
|
{
|
|
// Not in combat or they have regeneration
|
|
if (!IsInCombat() || IsPolymorphed() || m_baseHealthRegen != 0 || HasAuraType(AuraType.ModRegenDuringCombat) || HasAuraType(AuraType.ModHealthRegenInCombat))
|
|
RegenerateHealth();
|
|
|
|
m_regenTimerCount -= 2000;
|
|
}
|
|
|
|
m_regenTimer = 0;
|
|
}
|
|
void Regenerate(PowerType power)
|
|
{
|
|
// Skip regeneration for power type we cannot have
|
|
uint powerIndex = GetPowerIndex(power);
|
|
if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass)
|
|
return;
|
|
|
|
// @todo possible use of miscvalueb instead of amount
|
|
if (HasAuraTypeWithValue(AuraType.PreventRegeneratePower, (int)power))
|
|
return;
|
|
|
|
int curValue = GetPower(power);
|
|
|
|
// TODO: updating haste should update UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER for certain power types
|
|
PowerTypeRecord powerType = Global.DB2Mgr.GetPowerTypeEntry(power);
|
|
if (powerType == null)
|
|
return;
|
|
|
|
float addvalue = 0.0f;
|
|
|
|
if (!IsInCombat())
|
|
{
|
|
if (powerType.RegenerationDelay != 0 && Time.GetMSTimeDiffToNow(m_combatExitTime) < powerType.RegenerationDelay)
|
|
return;
|
|
|
|
addvalue = (powerType.RegenerationPeace + GetFloatValue(UnitFields.PowerRegenFlatModifier + (int)powerIndex)) * 0.001f * m_regenTimer;
|
|
}
|
|
else
|
|
addvalue = (powerType.RegenerationCombat + GetFloatValue(UnitFields.PowerRegenInterruptedFlatModifier + (int)powerIndex)) * 0.001f * m_regenTimer;
|
|
|
|
WorldCfg[] RatesForPower =
|
|
{
|
|
WorldCfg.RatePowerMana,
|
|
WorldCfg.RatePowerRageLoss,
|
|
WorldCfg.RatePowerFocus,
|
|
WorldCfg.RatePowerEnergy,
|
|
WorldCfg.RatePowerComboPointsLoss,
|
|
0, // runes
|
|
WorldCfg.RatePowerRunicPowerLoss,
|
|
WorldCfg.RatePowerSoulShards,
|
|
WorldCfg.RatePowerLunarPower,
|
|
WorldCfg.RatePowerHolyPower,
|
|
0, // alternate
|
|
WorldCfg.RatePowerMaelstrom,
|
|
WorldCfg.RatePowerChi,
|
|
WorldCfg.RatePowerInsanity,
|
|
0, // burning embers, unused
|
|
0, // demonic fury, unused
|
|
WorldCfg.RatePowerArcaneCharges,
|
|
WorldCfg.RatePowerFury,
|
|
WorldCfg.RatePowerPain,
|
|
};
|
|
|
|
if (RatesForPower[(int)power] != 0)
|
|
addvalue *= WorldConfig.GetFloatValue(RatesForPower[(int)power]);
|
|
|
|
// Mana regen calculated in Player.UpdateManaRegen()
|
|
if (power != PowerType.Mana)
|
|
{
|
|
var ModPowerRegenPCTAuras = GetAuraEffectsByType(AuraType.ModPowerRegenPercent);
|
|
foreach (var eff in ModPowerRegenPCTAuras)
|
|
if ((PowerType)eff.GetMiscValue() == power)
|
|
MathFunctions.AddPct(ref addvalue, eff.GetAmount());
|
|
|
|
addvalue += GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)power) * ((power != PowerType.Energy) ? m_regenTimerCount : m_regenTimer) / (5 * Time.InMilliseconds);
|
|
}
|
|
|
|
int minPower = powerType.RegenerationMin;
|
|
int maxPower = GetMaxPower(power);
|
|
|
|
if (addvalue < 0.0f)
|
|
{
|
|
if (curValue <= minPower)
|
|
return;
|
|
}
|
|
else if (addvalue > 0.0f)
|
|
{
|
|
if (curValue >= maxPower)
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
|
|
addvalue += m_powerFraction[powerIndex];
|
|
int integerValue = (int)Math.Abs(addvalue);
|
|
|
|
if (powerType.RegenerationCenter != 0)
|
|
{
|
|
if (curValue > powerType.RegenerationCenter)
|
|
{
|
|
addvalue = -Math.Abs(addvalue);
|
|
minPower = powerType.RegenerationCenter;
|
|
}
|
|
else if (curValue < powerType.RegenerationCenter)
|
|
{
|
|
addvalue = Math.Abs(addvalue);
|
|
maxPower = powerType.RegenerationCenter;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (addvalue < 0.0f)
|
|
{
|
|
if (curValue > minPower + integerValue)
|
|
{
|
|
curValue -= integerValue;
|
|
m_powerFraction[powerIndex] = addvalue + integerValue;
|
|
}
|
|
else
|
|
{
|
|
curValue = minPower;
|
|
m_powerFraction[powerIndex] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (curValue + integerValue <= maxPower)
|
|
{
|
|
curValue += integerValue;
|
|
m_powerFraction[powerIndex] = addvalue - integerValue;
|
|
}
|
|
else
|
|
{
|
|
curValue = maxPower;
|
|
m_powerFraction[powerIndex] = 0;
|
|
}
|
|
}
|
|
|
|
if (m_regenTimerCount >= 2000)
|
|
SetPower(power, curValue);
|
|
else
|
|
UpdateUInt32Value(UnitFields.Power + (int)powerIndex, (uint)curValue);
|
|
}
|
|
void RegenerateHealth()
|
|
{
|
|
uint curValue = (uint)GetHealth();
|
|
uint maxValue = (uint)GetMaxHealth();
|
|
|
|
if (curValue >= maxValue)
|
|
return;
|
|
|
|
float HealthIncreaseRate = WorldConfig.GetFloatValue(WorldCfg.RateHealth);
|
|
float addvalue = 0.0f;
|
|
|
|
// polymorphed case
|
|
if (IsPolymorphed())
|
|
addvalue = (float)GetMaxHealth() / 3;
|
|
// normal regen case (maybe partly in combat case)
|
|
else if (!IsInCombat() || HasAuraType(AuraType.ModRegenDuringCombat))
|
|
{
|
|
addvalue = HealthIncreaseRate;
|
|
if (!IsInCombat())
|
|
{
|
|
if (getLevel() < 15)
|
|
addvalue = (0.20f * (GetMaxHealth()) / getLevel() * HealthIncreaseRate);
|
|
else
|
|
addvalue = 0.015f * (GetMaxHealth()) * HealthIncreaseRate;
|
|
|
|
var mModHealthRegenPct = GetAuraEffectsByType(AuraType.ModHealthRegenPercent);
|
|
foreach (var eff in mModHealthRegenPct)
|
|
MathFunctions.AddPct(ref addvalue, eff.GetAmount());
|
|
|
|
addvalue += GetTotalAuraModifier(AuraType.ModRegen) * 2 * Time.InMilliseconds / (5 * Time.InMilliseconds);
|
|
}
|
|
else if (HasAuraType(AuraType.ModRegenDuringCombat))
|
|
MathFunctions.ApplyPct(ref addvalue, GetTotalAuraModifier(AuraType.ModRegenDuringCombat));
|
|
|
|
if (!IsStandState())
|
|
addvalue *= 1.5f;
|
|
}
|
|
|
|
// always regeneration bonus (including combat)
|
|
addvalue += GetTotalAuraModifier(AuraType.ModHealthRegenInCombat);
|
|
addvalue += m_baseHealthRegen / 2.5f;
|
|
|
|
if (addvalue < 0)
|
|
addvalue = 0;
|
|
|
|
ModifyHealth((int)addvalue);
|
|
}
|
|
public void ResetAllPowers()
|
|
{
|
|
SetFullHealth();
|
|
switch (GetPowerType())
|
|
{
|
|
case PowerType.Mana:
|
|
SetFullPower(PowerType.Mana);
|
|
break;
|
|
case PowerType.Rage:
|
|
SetPower(PowerType.Rage, 0);
|
|
break;
|
|
case PowerType.Energy:
|
|
SetFullPower(PowerType.Energy);
|
|
break;
|
|
case PowerType.RunicPower:
|
|
SetPower(PowerType.RunicPower, 0);
|
|
break;
|
|
case PowerType.LunarPower:
|
|
SetPower(PowerType.LunarPower, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
public Unit GetSelectedUnit()
|
|
{
|
|
ObjectGuid selectionGUID = GetTarget();
|
|
if (!selectionGUID.IsEmpty())
|
|
return Global.ObjAccessor.GetUnit(this, selectionGUID);
|
|
return null;
|
|
}
|
|
Player GetSelectedPlayer()
|
|
{
|
|
ObjectGuid selectionGUID = GetTarget();
|
|
if (!selectionGUID.IsEmpty())
|
|
return Global.ObjAccessor.GetPlayer(this, selectionGUID);
|
|
return null;
|
|
}
|
|
|
|
static uint GetSelectionFromContext(uint context, Class playerClass)
|
|
{
|
|
switch (context)
|
|
{
|
|
case 1:
|
|
if (playerClass == Class.Deathknight)
|
|
return 1;
|
|
if (playerClass == Class.DemonHunter)
|
|
return 3;
|
|
return 0;
|
|
case 2:
|
|
if (playerClass == Class.Deathknight)
|
|
return 5;
|
|
if (playerClass == Class.DemonHunter)
|
|
return 6;
|
|
return 4;
|
|
case 3:
|
|
return 7;
|
|
case 4:
|
|
if (playerClass == Class.Deathknight)
|
|
return 9;
|
|
if (playerClass == Class.DemonHunter)
|
|
return 10;
|
|
return 8;
|
|
default:
|
|
if (playerClass == Class.Deathknight)
|
|
return 1;
|
|
if (playerClass == Class.DemonHunter)
|
|
return 2;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool ComponentFlagsMatch(CharSectionsRecord entry, uint selection)
|
|
{
|
|
switch (selection)
|
|
{
|
|
case 0:
|
|
if (!entry.Flags.HasAnyFlag((ushort)1))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)0x2C);
|
|
case 1:
|
|
if (!entry.Flags.HasAnyFlag((ushort)1))
|
|
return false;
|
|
if (!entry.Flags.HasAnyFlag((ushort)0x94))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)8);
|
|
case 2:
|
|
if (!entry.Flags.HasAnyFlag((ushort)1))
|
|
return false;
|
|
if (!entry.Flags.HasAnyFlag((ushort)0x70))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)8);
|
|
case 3:
|
|
if (!entry.Flags.HasAnyFlag((ushort)1))
|
|
return false;
|
|
if (!entry.Flags.HasAnyFlag((ushort)0x20))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)8);
|
|
case 4:
|
|
case 8:
|
|
if (!entry.Flags.HasAnyFlag((ushort)3))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)0x2C);
|
|
case 5:
|
|
case 9:
|
|
if (!entry.Flags.HasAnyFlag((ushort)3))
|
|
return false;
|
|
if (!entry.Flags.HasAnyFlag((ushort)0x94))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)8);
|
|
case 6:
|
|
case 10:
|
|
if (!entry.Flags.HasAnyFlag((ushort)3))
|
|
return false;
|
|
if (!entry.Flags.HasAnyFlag((ushort)0x70))
|
|
return false;
|
|
return !entry.Flags.HasAnyFlag((ushort)8);
|
|
case 7:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool IsSectionFlagValid(CharSectionsRecord entry, Class class_, bool create)
|
|
{
|
|
if (create)
|
|
return ComponentFlagsMatch(entry, GetSelectionFromContext(0, class_));
|
|
|
|
return ComponentFlagsMatch(entry, GetSelectionFromContext(2, class_));
|
|
}
|
|
static bool IsSectionValid(Race race, Class class_, Gender gender, CharBaseSectionVariation variation, byte variationIndex, byte colorIndex, bool create)
|
|
{
|
|
CharSectionsRecord section = Global.DB2Mgr.GetCharSectionEntry(race, gender, variation, variationIndex, colorIndex);
|
|
if (section != null)
|
|
return IsSectionFlagValid(section, class_, create);
|
|
|
|
return false;
|
|
}
|
|
public static bool IsValidGender(Gender _gender) { return _gender <= Gender.Female; }
|
|
public static bool IsValidClass(Class _class) { return Convert.ToBoolean((1 << ((int)_class - 1)) & (int)Class.ClassMaskAllPlayable); }
|
|
public static bool IsValidRace(Race _race) { return Convert.ToBoolean((1 << ((int)_race - 1)) & (int)Race.RaceMaskAllPlayable); }
|
|
|
|
public static bool ValidateAppearance(Race race, Class class_, Gender gender, byte hairID, byte hairColor, byte faceID, byte facialHairId, byte skinColor, Array<byte> customDisplay, bool create = false)
|
|
{
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.Skin, 0, skinColor, create))
|
|
return false;
|
|
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.Face, faceID, skinColor, create))
|
|
return false;
|
|
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.Hair, hairID, hairColor, create))
|
|
return false;
|
|
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.FacialHair, facialHairId, hairColor, create))
|
|
if (Global.DB2Mgr.HasCharSections(race, gender, CharBaseSectionVariation.FacialHair) || !Global.DB2Mgr.HasCharacterFacialHairStyle(race, gender, facialHairId))
|
|
return false;
|
|
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.CustomDisplay1, customDisplay[0], 0, create))
|
|
return false;
|
|
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.CustomDisplay2, customDisplay[1], 0, create))
|
|
return false;
|
|
|
|
if (!IsSectionValid(race, class_, gender, CharBaseSectionVariation.CustomDisplay3, customDisplay[2], 0, create))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void OnCombatExit()
|
|
{
|
|
UpdatePotionCooldown();
|
|
m_combatExitTime = Time.GetMSTime();
|
|
}
|
|
|
|
void LeaveLFGChannel()
|
|
{
|
|
foreach (var i in m_channels)
|
|
{
|
|
if (i.IsLFG())
|
|
{
|
|
i.LeaveChannel(this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsImmuneToEnvironmentalDamage()
|
|
{
|
|
// check for GM and death state included in isAttackableByAOE
|
|
return (!isTargetableForAttack(false));
|
|
}
|
|
public uint EnvironmentalDamage(EnviromentalDamage type, uint damage)
|
|
{
|
|
if (IsImmuneToEnvironmentalDamage())
|
|
return 0;
|
|
|
|
// Absorb, resist some environmental damage type
|
|
uint absorb = 0;
|
|
uint resist = 0;
|
|
switch (type)
|
|
{
|
|
case EnviromentalDamage.Lava:
|
|
case EnviromentalDamage.Slime:
|
|
DamageInfo dmgInfo = new DamageInfo(this, this, damage, null, type == EnviromentalDamage.Lava ? SpellSchoolMask.Fire : SpellSchoolMask.Nature, DamageEffectType.Direct, WeaponAttackType.BaseAttack);
|
|
CalcAbsorbResist(dmgInfo);
|
|
absorb = dmgInfo.GetAbsorb();
|
|
resist = dmgInfo.GetResist();
|
|
damage = dmgInfo.GetDamage();
|
|
break;
|
|
}
|
|
|
|
DealDamageMods(this, ref damage, ref absorb);
|
|
|
|
EnvironmentalDamageLog packet = new EnvironmentalDamageLog();
|
|
packet.Victim = GetGUID();
|
|
packet.Type = type != EnviromentalDamage.FallToVoid ? type : EnviromentalDamage.Fall;
|
|
packet.Amount = (int)damage;
|
|
packet.Absorbed = (int)absorb;
|
|
packet.Resisted = (int)resist;
|
|
|
|
uint final_damage = DealDamage(this, damage, null, DamageEffectType.Self, SpellSchoolMask.Normal, null, false);
|
|
packet.LogData.Initialize(this);
|
|
|
|
SendCombatLogMessage(packet);
|
|
|
|
if (!IsAlive())
|
|
{
|
|
if (type == EnviromentalDamage.Fall) // DealDamage not apply item durability loss at self damage
|
|
{
|
|
Log.outDebug(LogFilter.Player, "We are fall to death, loosing 10 percents durability");
|
|
DurabilityLossAll(0.10f, false);
|
|
// durability lost message
|
|
SendDurabilityLoss(this, 10);
|
|
}
|
|
|
|
UpdateCriteria(CriteriaTypes.DeathsFrom, 1, (ulong)type);
|
|
}
|
|
|
|
return final_damage;
|
|
}
|
|
|
|
bool isTotalImmune()
|
|
{
|
|
var immune = GetAuraEffectsByType(AuraType.SchoolImmunity);
|
|
|
|
int immuneMask = 0;
|
|
foreach (var eff in immune)
|
|
{
|
|
immuneMask |= eff.GetMiscValue();
|
|
if (Convert.ToBoolean(immuneMask & (int)SpellSchoolMask.All)) // total immunity
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public override bool CanAlwaysSee(WorldObject obj)
|
|
{
|
|
// Always can see self
|
|
if (m_unitMovedByMe == obj)
|
|
return true;
|
|
|
|
ObjectGuid guid = GetGuidValue(PlayerFields.Farsight);
|
|
if (!guid.IsEmpty())
|
|
if (obj.GetGUID() == guid)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
public override bool IsNeverVisibleFor(WorldObject seer)
|
|
{
|
|
if (base.IsNeverVisibleFor(seer))
|
|
return true;
|
|
|
|
if (GetSession().PlayerLogout() || GetSession().PlayerLoading())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public void BuildPlayerRepop()
|
|
{
|
|
PreRessurect packet = new PreRessurect();
|
|
packet.PlayerGUID = GetGUID();
|
|
SendPacket(packet);
|
|
|
|
if (GetRace() == Race.NightElf)
|
|
CastSpell(this, 20584, true);
|
|
CastSpell(this, 8326, true);
|
|
|
|
// there must be SMSG.FORCE_RUN_SPEED_CHANGE, SMSG.FORCE_SWIM_SPEED_CHANGE, SMSG.MOVE_WATER_WALK
|
|
// there must be SMSG.STOP_MIRROR_TIMER
|
|
|
|
// the player cannot have a corpse already on current map, only bones which are not returned by GetCorpse
|
|
WorldLocation corpseLocation = GetCorpseLocation();
|
|
if (corpseLocation.GetMapId() == GetMapId())
|
|
{
|
|
Log.outError(LogFilter.Player, "BuildPlayerRepop: player {0} ({1}) already has a corpse", GetName(), GetGUID().ToString());
|
|
return;
|
|
}
|
|
|
|
// create a corpse and place it at the player's location
|
|
Corpse corpse = CreateCorpse();
|
|
if (corpse == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Error creating corpse for Player {0} ({1})", GetName(), GetGUID().ToString());
|
|
return;
|
|
}
|
|
GetMap().AddToMap(corpse);
|
|
|
|
// convert player body to ghost
|
|
SetHealth(1);
|
|
|
|
SetWaterWalking(true);
|
|
if (!GetSession().isLogingOut() && !HasUnitState(UnitState.Stunned))
|
|
SetRooted(false);
|
|
|
|
// BG - remove insignia related
|
|
RemoveFlag(UnitFields.Flags, UnitFlags.Skinnable);
|
|
|
|
int corpseReclaimDelay = CalculateCorpseReclaimDelay();
|
|
|
|
if (corpseReclaimDelay >= 0)
|
|
SendCorpseReclaimDelay(corpseReclaimDelay);
|
|
|
|
// to prevent cheating
|
|
corpse.ResetGhostTime();
|
|
|
|
StopMirrorTimers(); //disable timers(bars)
|
|
|
|
// set and clear other
|
|
SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, (byte)UnitBytes1Flags.AlwaysStand);
|
|
}
|
|
|
|
public void StopMirrorTimers()
|
|
{
|
|
StopMirrorTimer(MirrorTimerType.Fatigue);
|
|
StopMirrorTimer(MirrorTimerType.Breath);
|
|
StopMirrorTimer(MirrorTimerType.Fire);
|
|
}
|
|
|
|
public bool IsMirrorTimerActive(MirrorTimerType type)
|
|
{
|
|
return m_MirrorTimer[(int)type] == getMaxTimer(type);
|
|
}
|
|
|
|
void HandleDrowning(uint time_diff)
|
|
{
|
|
if (m_MirrorTimerFlags == 0)
|
|
return;
|
|
|
|
int breathTimer = (int)MirrorTimerType.Breath;
|
|
int fatigueTimer = (int)MirrorTimerType.Fatigue;
|
|
int fireTimer = (int)MirrorTimerType.Fire;
|
|
|
|
// In water
|
|
if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InWater))
|
|
{
|
|
// Breath timer not activated - activate it
|
|
if (m_MirrorTimer[breathTimer] == -1)
|
|
{
|
|
m_MirrorTimer[breathTimer] = getMaxTimer(MirrorTimerType.Breath);
|
|
SendMirrorTimer(MirrorTimerType.Breath, m_MirrorTimer[breathTimer], m_MirrorTimer[breathTimer], -1);
|
|
}
|
|
else // If activated - do tick
|
|
{
|
|
m_MirrorTimer[breathTimer] -= (int)time_diff;
|
|
// Timer limit - need deal damage
|
|
if (m_MirrorTimer[breathTimer] < 0)
|
|
{
|
|
m_MirrorTimer[breathTimer] += 1 * Time.InMilliseconds;
|
|
// Calculate and deal damage
|
|
// @todo Check this formula
|
|
uint damage = (uint)(GetMaxHealth() / 5 + RandomHelper.URand(0, getLevel() - 1));
|
|
EnvironmentalDamage(EnviromentalDamage.Drowning, damage);
|
|
}
|
|
else if (!m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InWater)) // Update time in client if need
|
|
SendMirrorTimer(MirrorTimerType.Breath, getMaxTimer(MirrorTimerType.Breath), m_MirrorTimer[breathTimer], -1);
|
|
}
|
|
}
|
|
else if (m_MirrorTimer[breathTimer] != -1) // Regen timer
|
|
{
|
|
int UnderWaterTime = getMaxTimer(MirrorTimerType.Breath);
|
|
// Need breath regen
|
|
m_MirrorTimer[breathTimer] += (int)(10 * time_diff);
|
|
if (m_MirrorTimer[breathTimer] >= UnderWaterTime || !IsAlive())
|
|
StopMirrorTimer(MirrorTimerType.Breath);
|
|
else if (m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InWater))
|
|
SendMirrorTimer(MirrorTimerType.Breath, UnderWaterTime, m_MirrorTimer[breathTimer], 10);
|
|
}
|
|
|
|
// In dark water
|
|
if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InDarkWater))
|
|
{
|
|
// Fatigue timer not activated - activate it
|
|
if (m_MirrorTimer[fatigueTimer] == -1)
|
|
{
|
|
m_MirrorTimer[fatigueTimer] = getMaxTimer(MirrorTimerType.Fatigue);
|
|
SendMirrorTimer(MirrorTimerType.Fatigue, m_MirrorTimer[fatigueTimer], m_MirrorTimer[fatigueTimer], -1);
|
|
}
|
|
else
|
|
{
|
|
m_MirrorTimer[fatigueTimer] -= (int)time_diff;
|
|
// Timer limit - need deal damage or teleport ghost to graveyard
|
|
if (m_MirrorTimer[fatigueTimer] < 0)
|
|
{
|
|
m_MirrorTimer[fatigueTimer] += 1 * Time.InMilliseconds;
|
|
if (IsAlive()) // Calculate and deal damage
|
|
{
|
|
uint damage = (uint)(GetMaxHealth() / 5 + RandomHelper.URand(0, getLevel() - 1));
|
|
EnvironmentalDamage(EnviromentalDamage.Exhausted, damage);
|
|
}
|
|
else if (HasFlag(PlayerFields.Flags, PlayerFlags.Ghost)) // Teleport ghost to graveyard
|
|
RepopAtGraveyard();
|
|
}
|
|
else if (!m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InDarkWater))
|
|
SendMirrorTimer(MirrorTimerType.Fatigue, getMaxTimer(MirrorTimerType.Fatigue), m_MirrorTimer[fatigueTimer], -1);
|
|
}
|
|
}
|
|
else if (m_MirrorTimer[fatigueTimer] != -1) // Regen timer
|
|
{
|
|
int DarkWaterTime = getMaxTimer(MirrorTimerType.Fatigue);
|
|
m_MirrorTimer[fatigueTimer] += (int)(10 * time_diff);
|
|
if (m_MirrorTimer[fatigueTimer] >= DarkWaterTime || !IsAlive())
|
|
StopMirrorTimer(MirrorTimerType.Fatigue);
|
|
else if (m_MirrorTimerFlagsLast.HasAnyFlag(PlayerUnderwaterState.InDarkWater))
|
|
SendMirrorTimer(MirrorTimerType.Fatigue, DarkWaterTime, m_MirrorTimer[fatigueTimer], 10);
|
|
}
|
|
|
|
if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InLava) && !(_lastLiquid != null && _lastLiquid.SpellID != 0))
|
|
{
|
|
// Breath timer not activated - activate it
|
|
if (m_MirrorTimer[fireTimer] == -1)
|
|
m_MirrorTimer[fireTimer] = getMaxTimer(MirrorTimerType.Fire);
|
|
else
|
|
{
|
|
m_MirrorTimer[fireTimer] -= (int)time_diff;
|
|
if (m_MirrorTimer[fireTimer] < 0)
|
|
{
|
|
m_MirrorTimer[fireTimer] += 1 * Time.InMilliseconds;
|
|
// Calculate and deal damage
|
|
// @todo Check this formula
|
|
uint damage = RandomHelper.URand(600, 700);
|
|
if (m_MirrorTimerFlags.HasAnyFlag(PlayerUnderwaterState.InLava))
|
|
EnvironmentalDamage(EnviromentalDamage.Lava, damage);
|
|
// need to skip Slime damage in Undercity,
|
|
// maybe someone can find better way to handle environmental damage
|
|
//else if (m_zoneUpdateId != 1497)
|
|
// EnvironmentalDamage(DAMAGE_SLIME, damage);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_MirrorTimer[fireTimer] = -1;
|
|
|
|
// Recheck timers flag
|
|
m_MirrorTimerFlags &= ~PlayerUnderwaterState.ExistTimers;
|
|
for (byte i = 0; i < (int)MirrorTimerType.Max; ++i)
|
|
{
|
|
if (m_MirrorTimer[i] != -1)
|
|
{
|
|
m_MirrorTimerFlags |= PlayerUnderwaterState.ExistTimers;
|
|
break;
|
|
}
|
|
}
|
|
m_MirrorTimerFlagsLast = m_MirrorTimerFlags;
|
|
}
|
|
|
|
void HandleSobering()
|
|
{
|
|
m_drunkTimer = 0;
|
|
|
|
byte currentDrunkValue = GetDrunkValue();
|
|
byte drunk = (byte)(currentDrunkValue != 0 ? --currentDrunkValue : 0);
|
|
SetDrunkValue(drunk);
|
|
}
|
|
|
|
void SendMirrorTimer(MirrorTimerType Type, int MaxValue, int CurrentValue, int Regen)
|
|
{
|
|
if (MaxValue == -1)
|
|
{
|
|
if (CurrentValue != -1)
|
|
StopMirrorTimer(Type);
|
|
return;
|
|
}
|
|
|
|
SendPacket(new StartMirrorTimer(Type, CurrentValue, MaxValue, Regen, 0, false));
|
|
}
|
|
|
|
void StopMirrorTimer(MirrorTimerType Type)
|
|
{
|
|
m_MirrorTimer[(int)Type] = -1;
|
|
SendPacket(new StopMirrorTimer(Type));
|
|
}
|
|
|
|
int getMaxTimer(MirrorTimerType timer)
|
|
{
|
|
switch (timer)
|
|
{
|
|
case MirrorTimerType.Fatigue:
|
|
return Time.Minute * Time.InMilliseconds;
|
|
case MirrorTimerType.Breath:
|
|
{
|
|
if (!IsAlive() || HasAuraType(AuraType.WaterBreathing) || GetSession().GetSecurity() >= (AccountTypes)WorldConfig.GetIntValue(WorldCfg.DisableBreathing))
|
|
return -1;
|
|
int UnderWaterTime = 3 * Time.Minute * Time.InMilliseconds;
|
|
var mModWaterBreathing = GetAuraEffectsByType(AuraType.WaterBreathing);
|
|
foreach (var eff in mModWaterBreathing)
|
|
MathFunctions.AddPct(ref UnderWaterTime, eff.GetAmount());
|
|
return UnderWaterTime;
|
|
}
|
|
case MirrorTimerType.Fire:
|
|
{
|
|
if (!IsAlive())
|
|
return -1;
|
|
return 1 * Time.InMilliseconds;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void UpdateMirrorTimers()
|
|
{
|
|
// Desync flags for update on next HandleDrowning
|
|
if (m_MirrorTimerFlags != 0)
|
|
m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags;
|
|
}
|
|
|
|
public void ResurrectPlayer(float restore_percent, bool applySickness = false)
|
|
{
|
|
DeathReleaseLoc packet = new DeathReleaseLoc();
|
|
packet.MapID = -1;
|
|
SendPacket(packet);
|
|
|
|
// speed change, land walk
|
|
|
|
// remove death flag + set aura
|
|
SetByteValue(UnitFields.Bytes1, UnitBytes1Offsets.AnimTier, 0);
|
|
RemoveFlag(PlayerFields.Flags, PlayerFlags.IsOutOfBounds);
|
|
|
|
// This must be called always even on Players with race != RACE_NIGHTELF in case of faction change
|
|
RemoveAurasDueToSpell(20584); // speed bonuses
|
|
RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST
|
|
|
|
if (GetSession().IsARecruiter() || (GetSession().GetRecruiterId() != 0))
|
|
SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.ReferAFriend);
|
|
|
|
setDeathState(DeathState.Alive);
|
|
|
|
// add the flag to make sure opcode is always sent
|
|
AddUnitMovementFlag(MovementFlag.WaterWalk);
|
|
SetWaterWalking(false);
|
|
if (!HasUnitState(UnitState.Stunned))
|
|
SetRooted(false);
|
|
|
|
m_deathTimer = 0;
|
|
|
|
// set health/powers (0- will be set in caller)
|
|
if (restore_percent > 0.0f)
|
|
{
|
|
SetHealth((ulong)(GetMaxHealth() * restore_percent));
|
|
SetPower(PowerType.Mana, (int)(GetMaxPower(PowerType.Mana) * restore_percent));
|
|
SetPower(PowerType.Rage, 0);
|
|
SetPower(PowerType.Energy, (int)(GetMaxPower(PowerType.Energy) * restore_percent));
|
|
SetPower(PowerType.Focus, (int)(GetMaxPower(PowerType.Focus) * restore_percent));
|
|
SetPower(PowerType.LunarPower, 0);
|
|
}
|
|
|
|
// trigger update zone for alive state zone updates
|
|
uint newzone, newarea;
|
|
GetZoneAndAreaId(out newzone, out newarea);
|
|
UpdateZone(newzone, newarea);
|
|
Global.OutdoorPvPMgr.HandlePlayerResurrects(this, newzone);
|
|
|
|
if (InBattleground())
|
|
{
|
|
Battleground bg = GetBattleground();
|
|
if (bg)
|
|
bg.HandlePlayerResurrect(this);
|
|
}
|
|
|
|
// update visibility
|
|
UpdateObjectVisibility();
|
|
|
|
if (!applySickness)
|
|
return;
|
|
|
|
//Characters from level 1-10 are not affected by resurrection sickness.
|
|
//Characters from level 11-19 will suffer from one minute of sickness
|
|
//for each level they are above 10.
|
|
//Characters level 20 and up suffer from ten minutes of sickness.
|
|
int startLevel = WorldConfig.GetIntValue(WorldCfg.DeathSicknessLevel);
|
|
|
|
if (getLevel() >= startLevel)
|
|
{
|
|
// set resurrection sickness
|
|
CastSpell(this, 15007, true);
|
|
|
|
// not full duration
|
|
if (getLevel() < startLevel + 9)
|
|
{
|
|
int delta = (int)(getLevel() - startLevel + 1) * Time.Minute;
|
|
Aura aur = GetAura(15007, GetGUID());
|
|
if (aur != null)
|
|
aur.SetDuration(delta * Time.InMilliseconds);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
public void KillPlayer()
|
|
{
|
|
if (IsFlying() && GetTransport() == null)
|
|
GetMotionMaster().MoveFall();
|
|
|
|
SetRooted(true);
|
|
|
|
StopMirrorTimers(); //disable timers(bars)
|
|
|
|
setDeathState(DeathState.Corpse);
|
|
|
|
SetUInt32Value(ObjectFields.DynamicFlags, 0);
|
|
ApplyModFlag(PlayerFields.LocalFlags, PlayerLocalFlags.ReleaseTimer, !CliDB.MapStorage.LookupByKey(GetMapId()).Instanceable() && !HasAuraType(AuraType.PreventResurrection));
|
|
|
|
// 6 minutes until repop at graveyard
|
|
m_deathTimer = 6 * Time.Minute * Time.InMilliseconds;
|
|
|
|
UpdateCorpseReclaimDelay(); // dependent at use SetDeathPvP() call before kill
|
|
|
|
int corpseReclaimDelay = CalculateCorpseReclaimDelay();
|
|
|
|
if (corpseReclaimDelay >= 0)
|
|
SendCorpseReclaimDelay(corpseReclaimDelay);
|
|
|
|
// don't create corpse at this moment, player might be falling
|
|
|
|
// update visibility
|
|
UpdateObjectVisibility();
|
|
}
|
|
|
|
public static void OfflineResurrect(ObjectGuid guid, SQLTransaction trans)
|
|
{
|
|
Corpse.DeleteFromDB(guid, trans);
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_ADD_AT_LOGIN_FLAG);
|
|
stmt.AddValue(0, (ushort)AtLoginFlags.Resurrect);
|
|
stmt.AddValue(1, guid.GetCounter());
|
|
DB.Characters.ExecuteOrAppend(trans, stmt);
|
|
}
|
|
|
|
Corpse CreateCorpse()
|
|
{
|
|
// prevent existence 2 corpse for player
|
|
SpawnCorpseBones();
|
|
|
|
uint _cfb1, _cfb2;
|
|
|
|
Corpse corpse = new Corpse(Convert.ToBoolean(m_ExtraFlags & PlayerExtraFlags.PVPDeath) ? CorpseType.ResurrectablePVP : CorpseType.ResurrectablePVE);
|
|
SetPvPDeath(false);
|
|
|
|
if (!corpse.Create(GetMap().GenerateLowGuid(HighGuid.Corpse), this))
|
|
return null;
|
|
|
|
_corpseLocation = new WorldLocation(this);
|
|
|
|
byte skin = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetSkinId);
|
|
byte face = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetFaceId);
|
|
byte hairstyle = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairStyleId);
|
|
byte haircolor = GetByteValue(PlayerFields.Bytes, PlayerFieldOffsets.BytesOffsetHairColorId);
|
|
byte facialhair = GetByteValue(PlayerFields.Bytes2, PlayerFieldOffsets.Bytes2OffsetFacialStyle);
|
|
|
|
_cfb1 = (uint)((0x00) | ((int)GetRace() << 8) | (GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender) << 16) | (skin << 24));
|
|
_cfb2 = (uint)((face) | (hairstyle << 8) | (haircolor << 16) | (facialhair << 24));
|
|
|
|
corpse.SetUInt32Value(CorpseFields.Bytes1, _cfb1);
|
|
corpse.SetUInt32Value(CorpseFields.Bytes2, _cfb2);
|
|
|
|
CorpseFlags flags = 0;
|
|
if (HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP))
|
|
flags |= CorpseFlags.PvP;
|
|
if (InBattleground() && !InArena())
|
|
flags |= CorpseFlags.Skinnable; // to be able to remove insignia
|
|
if (HasByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.FFAPvp))
|
|
flags |= CorpseFlags.FFAPvP;
|
|
|
|
corpse.SetUInt32Value(CorpseFields.Flags, (uint)flags);
|
|
corpse.SetUInt32Value(CorpseFields.DisplayId, GetNativeDisplayId());
|
|
corpse.SetUInt32Value(CorpseFields.FactionTemplate, CliDB.ChrRacesStorage.LookupByKey(GetRace()).FactionID);
|
|
|
|
for (byte i = 0; i < EquipmentSlot.End; i++)
|
|
{
|
|
if (m_items[i] != null)
|
|
{
|
|
uint itemDisplayId = m_items[i].GetDisplayId(this);
|
|
uint itemInventoryType;
|
|
ItemRecord itemEntry = CliDB.ItemStorage.LookupByKey(m_items[i].GetVisibleEntry(this));
|
|
if (itemEntry != null)
|
|
itemInventoryType = (uint)itemEntry.inventoryType;
|
|
else
|
|
itemInventoryType = (uint)m_items[i].GetTemplate().GetInventoryType();
|
|
|
|
corpse.SetUInt32Value(CorpseFields.Item + i, itemDisplayId | (itemInventoryType << 24));
|
|
}
|
|
}
|
|
|
|
// register for player, but not show
|
|
GetMap().AddCorpse(corpse);
|
|
|
|
// we do not need to save corpses for BG/arenas
|
|
if (!GetMap().IsBattlegroundOrArena())
|
|
corpse.SaveToDB();
|
|
|
|
return corpse;
|
|
}
|
|
|
|
public void SpawnCorpseBones(bool triggerSave = true)
|
|
{
|
|
_corpseLocation = new WorldLocation();
|
|
if (GetMap().ConvertCorpseToBones(GetGUID()))
|
|
if (triggerSave && !GetSession().PlayerLogoutWithSave()) // at logout we will already store the player
|
|
SaveToDB(); // prevent loading as ghost without corpse
|
|
}
|
|
|
|
public Corpse GetCorpse() { return GetMap().GetCorpseByPlayer(GetGUID()); }
|
|
|
|
public void RepopAtGraveyard()
|
|
{
|
|
// note: this can be called also when the player is alive
|
|
// for example from WorldSession.HandleMovementOpcodes
|
|
|
|
AreaTableRecord zone = CliDB.AreaTableStorage.LookupByKey(GetAreaId());
|
|
|
|
// Such zones are considered unreachable as a ghost and the player must be automatically revived
|
|
if ((!IsAlive() && zone != null && zone.Flags[0].HasAnyFlag(AreaFlags.NeedFly)) || GetTransport() != null || GetPositionZ() < GetMap().GetMinHeight(GetPositionX(), GetPositionY()))
|
|
{
|
|
ResurrectPlayer(0.5f);
|
|
SpawnCorpseBones();
|
|
}
|
|
|
|
WorldSafeLocsRecord ClosestGrave = null;
|
|
|
|
// Special handle for Battlegroundmaps
|
|
Battleground bg = GetBattleground();
|
|
if (bg)
|
|
ClosestGrave = bg.GetClosestGraveYard(this);
|
|
else
|
|
{
|
|
BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(GetZoneId());
|
|
if (bf != null)
|
|
ClosestGrave = bf.GetClosestGraveYard(this);
|
|
else
|
|
ClosestGrave = Global.ObjectMgr.GetClosestGraveYard(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), GetTeam());
|
|
}
|
|
|
|
// stop countdown until repop
|
|
m_deathTimer = 0;
|
|
|
|
// if no grave found, stay at the current location
|
|
// and don't show spirit healer location
|
|
if (ClosestGrave != null)
|
|
{
|
|
TeleportTo(ClosestGrave.MapID, ClosestGrave.Loc.X, ClosestGrave.Loc.Y, ClosestGrave.Loc.Z, (ClosestGrave.Facing * MathFunctions.PI) / 180);
|
|
if (IsDead()) // not send if alive, because it used in TeleportTo()
|
|
{
|
|
DeathReleaseLoc packet = new DeathReleaseLoc();
|
|
packet.MapID = (int)ClosestGrave.MapID;
|
|
packet.Loc = ClosestGrave.Loc;
|
|
SendPacket(packet);
|
|
}
|
|
}
|
|
else if (GetPositionZ() < GetMap().GetMinHeight(GetPositionX(), GetPositionY()))
|
|
TeleportTo(homebind);
|
|
|
|
RemoveFlag(PlayerFields.Flags, PlayerFlags.IsOutOfBounds);
|
|
}
|
|
|
|
public bool HasCorpse()
|
|
{
|
|
return _corpseLocation.GetMapId() != 0xFFFFFFFF;
|
|
}
|
|
public WorldLocation GetCorpseLocation() { return _corpseLocation; }
|
|
|
|
public uint GetCorpseReclaimDelay(bool pvp)
|
|
{
|
|
if (pvp)
|
|
{
|
|
if (!WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPvp))
|
|
return PlayerConst.copseReclaimDelay[0];
|
|
}
|
|
else if (!WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPve))
|
|
return 0;
|
|
|
|
long now = Time.UnixTime;
|
|
// 0..2 full period
|
|
// should be ceil(x)-1 but not floor(x)
|
|
ulong count = (ulong)((now < m_deathExpireTime - 1) ? (m_deathExpireTime - 1 - now) / PlayerConst.DeathExpireStep : 0);
|
|
return PlayerConst.copseReclaimDelay[count];
|
|
}
|
|
void UpdateCorpseReclaimDelay()
|
|
{
|
|
bool pvp = m_ExtraFlags.HasAnyFlag(PlayerExtraFlags.PVPDeath);
|
|
|
|
if ((pvp && !WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPvp)) ||
|
|
(!pvp && !WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPve)))
|
|
return;
|
|
long now = Time.UnixTime;
|
|
if (now < m_deathExpireTime)
|
|
{
|
|
// full and partly periods 1..3
|
|
ulong count = (ulong)(m_deathExpireTime - now) / PlayerConst.DeathExpireStep + 1;
|
|
if (count < PlayerConst.MaxDeathCount)
|
|
m_deathExpireTime = now + (long)(count + 1) * PlayerConst.DeathExpireStep;
|
|
else
|
|
m_deathExpireTime = now + PlayerConst.MaxDeathCount * PlayerConst.DeathExpireStep;
|
|
}
|
|
else
|
|
m_deathExpireTime = now + PlayerConst.DeathExpireStep;
|
|
}
|
|
int CalculateCorpseReclaimDelay(bool load = false)
|
|
{
|
|
Corpse corpse = GetCorpse();
|
|
if (load && !corpse)
|
|
return -1;
|
|
|
|
bool pvp = corpse ? corpse.GetCorpseType() == CorpseType.ResurrectablePVP : (m_ExtraFlags & PlayerExtraFlags.PVPDeath) != 0;
|
|
|
|
uint delay;
|
|
if (load)
|
|
{
|
|
if (corpse.GetGhostTime() > m_deathExpireTime)
|
|
return -1;
|
|
|
|
ulong count = 0;
|
|
if ((pvp && WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPvp)) ||
|
|
(!pvp && WorldConfig.GetBoolValue(WorldCfg.DeathCorpseReclaimDelayPve)))
|
|
{
|
|
count = (ulong)(m_deathExpireTime - corpse.GetGhostTime()) / PlayerConst.DeathExpireStep; ;
|
|
|
|
if (count >= PlayerConst.MaxDeathCount)
|
|
count = PlayerConst.MaxDeathCount - 1;
|
|
}
|
|
|
|
long expected_time = corpse.GetGhostTime() + PlayerConst.copseReclaimDelay[count];
|
|
long now = Time.UnixTime;
|
|
|
|
if (now >= expected_time)
|
|
return -1;
|
|
|
|
delay = (uint)(expected_time - now);
|
|
}
|
|
else
|
|
delay = GetCorpseReclaimDelay(pvp);
|
|
|
|
return (int)(delay * Time.InMilliseconds);
|
|
}
|
|
void SendCorpseReclaimDelay(int delay)
|
|
{
|
|
CorpseReclaimDelay packet = new CorpseReclaimDelay();
|
|
packet.Remaining = (uint)delay;
|
|
SendPacket(packet);
|
|
}
|
|
|
|
public override bool CanFly() { return m_movementInfo.HasMovementFlag(MovementFlag.CanFly); }
|
|
|
|
public Pet GetPet()
|
|
{
|
|
ObjectGuid petGuid = GetPetGUID();
|
|
if (!petGuid.IsEmpty())
|
|
{
|
|
if (!petGuid.IsPet())
|
|
return null;
|
|
|
|
Pet pet = ObjectAccessor.GetPet(this, petGuid);
|
|
if (pet == null)
|
|
return null;
|
|
|
|
if (IsInWorld && pet != null)
|
|
return pet;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public Pet SummonPet(uint entry, float x, float y, float z, float ang, PetType petType, uint duration)
|
|
{
|
|
Pet pet = new Pet(this, petType);
|
|
if (petType == PetType.Summon && pet.LoadPetFromDB(this, entry))
|
|
{
|
|
if (duration > 0)
|
|
pet.SetDuration(duration);
|
|
|
|
return null;
|
|
}
|
|
|
|
// petentry == 0 for hunter "call pet" (current pet summoned if any)
|
|
if (entry == 0)
|
|
return null;
|
|
|
|
pet.Relocate(x, y, z, ang);
|
|
if (!pet.IsPositionValid())
|
|
{
|
|
Log.outError(LogFilter.Server, "Pet (guidlow {0}, entry {1}) not summoned. Suggested coordinates isn't valid (X: {2} Y: {3})",
|
|
pet.GetGUID().ToString(), pet.GetEntry(), pet.GetPositionX(), pet.GetPositionY());
|
|
return null;
|
|
}
|
|
|
|
Map map = GetMap();
|
|
uint pet_number = Global.ObjectMgr.GeneratePetNumber();
|
|
if (!pet.Create(map.GenerateLowGuid(HighGuid.Pet), map, entry))
|
|
{
|
|
Log.outError(LogFilter.Server, "no such creature entry {0}", entry);
|
|
return null;
|
|
}
|
|
|
|
pet.CopyPhaseFrom(this);
|
|
|
|
pet.SetCreatorGUID(GetGUID());
|
|
pet.SetUInt32Value(UnitFields.FactionTemplate, getFaction());
|
|
|
|
pet.SetPowerType(PowerType.Mana);
|
|
pet.SetUInt64Value(UnitFields.NpcFlags, (uint)NPCFlags.None);
|
|
pet.SetUInt32Value(UnitFields.Bytes1, 0);
|
|
pet.InitStatsForLevel(getLevel());
|
|
|
|
SetMinion(pet, true);
|
|
|
|
switch (petType)
|
|
{
|
|
case PetType.Summon:
|
|
// this enables pet details window (Shift+P)
|
|
pet.GetCharmInfo().SetPetNumber(pet_number, true);
|
|
pet.SetByteValue(UnitFields.Bytes0, 1, (byte)Class.Mage);
|
|
pet.SetUInt32Value(UnitFields.PetExperience, 0);
|
|
pet.SetUInt32Value(UnitFields.PetNextLevelExp, 1000);
|
|
pet.SetFullHealth();
|
|
pet.SetFullPower(PowerType.Mana);
|
|
pet.SetUInt32Value(UnitFields.PetNameTimestamp, (uint)Time.UnixTime); // cast can't be helped in this case
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
map.AddToMap(pet.ToCreature());
|
|
|
|
switch (petType)
|
|
{
|
|
case PetType.Summon:
|
|
pet.InitPetCreateSpells();
|
|
pet.SavePetToDB(PetSaveMode.AsCurrent);
|
|
PetSpellInitialize();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (duration > 0)
|
|
pet.SetDuration(duration);
|
|
|
|
//ObjectAccessor.UpdateObjectVisibility(pet);
|
|
|
|
return pet;
|
|
}
|
|
|
|
public void RemovePet(Pet pet, PetSaveMode mode, bool returnreagent = false)
|
|
{
|
|
if (!pet)
|
|
pet = GetPet();
|
|
|
|
if (pet)
|
|
{
|
|
Log.outDebug(LogFilter.Pet, "RemovePet {0}, {1}, {2}", pet.GetEntry(), mode, returnreagent);
|
|
|
|
if (pet.m_removed)
|
|
return;
|
|
}
|
|
|
|
if (returnreagent && (pet || m_temporaryUnsummonedPetNumber != 0) && !InBattleground())
|
|
{
|
|
//returning of reagents only for players, so best done here
|
|
uint spellId = pet ? pet.GetUInt32Value(UnitFields.CreatedBySpell) : m_oldpetspell;
|
|
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
|
|
|
|
if (spellInfo != null)
|
|
{
|
|
for (uint i = 0; i < SpellConst.MaxReagents; ++i)
|
|
{
|
|
if (spellInfo.Reagent[i] > 0)
|
|
{
|
|
List<ItemPosCount> dest = new List<ItemPosCount>(); //for succubus, voidwalker, felhunter and felguard credit soulshard when despawn reason other than death (out of range, logout)
|
|
InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, (uint)spellInfo.Reagent[i], spellInfo.ReagentCount[i]);
|
|
if (msg == InventoryResult.Ok)
|
|
{
|
|
Item item = StoreNewItem(dest, (uint)spellInfo.Reagent[i], true);
|
|
if (IsInWorld)
|
|
SendNewItem(item, spellInfo.ReagentCount[i], true, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_temporaryUnsummonedPetNumber = 0;
|
|
}
|
|
|
|
if (!pet || pet.GetOwnerGUID() != GetGUID())
|
|
return;
|
|
|
|
pet.CombatStop();
|
|
|
|
if (returnreagent)
|
|
{
|
|
switch (pet.GetEntry())
|
|
{
|
|
//warlock pets except imp are removed(?) when logging out
|
|
case 1860:
|
|
case 1863:
|
|
case 417:
|
|
case 17252:
|
|
mode = PetSaveMode.NotInSlot;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// only if current pet in slot
|
|
pet.SavePetToDB(mode);
|
|
|
|
SetMinion(pet, false);
|
|
|
|
pet.AddObjectToRemoveList();
|
|
pet.m_removed = true;
|
|
|
|
if (pet.isControlled())
|
|
{
|
|
SendPacket(new PetSpells());
|
|
|
|
if (GetGroup())
|
|
SetGroupUpdateFlag(GroupUpdateFlags.Pet);
|
|
}
|
|
}
|
|
|
|
public bool InArena()
|
|
{
|
|
Battleground bg = GetBattleground();
|
|
if (!bg || !bg.isArena())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void SendOnCancelExpectedVehicleRideAura()
|
|
{
|
|
SendPacket(new OnCancelExpectedRideVehicleAura());
|
|
}
|
|
|
|
public void SendMovementSetCollisionHeight(float height)
|
|
{
|
|
MoveSetCollisionHeight setCollisionHeight = new MoveSetCollisionHeight();
|
|
setCollisionHeight.MoverGUID = GetGUID();
|
|
setCollisionHeight.SequenceIndex = m_movementCounter++;
|
|
setCollisionHeight.Height = height;
|
|
setCollisionHeight.Scale = GetObjectScale();
|
|
setCollisionHeight.MountDisplayID = GetUInt32Value(UnitFields.MountDisplayId);
|
|
setCollisionHeight.ScaleDuration = (int)GetUInt32Value(UnitFields.ScaleDuration);
|
|
setCollisionHeight.Reason = UpdateCollisionHeightReason.Mount;
|
|
SendPacket(setCollisionHeight);
|
|
|
|
MoveUpdateCollisionHeight updateCollisionHeight = new MoveUpdateCollisionHeight();
|
|
updateCollisionHeight.Status = m_movementInfo;
|
|
updateCollisionHeight.Height = height;
|
|
updateCollisionHeight.Scale = GetObjectScale();
|
|
SendMessageToSet(updateCollisionHeight, false);
|
|
}
|
|
|
|
public void SendPlayerChoice(ObjectGuid sender, int choiceId)
|
|
{
|
|
PlayerChoice playerChoice = Global.ObjectMgr.GetPlayerChoice(choiceId);
|
|
if (playerChoice == null)
|
|
return;
|
|
|
|
LocaleConstant locale = GetSession().GetSessionDbLocaleIndex();
|
|
PlayerChoiceLocale playerChoiceLocale = locale != LocaleConstant.enUS ? Global.ObjectMgr.GetPlayerChoiceLocale(choiceId) : null;
|
|
|
|
PlayerTalkClass.GetInteractionData().Reset();
|
|
PlayerTalkClass.GetInteractionData().SourceGuid = sender;
|
|
PlayerTalkClass.GetInteractionData().PlayerChoiceId = (uint)choiceId;
|
|
|
|
DisplayPlayerChoice displayPlayerChoice = new DisplayPlayerChoice();
|
|
displayPlayerChoice.SenderGUID = sender;
|
|
displayPlayerChoice.ChoiceID = choiceId;
|
|
displayPlayerChoice.Question = playerChoice.Question;
|
|
if (playerChoiceLocale != null)
|
|
ObjectManager.GetLocaleString(playerChoiceLocale.Question, locale, ref displayPlayerChoice.Question);
|
|
|
|
displayPlayerChoice.CloseChoiceFrame = false;
|
|
|
|
for (var i = 0; i < playerChoice.Responses.Count; ++i)
|
|
{
|
|
PlayerChoiceResponse playerChoiceResponseTemplate = playerChoice.Responses[i];
|
|
var playerChoiceResponse = new Network.Packets.PlayerChoiceResponse();
|
|
|
|
playerChoiceResponse.ResponseID = playerChoiceResponseTemplate.ResponseId;
|
|
playerChoiceResponse.ChoiceArtFileID = playerChoiceResponseTemplate.ChoiceArtFileId;
|
|
playerChoiceResponse.Answer = playerChoiceResponseTemplate.Answer;
|
|
playerChoiceResponse.Header = playerChoiceResponseTemplate.Header;
|
|
playerChoiceResponse.Description = playerChoiceResponseTemplate.Description;
|
|
playerChoiceResponse.Confirmation = playerChoiceResponseTemplate.Confirmation;
|
|
if (playerChoiceLocale != null)
|
|
{
|
|
PlayerChoiceResponseLocale playerChoiceResponseLocale = playerChoiceLocale.Responses.LookupByKey(playerChoiceResponseTemplate.ResponseId);
|
|
if (playerChoiceResponseLocale != null)
|
|
{
|
|
ObjectManager.GetLocaleString(playerChoiceResponseLocale.Answer, locale, ref playerChoiceResponse.Answer);
|
|
ObjectManager.GetLocaleString(playerChoiceResponseLocale.Header, locale, ref playerChoiceResponse.Header);
|
|
ObjectManager.GetLocaleString(playerChoiceResponseLocale.Description, locale, ref playerChoiceResponse.Description);
|
|
ObjectManager.GetLocaleString(playerChoiceResponseLocale.Confirmation, locale, ref playerChoiceResponse.Confirmation);
|
|
}
|
|
}
|
|
|
|
if (playerChoiceResponseTemplate.Reward.HasValue)
|
|
{
|
|
var reward = new Network.Packets.PlayerChoiceResponseReward();
|
|
reward.TitleID = playerChoiceResponseTemplate.Reward.Value.TitleId;
|
|
reward.PackageID = playerChoiceResponseTemplate.Reward.Value.PackageId;
|
|
reward.SkillLineID = playerChoiceResponseTemplate.Reward.Value.SkillLineId;
|
|
reward.SkillPointCount = playerChoiceResponseTemplate.Reward.Value.SkillPointCount;
|
|
reward.ArenaPointCount = playerChoiceResponseTemplate.Reward.Value.ArenaPointCount;
|
|
reward.HonorPointCount = playerChoiceResponseTemplate.Reward.Value.HonorPointCount;
|
|
reward.Money = playerChoiceResponseTemplate.Reward.Value.Money;
|
|
reward.Xp = playerChoiceResponseTemplate.Reward.Value.Xp;
|
|
|
|
foreach (var item in playerChoiceResponseTemplate.Reward.Value.Items)
|
|
{
|
|
var rewardEntry = new Network.Packets.PlayerChoiceResponseRewardEntry();
|
|
rewardEntry.Item.ItemID = item.Id;
|
|
rewardEntry.Quantity = item.Quantity;
|
|
if (!item.BonusListIDs.Empty())
|
|
{
|
|
rewardEntry.Item.ItemBonus.HasValue = true;
|
|
rewardEntry.Item.ItemBonus.Value.BonusListIDs = item.BonusListIDs;
|
|
}
|
|
reward.Items.Add(rewardEntry);
|
|
}
|
|
|
|
foreach (var currency in playerChoiceResponseTemplate.Reward.Value.Currency)
|
|
{
|
|
var rewardEntry = new Network.Packets.PlayerChoiceResponseRewardEntry();
|
|
rewardEntry.Item.ItemID = currency.Id;
|
|
rewardEntry.Quantity = currency.Quantity;
|
|
reward.Items.Add(rewardEntry);
|
|
}
|
|
|
|
foreach (var faction in playerChoiceResponseTemplate.Reward.Value.Faction)
|
|
{
|
|
var rewardEntry = new Network.Packets.PlayerChoiceResponseRewardEntry();
|
|
rewardEntry.Item.ItemID = faction.Id;
|
|
rewardEntry.Quantity = faction.Quantity;
|
|
reward.Items.Add(rewardEntry);
|
|
}
|
|
|
|
playerChoiceResponse.Reward.Set(reward);
|
|
displayPlayerChoice.Responses[i] = playerChoiceResponse;
|
|
}
|
|
}
|
|
|
|
SendPacket(displayPlayerChoice);
|
|
}
|
|
|
|
public float GetCollisionHeight(bool mounted)
|
|
{
|
|
if (mounted)
|
|
{
|
|
var mountDisplayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetUInt32Value(UnitFields.MountDisplayId));
|
|
if (mountDisplayInfo == null)
|
|
return GetCollisionHeight(false);
|
|
|
|
var mountModelData = CliDB.CreatureModelDataStorage.LookupByKey(mountDisplayInfo.ModelID);
|
|
if (mountModelData == null)
|
|
return GetCollisionHeight(false);
|
|
|
|
var displayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId());
|
|
Contract.Assert(displayInfo != null);
|
|
var modelData = CliDB.CreatureModelDataStorage.LookupByKey(displayInfo.ModelID);
|
|
Contract.Assert(modelData != null);
|
|
|
|
float scaleMod = GetObjectScale(); // 99% sure about this
|
|
|
|
return scaleMod * mountModelData.MountHeight + modelData.CollisionHeight * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
//! Dismounting case - use basic default model data
|
|
var displayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId());
|
|
Contract.Assert(displayInfo != null);
|
|
var modelData = CliDB.CreatureModelDataStorage.LookupByKey(displayInfo.ModelID);
|
|
Contract.Assert(modelData != null);
|
|
|
|
return modelData.CollisionHeight;
|
|
}
|
|
}
|
|
|
|
// Used in triggers for check "Only to targets that grant experience or honor" req
|
|
public bool isHonorOrXPTarget(Unit victim)
|
|
{
|
|
uint v_level = victim.GetLevelForTarget(this);
|
|
uint k_grey = Formulas.GetGrayLevel(getLevel());
|
|
|
|
// Victim level less gray level
|
|
if (v_level < k_grey)
|
|
return false;
|
|
|
|
Creature creature = victim.ToCreature();
|
|
if (creature != null)
|
|
{
|
|
if (!creature.CanGiveExperience())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void setRegenTimerCount(uint time) { m_regenTimerCount = time; }
|
|
void setWeaponChangeTimer(uint time) { m_weaponChangeTimer = time; }
|
|
|
|
//Team
|
|
public static Team TeamForRace(Race race)
|
|
{
|
|
switch (TeamIdForRace(race))
|
|
{
|
|
case 0:
|
|
return Team.Alliance;
|
|
case 1:
|
|
return Team.Horde;
|
|
}
|
|
|
|
return Team.Alliance;
|
|
}
|
|
public static uint TeamIdForRace(Race race)
|
|
{
|
|
ChrRacesRecord rEntry = CliDB.ChrRacesStorage.LookupByKey((byte)race);
|
|
if (rEntry != null)
|
|
return rEntry.TeamID;
|
|
|
|
Log.outError(LogFilter.Player, "Race ({0}) not found in DBC: wrong DBC files?", race);
|
|
return TeamId.Neutral;
|
|
}
|
|
public Team GetTeam() { return m_team; }
|
|
public int GetTeamId() { return m_team == Team.Alliance ? TeamId.Alliance : TeamId.Horde; }
|
|
|
|
//Money
|
|
public ulong GetMoney() { return GetUInt64Value(PlayerFields.Coinage); }
|
|
public bool HasEnoughMoney(ulong amount) { return GetMoney() >= amount; }
|
|
public bool HasEnoughMoney(long amount)
|
|
{
|
|
if (amount > 0)
|
|
return (GetMoney() >= (ulong)amount);
|
|
return true;
|
|
}
|
|
public bool ModifyMoney(long amount, bool sendError = true)
|
|
{
|
|
if (amount == 0)
|
|
return true;
|
|
|
|
Global.ScriptMgr.OnPlayerMoneyChanged(this, amount);
|
|
|
|
if (amount < 0)
|
|
SetMoney((ulong)(GetMoney() > (ulong)-amount ? (long)GetMoney() + amount : 0));
|
|
else
|
|
{
|
|
if (GetMoney() <= (PlayerConst.MaxMoneyAmount - (ulong)amount))
|
|
SetMoney((ulong)(GetMoney() + (ulong)amount));
|
|
else
|
|
{
|
|
if (sendError)
|
|
SendEquipError(InventoryResult.TooMuchGold);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
public void SetMoney(ulong value)
|
|
{
|
|
SetUInt64Value(PlayerFields.Coinage, value);
|
|
MoneyChanged((uint)value);
|
|
UpdateCriteria(CriteriaTypes.HighestGoldValueOwned);
|
|
}
|
|
|
|
//Target
|
|
public void SetSelection(ObjectGuid guid)
|
|
{
|
|
SetGuidValue(UnitFields.Target, guid);
|
|
}
|
|
|
|
//LoginFlag
|
|
public bool HasAtLoginFlag(AtLoginFlags f) { return Convert.ToBoolean(atLoginFlags & f); }
|
|
public void SetAtLoginFlag(AtLoginFlags f) { atLoginFlags |= f; }
|
|
public void RemoveAtLoginFlag(AtLoginFlags flags, bool persist = false)
|
|
{
|
|
atLoginFlags &= ~flags;
|
|
if (persist)
|
|
{
|
|
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_REM_AT_LOGIN_FLAG);
|
|
stmt.AddValue(0, (ushort)flags);
|
|
stmt.AddValue(1, GetGUID().GetCounter());
|
|
|
|
DB.Characters.Execute(stmt);
|
|
}
|
|
}
|
|
void SetFactionForRace(Race race)
|
|
{
|
|
m_team = TeamForRace(race);
|
|
|
|
var rEntry = CliDB.ChrRacesStorage.LookupByKey(race);
|
|
SetFaction(rEntry.FactionID);
|
|
}
|
|
|
|
//Guild
|
|
public void SetInGuild(ulong guildId)
|
|
{
|
|
if (guildId != 0)
|
|
SetGuidValue(ObjectFields.Data, ObjectGuid.Create(HighGuid.Guild, guildId));
|
|
else
|
|
SetGuidValue(ObjectFields.Data, ObjectGuid.Empty);
|
|
|
|
ApplyModFlag(PlayerFields.Flags, PlayerFlags.GuildLevelEnabled, guildId != 0);
|
|
SetUInt16Value(ObjectFields.Type, 1, (ushort)(guildId != 0 ? 1 : 0));
|
|
}
|
|
public void SetRank(uint rankId) { SetUInt32Value(PlayerFields.GuildRank, rankId); }
|
|
byte GetRank() { return (byte)GetUInt32Value(PlayerFields.GuildRank); }
|
|
public void SetGuildLevel(uint level) { SetUInt32Value(PlayerFields.GuildLevel, level); }
|
|
uint GetGuildLevel() { return GetUInt32Value(PlayerFields.GuildLevel); }
|
|
public void SetGuildIdInvited(ulong GuildId) { m_GuildIdInvited = GuildId; }
|
|
public uint GetGuildId() { return GetUInt32Value(ObjectFields.Data); }
|
|
public Guild GetGuild()
|
|
{
|
|
uint guildId = GetGuildId();
|
|
return guildId != 0 ? Global.GuildMgr.GetGuildById(guildId) : null;
|
|
}
|
|
public ulong GetGuildIdInvited() { return m_GuildIdInvited; }
|
|
public string GetGuildName()
|
|
{
|
|
return GetGuildId() != 0 ? Global.GuildMgr.GetGuildById(GetGuildId()).GetName() : "";
|
|
}
|
|
|
|
public void SetFreePrimaryProfessions(uint profs) { SetUInt32Value(PlayerFields.CharacterPoints, profs); }
|
|
public void GiveLevel(uint level)
|
|
{
|
|
var oldLevel = getLevel();
|
|
if (level == oldLevel)
|
|
return;
|
|
|
|
Guild guild = GetGuild();
|
|
if (guild != null)
|
|
guild.UpdateMemberData(this, GuildMemberData.Level, level);
|
|
|
|
PlayerLevelInfo info = Global.ObjectMgr.GetPlayerLevelInfo(GetRace(), GetClass(), level);
|
|
|
|
uint basemana = 0;
|
|
Global.ObjectMgr.GetPlayerClassLevelInfo(GetClass(), level, out basemana);
|
|
|
|
LevelUpInfo packet = new LevelUpInfo();
|
|
packet.Level = level;
|
|
packet.HealthDelta = 0;
|
|
|
|
/// @todo find some better solution
|
|
packet.PowerDelta[0] = basemana - GetCreateMana();
|
|
packet.PowerDelta[1] = 0;
|
|
packet.PowerDelta[2] = 0;
|
|
packet.PowerDelta[3] = 0;
|
|
packet.PowerDelta[4] = 0;
|
|
packet.PowerDelta[5] = 0;
|
|
|
|
for (Stats i = Stats.Strength; i < Stats.Max; ++i)
|
|
packet.StatDelta[(int)i] = info.stats[(int)i] - (uint)GetCreateStat(i);
|
|
|
|
uint[] rowLevels = (GetClass() != Class.Deathknight) ? PlayerConst.DefaultTalentRowLevels : PlayerConst.DKTalentRowLevels;
|
|
|
|
packet.Cp = rowLevels.Any(p => p == level) ? 1 : 0;
|
|
|
|
SendPacket(packet);
|
|
|
|
SetUInt32Value(PlayerFields.NextLevelXp, Global.ObjectMgr.GetXPForLevel(level));
|
|
|
|
//update level, max level of skills
|
|
m_PlayedTimeLevel = 0; // Level Played Time reset
|
|
|
|
_ApplyAllLevelScaleItemMods(false);
|
|
|
|
SetLevel(level);
|
|
|
|
UpdateSkillsForLevel();
|
|
LearnDefaultSkills();
|
|
LearnSpecializationSpells();
|
|
|
|
// save base values (bonuses already included in stored stats
|
|
for (var i = Stats.Strength; i < Stats.Max; ++i)
|
|
SetCreateStat(i, info.stats[(int)i]);
|
|
|
|
SetCreateHealth(0);
|
|
SetCreateMana(basemana);
|
|
|
|
InitTalentForLevel();
|
|
InitTaxiNodesForLevel();
|
|
|
|
UpdateAllStats();
|
|
|
|
if (WorldConfig.GetBoolValue(WorldCfg.AlwaysMaxskill)) // Max weapon skill when leveling up
|
|
UpdateSkillsToMaxSkillsForLevel();
|
|
|
|
_ApplyAllLevelScaleItemMods(true); // Moved to above SetFullHealth so player will have full health from Heirlooms
|
|
|
|
// set current level health and mana/energy to maximum after applying all mods.
|
|
SetFullHealth();
|
|
SetFullPower(PowerType.Mana);
|
|
SetFullPower(PowerType.Energy);
|
|
if (GetPower(PowerType.Rage) > GetMaxPower(PowerType.Rage))
|
|
SetFullPower(PowerType.Rage);
|
|
SetPower(PowerType.Focus, 0);
|
|
|
|
// update level to hunter/summon pet
|
|
Pet pet = GetPet();
|
|
if (pet)
|
|
pet.SynchronizeLevelWithOwner();
|
|
|
|
MailLevelReward mailReward = Global.ObjectMgr.GetMailLevelReward(level, getRaceMask());
|
|
if (mailReward != null)
|
|
{
|
|
//- TODO: Poor design of mail system
|
|
SQLTransaction trans = new SQLTransaction();
|
|
new MailDraft(mailReward.mailTemplateId).SendMailTo(trans, this, new MailSender(MailMessageType.Creature, mailReward.senderEntry));
|
|
DB.Characters.CommitTransaction(trans);
|
|
}
|
|
|
|
UpdateCriteria(CriteriaTypes.ReachLevel);
|
|
|
|
// Refer-A-Friend
|
|
if (GetSession().GetRecruiterId() != 0)
|
|
{
|
|
if (level < WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel))
|
|
{
|
|
if (level % 2 == 0)
|
|
{
|
|
++m_grantableLevels;
|
|
|
|
if (!HasByteFlag(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetRafGrantableLevel, 0x01))
|
|
SetByteFlag(PlayerFields.FieldBytes, PlayerFieldOffsets.FieldBytesOffsetRafGrantableLevel, 0x01);
|
|
}
|
|
}
|
|
}
|
|
|
|
Global.ScriptMgr.OnPlayerLevelChanged(this, (byte)oldLevel);
|
|
}
|
|
|
|
public bool CanParry()
|
|
{
|
|
return m_canParry;
|
|
}
|
|
public bool CanBlock()
|
|
{
|
|
return m_canBlock;
|
|
}
|
|
|
|
public void ToggleAFK()
|
|
{
|
|
ToggleFlag(PlayerFields.Flags, PlayerFlags.AFK);
|
|
|
|
// afk player not allowed in Battleground
|
|
if (!IsGameMaster() && isAFK() && InBattleground() && !InArena())
|
|
LeaveBattleground();
|
|
}
|
|
public void ToggleDND()
|
|
{
|
|
ToggleFlag(PlayerFields.Flags, PlayerFlags.DND);
|
|
}
|
|
public bool isAFK() { return HasFlag(PlayerFields.Flags, PlayerFlags.AFK); }
|
|
public bool isDND() { return HasFlag(PlayerFields.Flags, PlayerFlags.DND); }
|
|
public ChatFlags GetChatFlags()
|
|
{
|
|
ChatFlags tag = ChatFlags.None;
|
|
|
|
if (isGMChat())
|
|
tag |= ChatFlags.GM;
|
|
if (isDND())
|
|
tag |= ChatFlags.DND;
|
|
if (isAFK())
|
|
tag |= ChatFlags.AFK;
|
|
if (HasFlag(PlayerFields.Flags, PlayerFlags.Developer))
|
|
tag |= ChatFlags.Dev;
|
|
|
|
return tag;
|
|
}
|
|
|
|
public void InitDisplayIds()
|
|
{
|
|
PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass());
|
|
if (info == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player {0} has incorrect race/class pair. Can't init display ids.", GetGUID().ToString());
|
|
return;
|
|
}
|
|
|
|
byte gender = GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetGender);
|
|
switch ((Gender)gender)
|
|
{
|
|
case Gender.Female:
|
|
SetDisplayId(info.DisplayId_f);
|
|
SetNativeDisplayId(info.DisplayId_f);
|
|
break;
|
|
case Gender.Male:
|
|
SetDisplayId(info.DisplayId_m);
|
|
SetNativeDisplayId(info.DisplayId_m);
|
|
break;
|
|
default:
|
|
Log.outError(LogFilter.Player, "Player {0} ({1}) has invalid gender {2}", GetName(), GetGUID().ToString(), gender);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Creature
|
|
public Creature GetNPCIfCanInteractWith(ObjectGuid guid, NPCFlags npcflagmask)
|
|
{
|
|
// unit checks
|
|
if (guid.IsEmpty())
|
|
return null;
|
|
|
|
if (!IsInWorld)
|
|
return null;
|
|
|
|
if (IsInFlight())
|
|
return null;
|
|
|
|
// exist (we need look pets also for some interaction (quest/etc)
|
|
Creature creature = ObjectAccessor.GetCreatureOrPetOrVehicle(this, guid);
|
|
if (creature == null)
|
|
return null;
|
|
|
|
// Deathstate checks
|
|
if (!IsAlive() && !Convert.ToBoolean(creature.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.GhostVisible))
|
|
return null;
|
|
|
|
// alive or spirit healer
|
|
if (!creature.IsAlive() && !Convert.ToBoolean(creature.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.CanInteractWhileDead))
|
|
return null;
|
|
|
|
// appropriate npc type
|
|
if (npcflagmask != 0 && !creature.HasFlag64(UnitFields.NpcFlags, npcflagmask))
|
|
return null;
|
|
|
|
// not allow interaction under control, but allow with own pets
|
|
if (!creature.GetCharmerGUID().IsEmpty())
|
|
return null;
|
|
|
|
// not unfriendly/hostile
|
|
if (creature.GetReactionTo(this) <= ReputationRank.Unfriendly)
|
|
return null;
|
|
|
|
// not too far
|
|
if (!creature.IsWithinDistInMap(this, SharedConst.InteractionDistance))
|
|
return null;
|
|
|
|
return creature;
|
|
}
|
|
|
|
public GameObject GetGameObjectIfCanInteractWith(ObjectGuid guid)
|
|
{
|
|
GameObject go = GetMap().GetGameObject(guid);
|
|
if (go)
|
|
{
|
|
if (go.IsWithinDistInMap(this, go.GetInteractionDistance()))
|
|
return go;
|
|
|
|
Log.outDebug(LogFilter.Maps, "Player.GetGameObjectIfCanInteractWith: GameObject '{0}' ({1}) is too far away from player '{2}' ({3}) to be used by him (Distance: {4}, maximal {5} is allowed)",
|
|
go.GetName(), go.GetGUID().ToString(), GetName(), GetGUID().ToString(), go.GetDistance(this), go.GetInteractionDistance());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
public GameObject GetGameObjectIfCanInteractWith(ObjectGuid guid, GameObjectTypes type)
|
|
{
|
|
GameObject go = GetMap().GetGameObject(guid);
|
|
if (go != null)
|
|
{
|
|
if (go.GetGoType() == type)
|
|
{
|
|
if (go.IsWithinDistInMap(this, go.GetInteractionDistance()))
|
|
return go;
|
|
|
|
Log.outDebug(LogFilter.Maps, "Player.GetGameObjectIfCanInteractWith: GameObject '{0}' ({1}) is too far away from player '{2}' ({3}) to be used by him (Distance: {4}, maximal {5} is allowed)",
|
|
go.GetName(), go.GetGUID().ToString(), GetName(), GetGUID().ToString(), go.GetDistance(this), go.GetInteractionDistance());
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void SendInitialPacketsBeforeAddToMap()
|
|
{
|
|
if (!m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless))
|
|
{
|
|
m_movementCounter = 0;
|
|
ResetTimeSync();
|
|
}
|
|
|
|
SendTimeSync();
|
|
|
|
GetSocial().SendSocialList(this, SocialFlag.All);
|
|
|
|
// SMSG_BINDPOINTUPDATE
|
|
SendBindPointUpdate();
|
|
|
|
// SMSG_SET_PROFICIENCY
|
|
// SMSG_SET_PCT_SPELL_MODIFIER
|
|
// SMSG_SET_FLAT_SPELL_MODIFIER
|
|
|
|
// SMSG_TALENTS_INFO
|
|
SendTalentsInfoData();
|
|
|
|
// SMSG_INITIAL_SPELLS
|
|
SendKnownSpells();
|
|
|
|
// SMSG_SEND_UNLEARN_SPELLS
|
|
SendPacket(new SendUnlearnSpells());
|
|
|
|
// SMSG_SEND_SPELL_HISTORY
|
|
SendSpellHistory sendSpellHistory = new SendSpellHistory();
|
|
GetSpellHistory().WritePacket(sendSpellHistory);
|
|
SendPacket(sendSpellHistory);
|
|
|
|
// SMSG_SEND_SPELL_CHARGES
|
|
SendSpellCharges sendSpellCharges = new SendSpellCharges();
|
|
GetSpellHistory().WritePacket(sendSpellCharges);
|
|
SendPacket(sendSpellCharges);
|
|
|
|
ActiveGlyphs activeGlyphs = new ActiveGlyphs();
|
|
foreach (uint glyphId in GetGlyphs(GetActiveTalentGroup()))
|
|
{
|
|
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);
|
|
|
|
// SMSG_ACTION_BUTTONS
|
|
SendInitialActionButtons();
|
|
|
|
// SMSG_INITIALIZE_FACTIONS
|
|
reputationMgr.SendInitialReputations();
|
|
|
|
// SMSG_SETUP_CURRENCY
|
|
SendCurrencies();
|
|
|
|
// SMSG_EQUIPMENT_SET_LIST
|
|
SendEquipmentSetList();
|
|
|
|
m_achievementSys.SendAllData(this);
|
|
m_questObjectiveCriteriaMgr.SendAllData(this);
|
|
|
|
// SMSG_LOGIN_SETTIMESPEED
|
|
float TimeSpeed = 0.01666667f;
|
|
LoginSetTimeSpeed loginSetTimeSpeed = new LoginSetTimeSpeed();
|
|
loginSetTimeSpeed.NewSpeed = TimeSpeed;
|
|
loginSetTimeSpeed.GameTime = (uint)Global.WorldMgr.GetGameTime();
|
|
loginSetTimeSpeed.ServerTime = (uint)Global.WorldMgr.GetGameTime();
|
|
loginSetTimeSpeed.GameTimeHolidayOffset = 0; /// @todo
|
|
loginSetTimeSpeed.ServerTimeHolidayOffset = 0; /// @todo
|
|
SendPacket(loginSetTimeSpeed);
|
|
|
|
// SMSG_WORLD_SERVER_INFO
|
|
WorldServerInfo worldServerInfo = new WorldServerInfo();
|
|
worldServerInfo.InstanceGroupSize.Set(GetMap().GetMapDifficulty().MaxPlayers); /// @todo
|
|
worldServerInfo.IsTournamentRealm = 0; /// @todo
|
|
worldServerInfo.RestrictedAccountMaxLevel.Clear(); /// @todo
|
|
worldServerInfo.RestrictedAccountMaxMoney.Clear(); /// @todo
|
|
worldServerInfo.DifficultyID = (uint)GetMap().GetDifficultyID();
|
|
// worldServerInfo.XRealmPvpAlert; /// @todo
|
|
SendPacket(worldServerInfo);
|
|
|
|
// Spell modifiers
|
|
SendSpellModifiers();
|
|
|
|
// SMSG_ACCOUNT_MOUNT_UPDATE
|
|
AccountMountUpdate mountUpdate = new AccountMountUpdate();
|
|
mountUpdate.IsFullUpdate = true;
|
|
mountUpdate.Mounts = GetSession().GetCollectionMgr().GetAccountMounts();
|
|
SendPacket(mountUpdate);
|
|
|
|
// SMSG_ACCOUNT_TOYS_UPDATE
|
|
AccountToysUpdate toysUpdate = new AccountToysUpdate();
|
|
toysUpdate.IsFullUpdate = true;
|
|
toysUpdate.Toys = GetSession().GetCollectionMgr().GetAccountToys();
|
|
SendPacket(toysUpdate);
|
|
|
|
// SMSG_ACCOUNT_HEIRLOOM_UPDATE
|
|
AccountHeirloomUpdate heirloomUpdate = new AccountHeirloomUpdate();
|
|
heirloomUpdate.IsFullUpdate = true;
|
|
heirloomUpdate.Heirlooms = GetSession().GetCollectionMgr().GetAccountHeirlooms();
|
|
SendPacket(heirloomUpdate);
|
|
|
|
GetSession().GetCollectionMgr().SendFavoriteAppearances();
|
|
|
|
InitialSetup initialSetup = new InitialSetup();
|
|
initialSetup.ServerExpansionLevel = (byte)WorldConfig.GetIntValue(WorldCfg.Expansion);
|
|
SendPacket(initialSetup);
|
|
|
|
SetMover(this);
|
|
}
|
|
|
|
public void SendInitialPacketsAfterAddToMap()
|
|
{
|
|
UpdateVisibilityForPlayer();
|
|
|
|
// update zone
|
|
uint newzone, newarea;
|
|
GetZoneAndAreaId(out newzone, out newarea);
|
|
UpdateZone(newzone, newarea); // also call SendInitWorldStates();
|
|
|
|
GetSession().SendLoadCUFProfiles();
|
|
|
|
CastSpell(this, 836, true); // LOGINEFFECT
|
|
|
|
// set some aura effects that send packet to player client after add player to map
|
|
// SendMessageToSet not send it to player not it map, only for aura that not changed anything at re-apply
|
|
// same auras state lost at far teleport, send it one more time in this case also
|
|
AuraType[] auratypes =
|
|
{
|
|
AuraType.ModFear, AuraType.Transform, AuraType.WaterWalk,
|
|
AuraType.FeatherFall, AuraType.Hover, AuraType.SafeFall,
|
|
AuraType.Fly, AuraType.ModIncreaseMountedFlightSpeed, AuraType.None
|
|
};
|
|
foreach (var aura in auratypes)
|
|
{
|
|
var auraList = GetAuraEffectsByType(aura);
|
|
if (!auraList.Empty())
|
|
auraList.First().HandleEffect(this, AuraEffectHandleModes.SendForClient, true);
|
|
}
|
|
|
|
if (HasAuraType(AuraType.ModStun))
|
|
SetRooted(true);
|
|
|
|
MoveSetCompoundState setCompoundState = new MoveSetCompoundState();
|
|
// manual send package (have code in HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true); that must not be re-applied.
|
|
if (HasAuraType(AuraType.ModRoot) || HasAuraType(AuraType.ModRoot2))
|
|
setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveRoot, m_movementCounter++));
|
|
|
|
if (HasAuraType(AuraType.FeatherFall))
|
|
setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetFeatherFall, m_movementCounter++));
|
|
|
|
if (HasAuraType(AuraType.WaterWalk))
|
|
setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetWaterWalk, m_movementCounter++));
|
|
|
|
if (HasAuraType(AuraType.Hover))
|
|
setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetHovering, m_movementCounter++));
|
|
|
|
if (HasAuraType(AuraType.CanTurnWhileFalling))
|
|
setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveSetCanTurnWhileFalling, m_movementCounter++));
|
|
|
|
if (HasAura(196055)) //DH DoubleJump
|
|
setCompoundState.StateChanges.Add(new MoveSetCompoundState.MoveStateChange(ServerOpcodes.MoveEnableDoubleJump, m_movementCounter++));
|
|
|
|
if (!setCompoundState.StateChanges.Empty())
|
|
{
|
|
setCompoundState.MoverGUID = GetGUID();
|
|
SendPacket(setCompoundState);
|
|
}
|
|
|
|
SendAurasForTarget(this);
|
|
SendEnchantmentDurations(); // must be after add to map
|
|
SendItemDurations(); // must be after add to map
|
|
|
|
// raid downscaling - send difficulty to player
|
|
if (GetMap().IsRaid())
|
|
{
|
|
m_prevMapDifficulty = GetMap().GetDifficultyID();
|
|
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(m_prevMapDifficulty);
|
|
SendRaidDifficulty(difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy), (int)m_prevMapDifficulty);
|
|
}
|
|
else if (GetMap().IsNonRaidDungeon())
|
|
{
|
|
m_prevMapDifficulty = GetMap().GetDifficultyID();
|
|
SendDungeonDifficulty((int)m_prevMapDifficulty);
|
|
}
|
|
else if (!GetMap().Instanceable())
|
|
{
|
|
DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(m_prevMapDifficulty);
|
|
SendRaidDifficulty(difficulty.Flags.HasAnyFlag(DifficultyFlags.Legacy));
|
|
}
|
|
|
|
if (_garrison != null)
|
|
_garrison.SendRemoteInfo();
|
|
}
|
|
|
|
public bool CanSpeak()
|
|
{
|
|
return GetSession().m_muteTime <= Time.UnixTime;
|
|
}
|
|
|
|
public void RemoveSocial()
|
|
{
|
|
Global.SocialMgr.RemovePlayerSocial(GetGUID());
|
|
m_social = null;
|
|
}
|
|
|
|
public void SaveRecallPosition()
|
|
{
|
|
m_recall_location = new WorldLocation(this);
|
|
}
|
|
public void Recall() { TeleportTo(m_recall_location); }
|
|
|
|
public uint GetSaveTimer() { return m_nextSave; }
|
|
void SetSaveTimer(uint timer) { m_nextSave = timer; }
|
|
|
|
void SendAurasForTarget(Unit target)
|
|
{
|
|
if (target == null || target.GetVisibleAuras().Empty()) // speedup things
|
|
return;
|
|
|
|
var visibleAuras = target.GetVisibleAuras();
|
|
|
|
AuraUpdate update = new AuraUpdate();
|
|
update.UpdateAll = true;
|
|
update.UnitGUID = target.GetGUID();
|
|
|
|
foreach (var auraApp in visibleAuras)
|
|
{
|
|
AuraInfo auraInfo = new AuraInfo();
|
|
auraApp.BuildUpdatePacket(ref auraInfo, false);
|
|
update.Auras.Add(auraInfo);
|
|
}
|
|
|
|
SendPacket(update);
|
|
}
|
|
|
|
public void InitStatsForLevel(bool reapplyMods = false)
|
|
{
|
|
if (reapplyMods) //reapply stats values only on .reset stats (level) command
|
|
_RemoveAllStatBonuses();
|
|
|
|
uint basemana;
|
|
Global.ObjectMgr.GetPlayerClassLevelInfo(GetClass(), getLevel(), out basemana);
|
|
|
|
PlayerLevelInfo info = Global.ObjectMgr.GetPlayerLevelInfo(GetRace(), GetClass(), getLevel());
|
|
|
|
SetUInt32Value(PlayerFields.MaxLevel, WorldConfig.GetUIntValue(WorldCfg.MaxPlayerLevel));
|
|
SetUInt32Value(PlayerFields.NextLevelXp, Global.ObjectMgr.GetXPForLevel(getLevel()));
|
|
|
|
// reset before any aura state sources (health set/aura apply)
|
|
SetUInt32Value(UnitFields.AuraState, 0);
|
|
|
|
UpdateSkillsForLevel();
|
|
|
|
// set default cast time multiplier
|
|
SetFloatValue(UnitFields.ModCastSpeed, 1.0f);
|
|
SetFloatValue(UnitFields.ModCastHaste, 1.0f);
|
|
SetFloatValue(UnitFields.ModHaste, 1.0f);
|
|
SetFloatValue(UnitFields.ModRangedHaste, 1.0f);
|
|
SetFloatValue(UnitFields.ModHasteRegen, 1.0f);
|
|
SetFloatValue(UnitFields.ModTimeRate, 1.0f);
|
|
|
|
// reset size before reapply auras
|
|
SetObjectScale(1.0f);
|
|
|
|
// save base values (bonuses already included in stored stats
|
|
for (var i = Stats.Strength; i < Stats.Max; ++i)
|
|
SetCreateStat(i, info.stats[(int)i]);
|
|
|
|
for (var i = Stats.Strength; i < Stats.Max; ++i)
|
|
SetStat(i, info.stats[(int)i]);
|
|
|
|
SetCreateHealth(0);
|
|
|
|
//set create powers
|
|
SetCreateMana(basemana);
|
|
|
|
SetArmor((int)(GetCreateStat(Stats.Agility) * 2));
|
|
|
|
InitStatBuffMods();
|
|
|
|
//reset rating fields values
|
|
for (var index = PlayerFields.CombatRating1; index < PlayerFields.CombatRating1 + (int)CombatRating.Max; ++index)
|
|
SetUInt32Value(index, 0);
|
|
|
|
SetUInt32Value(PlayerFields.ModHealingDonePos, 0);
|
|
SetFloatValue(PlayerFields.ModHealingPct, 1.0f);
|
|
SetFloatValue(PlayerFields.ModHealingDonePct, 1.0f);
|
|
SetFloatValue(PlayerFields.ModPeriodicHealingDonePercent, 1.0f);
|
|
for (byte i = 0; i < 7; ++i)
|
|
{
|
|
SetUInt32Value(PlayerFields.ModDamageDoneNeg + i, 0);
|
|
SetUInt32Value(PlayerFields.ModDamageDonePos + i, 0);
|
|
SetFloatValue(PlayerFields.ModDamageDonePct + i, 1.0f);
|
|
}
|
|
SetFloatValue(PlayerFields.ModSpellPowerPct, 1.0f);
|
|
|
|
//reset attack power, damage and attack speed fields
|
|
SetBaseAttackTime(WeaponAttackType.BaseAttack, SharedConst.BaseAttackTime);
|
|
SetBaseAttackTime(WeaponAttackType.OffAttack, SharedConst.BaseAttackTime);
|
|
SetBaseAttackTime(WeaponAttackType.RangedAttack, SharedConst.BaseAttackTime);
|
|
|
|
SetFloatValue(UnitFields.MinDamage, 0.0f);
|
|
SetFloatValue(UnitFields.MaxDamage, 0.0f);
|
|
SetFloatValue(UnitFields.MinOffHandDamage, 0.0f);
|
|
SetFloatValue(UnitFields.MaxOffHandDamage, 0.0f);
|
|
SetFloatValue(UnitFields.MinRangedDamage, 0.0f);
|
|
SetFloatValue(UnitFields.MaxRangedDamage, 0.0f);
|
|
for (var i = 0; i < 3; ++i)
|
|
{
|
|
SetFloatValue(PlayerFields.WeaponDmgMultipliers + i, 1.0f);
|
|
SetFloatValue(PlayerFields.WeaponAtkSpeedMultipliers + i, 1.0f);
|
|
}
|
|
|
|
SetInt32Value(UnitFields.AttackPower, 0);
|
|
SetFloatValue(UnitFields.AttackPowerMultiplier, 0.0f);
|
|
SetInt32Value(UnitFields.RangedAttackPower, 0);
|
|
SetFloatValue(UnitFields.RangedAttackPowerMultiplier, 0.0f);
|
|
|
|
// Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
|
|
SetFloatValue(PlayerFields.CritPercentage, 0.0f);
|
|
SetFloatValue(PlayerFields.OffhandCritPercentage, 0.0f);
|
|
SetFloatValue(PlayerFields.RangedCritPercentage, 0.0f);
|
|
|
|
// Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
|
|
SetFloatValue(PlayerFields.SpellCritPercentage1, 0.0f);
|
|
|
|
SetFloatValue(PlayerFields.ParryPercentage, 0.0f);
|
|
SetFloatValue(PlayerFields.BlockPercentage, 0.0f);
|
|
|
|
// Static 30% damage blocked
|
|
SetUInt32Value(PlayerFields.ShieldBlock, 30);
|
|
|
|
// Dodge percentage
|
|
SetFloatValue(PlayerFields.DodgePercentage, 0.0f);
|
|
|
|
// set armor (resistance 0) to original value (create_agility*2)
|
|
SetArmor((int)(GetCreateStat(Stats.Agility) * 2));
|
|
SetResistanceBuffMods(SpellSchools.Normal, true, 0.0f);
|
|
SetResistanceBuffMods(SpellSchools.Normal, false, 0.0f);
|
|
// set other resistance to original value (0)
|
|
for (var i = 1; i < (int)SpellSchools.Max; ++i)
|
|
{
|
|
SetResistance((SpellSchools)i, 0);
|
|
SetResistanceBuffMods((SpellSchools)i, true, 0.0f);
|
|
SetResistanceBuffMods((SpellSchools)i, false, 0.0f);
|
|
}
|
|
|
|
SetUInt32Value(PlayerFields.ModTargetResistance, 0);
|
|
SetUInt32Value(PlayerFields.ModTargetPhysicalResistance, 0);
|
|
for (var i = 0; i < (int)SpellSchools.Max; ++i)
|
|
{
|
|
SetUInt32Value(UnitFields.PowerCostModifier + i, 0);
|
|
SetFloatValue(UnitFields.PowerCostMultiplier + i, 0.0f);
|
|
}
|
|
// Reset no reagent cost field
|
|
for (byte i = 0; i < 3; ++i)
|
|
SetUInt32Value(PlayerFields.NoReagentCost1 + i, 0);
|
|
// Init data for form but skip reapply item mods for form
|
|
InitDataForForm(reapplyMods);
|
|
|
|
// save new stats
|
|
for (var i = PowerType.Mana; i < PowerType.Max; ++i)
|
|
SetMaxPower(i, GetCreatePowers(i));
|
|
|
|
SetMaxHealth(0); // stamina bonus will applied later
|
|
|
|
// cleanup mounted state (it will set correctly at aura loading if player saved at mount.
|
|
SetUInt32Value(UnitFields.MountDisplayId, 0);
|
|
|
|
// cleanup unit flags (will be re-applied if need at aura load).
|
|
RemoveFlag(UnitFields.Flags,
|
|
UnitFlags.NonAttackable | UnitFlags.RemoveClientControl | UnitFlags.NotAttackable1 |
|
|
UnitFlags.ImmuneToPc | UnitFlags.ImmuneToNpc | UnitFlags.Looting |
|
|
UnitFlags.PetInCombat | UnitFlags.Silenced | UnitFlags.Pacified |
|
|
UnitFlags.Stunned | UnitFlags.InCombat | UnitFlags.Disarmed |
|
|
UnitFlags.Confused | UnitFlags.Fleeing | UnitFlags.NotSelectable |
|
|
UnitFlags.Skinnable | UnitFlags.Mount | UnitFlags.TaxiFlight);
|
|
SetFlag(UnitFields.Flags, UnitFlags.PvpAttackable); // must be set
|
|
|
|
SetFlag(UnitFields.Flags2, UnitFlags2.RegeneratePower);// must be set
|
|
|
|
// cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example.
|
|
RemoveFlag(PlayerFields.Flags, PlayerFlags.AFK | PlayerFlags.DND | PlayerFlags.GM | PlayerFlags.Ghost | PlayerFlags.AllowOnlyAbility);
|
|
|
|
RemoveStandFlags(UnitBytes1Flags.All); // one form stealth modified bytes
|
|
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, (UnitBytes2Flags.FFAPvp | UnitBytes2Flags.Sanctuary));
|
|
|
|
// restore if need some important flags
|
|
SetUInt32Value(PlayerFields.FieldBytes2, 0); // flags empty by default
|
|
|
|
if (reapplyMods) // reapply stats values only on .reset stats (level) command
|
|
_ApplyAllStatBonuses();
|
|
|
|
// set current level health and mana/energy to maximum after applying all mods.
|
|
SetFullHealth();
|
|
SetFullPower(PowerType.Mana);
|
|
SetFullPower(PowerType.Energy);
|
|
if (GetPower(PowerType.Rage) > GetMaxPower(PowerType.Rage))
|
|
SetFullPower(PowerType.Rage);
|
|
SetFullPower(PowerType.Focus);
|
|
SetPower(PowerType.RunicPower, 0);
|
|
|
|
// update level to hunter/summon pet
|
|
Pet pet = GetPet();
|
|
if (pet)
|
|
pet.SynchronizeLevelWithOwner();
|
|
}
|
|
public void InitDataForForm(bool reapplyMods = false)
|
|
{
|
|
ShapeShiftForm form = GetShapeshiftForm();
|
|
|
|
var ssEntry = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)form);
|
|
if (ssEntry != null && ssEntry.CombatRoundTime != 0)
|
|
{
|
|
SetBaseAttackTime(WeaponAttackType.BaseAttack, ssEntry.CombatRoundTime);
|
|
SetBaseAttackTime(WeaponAttackType.OffAttack, ssEntry.CombatRoundTime);
|
|
SetBaseAttackTime(WeaponAttackType.RangedAttack, SharedConst.BaseAttackTime);
|
|
}
|
|
else
|
|
SetRegularAttackTime();
|
|
|
|
UpdateDisplayPower();
|
|
|
|
// update auras at form change, ignore this at mods reapply (.reset stats/etc) when form not change.
|
|
if (!reapplyMods)
|
|
UpdateEquipSpellsAtFormChange();
|
|
|
|
UpdateAttackPowerAndDamage();
|
|
UpdateAttackPowerAndDamage(true);
|
|
}
|
|
|
|
public ReputationRank GetReputationRank(uint faction)
|
|
{
|
|
var factionEntry = CliDB.FactionStorage.LookupByKey(faction);
|
|
return GetReputationMgr().GetRank(factionEntry);
|
|
}
|
|
public ReputationMgr GetReputationMgr()
|
|
{
|
|
return reputationMgr;
|
|
}
|
|
|
|
#region Sends / Updates
|
|
void BeforeVisibilityDestroy(WorldObject obj, Player p)
|
|
{
|
|
if (!obj.IsTypeId(TypeId.Unit))
|
|
return;
|
|
|
|
if (p.GetPetGUID() == obj.GetGUID() && obj.ToCreature().IsPet())
|
|
((Pet)obj).Remove(PetSaveMode.NotInSlot, true);
|
|
}
|
|
public void UpdateVisibilityOf(WorldObject target)
|
|
{
|
|
if (HaveAtClient(target))
|
|
{
|
|
if (!CanSeeOrDetect(target, false, true))
|
|
{
|
|
if (target.IsTypeId(TypeId.Unit))
|
|
BeforeVisibilityDestroy(target.ToCreature(), this);
|
|
|
|
target.DestroyForPlayer(this);
|
|
m_clientGUIDs.Remove(target.GetGUID());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CanSeeOrDetect(target, false, true))
|
|
{
|
|
target.SendUpdateToPlayer(this);
|
|
m_clientGUIDs.Add(target.GetGUID());
|
|
|
|
// target aura duration for caster show only if target exist at caster client
|
|
// send data at target visibility change (adding to client)
|
|
if (target.isTypeMask(TypeMask.Unit))
|
|
SendInitialVisiblePackets(target.ToUnit());
|
|
}
|
|
}
|
|
}
|
|
public void UpdateVisibilityOf<T>(T target, UpdateData data, List<Unit> visibleNow) where T : WorldObject
|
|
{
|
|
if (HaveAtClient(target))
|
|
{
|
|
if (!CanSeeOrDetect(target, false, true))
|
|
{
|
|
BeforeVisibilityDestroy(target, this);
|
|
|
|
target.BuildOutOfRangeUpdateBlock(data);
|
|
m_clientGUIDs.Remove(target.GetGUID());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CanSeeOrDetect(target, false, true))
|
|
{
|
|
target.BuildCreateUpdateBlockForPlayer(data, this);
|
|
UpdateVisibilityOf_helper(m_clientGUIDs, target, visibleNow);
|
|
}
|
|
}
|
|
}
|
|
void UpdateVisibilityOf_helper<T>(List<ObjectGuid> s64, GameObject target, List<Unit> v)
|
|
{
|
|
// @HACK: This is to prevent objects like deeprun tram from disappearing when player moves far from its spawn point while riding it
|
|
// But exclude stoppable elevators from this hack - they would be teleporting from one end to another
|
|
// if affected transports move so far horizontally that it causes them to run out of visibility range then you are out of luck
|
|
// fix visibility instead of adding hacks here
|
|
if (target.IsDynTransport())
|
|
s64.Add(target.GetGUID());
|
|
}
|
|
void UpdateVisibilityOf_helper(List<ObjectGuid> s64, Creature target, List<Unit> v)
|
|
{
|
|
s64.Add(target.GetGUID());
|
|
v.Add(target);
|
|
}
|
|
|
|
void UpdateVisibilityOf_helper(List<ObjectGuid> s64, Player target, List<Unit> v)
|
|
{
|
|
s64.Add(target.GetGUID());
|
|
v.Add(target);
|
|
}
|
|
|
|
void UpdateVisibilityOf_helper<T>(List<ObjectGuid> s64, T target, List<Unit> v) where T : WorldObject
|
|
{
|
|
s64.Add(target.GetGUID());
|
|
}
|
|
|
|
public void SendInitialVisiblePackets(Unit target)
|
|
{
|
|
SendAurasForTarget(target);
|
|
if (target.IsAlive())
|
|
{
|
|
if (target.HasUnitState(UnitState.MeleeAttacking) && target.GetVictim() != null)
|
|
target.SendMeleeAttackStart(target.GetVictim());
|
|
}
|
|
}
|
|
|
|
public override void UpdateObjectVisibility(bool forced = true)
|
|
{
|
|
// Prevent updating visibility if player is not in world (example: LoadFromDB sets drunkstate which updates invisibility while player is not in map)
|
|
if (!IsInWorld)
|
|
return;
|
|
|
|
if (!forced)
|
|
AddToNotify(NotifyFlags.VisibilityChanged);
|
|
else
|
|
{
|
|
base.UpdateObjectVisibility(true);
|
|
UpdateVisibilityForPlayer();
|
|
}
|
|
}
|
|
|
|
public void UpdateVisibilityForPlayer()
|
|
{
|
|
// updates visibility of all objects around point of view for current player
|
|
var notifier = new VisibleNotifier(this);
|
|
Cell.VisitAllObjects(seerView, notifier, GetSightRange());
|
|
notifier.SendToSelf(); // send gathered data
|
|
}
|
|
|
|
public void SetSeer(WorldObject target) { seerView = target; }
|
|
|
|
public override void SendMessageToSetInRange(ServerPacket data, float dist, bool self)
|
|
{
|
|
if (self)
|
|
SendPacket(data);
|
|
|
|
MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, dist);
|
|
Cell.VisitWorldObjects(this, notifier, dist);
|
|
}
|
|
|
|
void SendMessageToSetInRange(ServerPacket data, float dist, bool self, bool own_team_only)
|
|
{
|
|
if (self)
|
|
SendPacket(data);
|
|
|
|
MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, dist, own_team_only);
|
|
Cell.VisitWorldObjects(this, notifier, dist);
|
|
}
|
|
|
|
public override void SendMessageToSet(ServerPacket data, Player skipped_rcvr)
|
|
{
|
|
if (skipped_rcvr != this)
|
|
SendPacket(data);
|
|
|
|
// we use World.GetMaxVisibleDistance() because i cannot see why not use a distance
|
|
// update: replaced by GetMap().GetVisibilityDistance()
|
|
MessageDistDeliverer notifier = new MessageDistDeliverer(this, data, GetVisibilityRange(), false, skipped_rcvr);
|
|
Cell.VisitWorldObjects(this, notifier, GetVisibilityRange());
|
|
}
|
|
public override void SendMessageToSet(ServerPacket data, bool self)
|
|
{
|
|
SendMessageToSetInRange(data, GetVisibilityRange(), self);
|
|
}
|
|
|
|
public override bool UpdatePosition(Position pos, bool teleport = false)
|
|
{
|
|
return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport);
|
|
}
|
|
|
|
public override bool UpdatePosition(float x, float y, float z, float orientation, bool teleport = false)
|
|
{
|
|
if (!base.UpdatePosition(x, y, z, orientation, teleport))
|
|
return false;
|
|
|
|
// group update
|
|
if (GetGroup())
|
|
SetGroupUpdateFlag(GroupUpdateFlags.Position);
|
|
|
|
if (GetTrader() && !IsWithinDistInMap(GetTrader(), SharedConst.InteractionDistance))
|
|
GetSession().SendCancelTrade();
|
|
|
|
CheckAreaExploreAndOutdoor();
|
|
|
|
return true;
|
|
}
|
|
|
|
void SendNewCurrency(uint id)
|
|
{
|
|
var Curr = _currencyStorage.LookupByKey(id);
|
|
if (Curr == null)
|
|
return;
|
|
|
|
CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(id);
|
|
if (entry == null) // should never happen
|
|
return;
|
|
|
|
SetupCurrency packet = new SetupCurrency();
|
|
SetupCurrency.Record record = new SetupCurrency.Record();
|
|
record.Type = entry.Id;
|
|
record.Quantity = Curr.Quantity;
|
|
record.WeeklyQuantity.Set(Curr.WeeklyQuantity);
|
|
record.MaxWeeklyQuantity.Set(GetCurrencyWeekCap(entry));
|
|
record.TrackedQuantity.Set(Curr.TrackedQuantity);
|
|
record.Flags = Curr.Flags;
|
|
|
|
packet.Data.Add(record);
|
|
|
|
SendPacket(packet);
|
|
}
|
|
|
|
void SendCurrencies()
|
|
{
|
|
SetupCurrency packet = new SetupCurrency();
|
|
|
|
foreach (var pair in _currencyStorage)
|
|
{
|
|
CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(pair.Key);
|
|
|
|
// not send init meta currencies.
|
|
if (entry == null || entry.CategoryID == 89) //CURRENCY_CATEGORY_META_CONQUEST
|
|
continue;
|
|
|
|
SetupCurrency.Record record = new SetupCurrency.Record();
|
|
record.Type = entry.Id;
|
|
record.Quantity = pair.Value.Quantity;
|
|
record.WeeklyQuantity.Set(pair.Value.WeeklyQuantity);
|
|
record.MaxWeeklyQuantity.Set(GetCurrencyWeekCap(entry));
|
|
record.TrackedQuantity.Set(pair.Value.TrackedQuantity);
|
|
record.Flags = pair.Value.Flags;
|
|
|
|
packet.Data.Add(record);
|
|
}
|
|
|
|
SendPacket(packet);
|
|
}
|
|
|
|
public void ResetCurrencyWeekCap()
|
|
{
|
|
for (byte arenaSlot = 0; arenaSlot < 3; arenaSlot++)
|
|
{
|
|
uint arenaTeamId = GetArenaTeamId(arenaSlot);
|
|
if (arenaTeamId != 0)
|
|
{
|
|
ArenaTeam arenaTeam = Global.ArenaTeamMgr.GetArenaTeamById(arenaTeamId);
|
|
arenaTeam.FinishWeek(); // set played this week etc values to 0 in memory, too
|
|
arenaTeam.SaveToDB(); // save changes
|
|
arenaTeam.NotifyStatsChanged(); // notify the players of the changes
|
|
}
|
|
}
|
|
|
|
foreach (var currency in _currencyStorage.Values)
|
|
{
|
|
|
|
currency.WeeklyQuantity = 0;
|
|
currency.state = PlayerCurrencyState.Changed;
|
|
}
|
|
|
|
SendPacket(new ResetWeeklyCurrency());
|
|
}
|
|
|
|
void CheckAreaExploreAndOutdoor()
|
|
{
|
|
if (!IsAlive())
|
|
return;
|
|
|
|
if (IsInFlight())
|
|
return;
|
|
|
|
bool isOutdoor;
|
|
uint areaId = GetMap().GetAreaId(GetPositionX(), GetPositionY(), GetPositionZ(), out isOutdoor);
|
|
AreaTableRecord areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId);
|
|
|
|
if (WorldConfig.GetBoolValue(WorldCfg.VmapIndoorCheck) && !isOutdoor)
|
|
RemoveAurasWithAttribute(SpellAttr0.OutdoorsOnly);
|
|
|
|
if (areaId == 0)
|
|
return;
|
|
|
|
if (areaEntry == null)
|
|
{
|
|
Log.outError(LogFilter.Player, "Player '{0}' ({1}) discovered unknown area (x: {2} y: {3} z: {4} map: {5})",
|
|
GetName(), GetGUID().ToString(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId());
|
|
return;
|
|
}
|
|
|
|
int offset = areaEntry.AreaBit / 32;
|
|
if (offset >= PlayerConst.ExploredZonesSize)
|
|
{
|
|
Log.outError(LogFilter.Player, "Wrong area flag {0} in map data for (X: {1} Y: {2}) point to field PLAYER_EXPLORED_ZONES_1 + {3} ( {4} must be < {5} ).",
|
|
areaId, GetPositionX(), GetPositionY(), offset, offset, PlayerConst.ExploredZonesSize);
|
|
return;
|
|
}
|
|
|
|
uint val = 1u << (areaEntry.AreaBit % 32);
|
|
uint currFields = GetUInt32Value(PlayerFields.ExploredZones1 + offset);
|
|
|
|
if (!Convert.ToBoolean(currFields & val))
|
|
{
|
|
SetUInt32Value(PlayerFields.ExploredZones1 + offset, currFields | val);
|
|
|
|
UpdateCriteria(CriteriaTypes.ExploreArea);
|
|
|
|
if (areaEntry.ExplorationLevel > 0)
|
|
{
|
|
if (getLevel() >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
|
{
|
|
SendExplorationExperience(areaId, 0);
|
|
}
|
|
else
|
|
{
|
|
int diff = (int)(getLevel() - areaEntry.ExplorationLevel);
|
|
uint XP = 0;
|
|
if (diff < -5)
|
|
{
|
|
XP = (uint)(Global.ObjectMgr.GetBaseXP(getLevel() + 5) * WorldConfig.GetFloatValue(WorldCfg.RateXpExplore));
|
|
}
|
|
else if (diff > 5)
|
|
{
|
|
int exploration_percent = 100 - ((diff - 5) * 5);
|
|
if (exploration_percent < 0)
|
|
exploration_percent = 0;
|
|
|
|
XP = (uint)(Global.ObjectMgr.GetBaseXP(areaEntry.ExplorationLevel) * exploration_percent / 100 * WorldConfig.GetFloatValue(WorldCfg.RateXpExplore));
|
|
}
|
|
else
|
|
{
|
|
XP = (uint)(Global.ObjectMgr.GetBaseXP(areaEntry.ExplorationLevel) * WorldConfig.GetFloatValue(WorldCfg.RateXpExplore));
|
|
}
|
|
|
|
GiveXP(XP, null);
|
|
SendExplorationExperience(areaId, XP);
|
|
}
|
|
Log.outInfo(LogFilter.Player, "Player {0} discovered a new area: {1}", GetGUID().ToString(), areaId);
|
|
}
|
|
}
|
|
}
|
|
void SendExplorationExperience(uint Area, uint Experience)
|
|
{
|
|
SendPacket(new ExplorationExperience(Experience, Area));
|
|
}
|
|
|
|
public void SendSysMessage(CypherStrings str, params object[] args)
|
|
{
|
|
string input = Global.ObjectMgr.GetCypherString(str);
|
|
string pattern = @"%(\d+(\.\d+)?)?(d|f|s|u)";
|
|
|
|
int count = 0;
|
|
string result = System.Text.RegularExpressions.Regex.Replace(input, pattern, m =>
|
|
{
|
|
return String.Concat("{", count++, "}");
|
|
});
|
|
|
|
SendSysMessage(result, args);
|
|
}
|
|
public void SendSysMessage(string str, params object[] args)
|
|
{
|
|
new CommandHandler(Session).SendSysMessage(str, args);
|
|
}
|
|
public void SendBuyError(BuyResult msg, Creature creature, uint item)
|
|
{
|
|
BuyFailed packet = new BuyFailed();
|
|
packet.VendorGUID = creature ? creature.GetGUID() : ObjectGuid.Empty;
|
|
packet.Muid = item;
|
|
packet.Reason = msg;
|
|
SendPacket(packet);
|
|
}
|
|
public void SendSellError(SellResult msg, Creature creature, ObjectGuid guid)
|
|
{
|
|
SellResponse sellResponse = new SellResponse();
|
|
sellResponse.VendorGUID = (creature ? creature.GetGUID() : ObjectGuid.Empty);
|
|
sellResponse.ItemGUID = guid;
|
|
sellResponse.Reason = msg;
|
|
SendPacket(sellResponse);
|
|
}
|
|
#endregion
|
|
|
|
#region Chat
|
|
public override void Say(string text, Language language, WorldObject obj = null)
|
|
{
|
|
Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Say, language, text);
|
|
|
|
ChatPkt data = new ChatPkt();
|
|
data.Initialize(ChatMsg.Say, language, this, this, text);
|
|
SendMessageToSetInRange(data, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), true);
|
|
}
|
|
public override void Say(uint textId, WorldObject target = null)
|
|
{
|
|
Talk(textId, ChatMsg.Say, WorldConfig.GetFloatValue(WorldCfg.ListenRangeSay), target);
|
|
}
|
|
public override void Yell(string text, Language language, WorldObject obj = null)
|
|
{
|
|
Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Yell, language, text);
|
|
|
|
ChatPkt data = new ChatPkt();
|
|
data.Initialize(ChatMsg.Yell, language, this, this, text);
|
|
SendMessageToSetInRange(data, WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell), true);
|
|
}
|
|
public override void Yell(uint textId, WorldObject target = null)
|
|
{
|
|
Talk(textId, ChatMsg.Yell, WorldConfig.GetFloatValue(WorldCfg.ListenRangeYell), target);
|
|
}
|
|
public override void TextEmote(string text, WorldObject obj = null, bool something = false)
|
|
{
|
|
Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Emote, Language.Universal, text);
|
|
|
|
ChatPkt data = new ChatPkt();
|
|
data.Initialize(ChatMsg.Emote, Language.Universal, this, this, text);
|
|
SendMessageToSetInRange(data, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), !GetSession().HasPermission(RBACPermissions.TwoSideInteractionChat));
|
|
}
|
|
public override void TextEmote(uint textId, WorldObject target = null, bool isBossEmote = false)
|
|
{
|
|
Talk(textId, ChatMsg.Emote, WorldConfig.GetFloatValue(WorldCfg.ListenRangeTextemote), target);
|
|
}
|
|
public void WhisperAddon(string text, string prefix, Player receiver)
|
|
{
|
|
Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Whisper, Language.Universal, text, receiver);
|
|
|
|
if (!receiver.GetSession().IsAddonRegistered(prefix))
|
|
return;
|
|
|
|
ChatPkt data = new ChatPkt();
|
|
data.Initialize(ChatMsg.Whisper, Language.Addon, this, this, text, 0, "", LocaleConstant.enUS, prefix);
|
|
receiver.SendPacket(data);
|
|
}
|
|
public override void Whisper(string text, Language language, Player target = null, bool something = false)
|
|
{
|
|
bool isAddonMessage = language == Language.Addon;
|
|
|
|
if (!isAddonMessage) // if not addon data
|
|
language = Language.Universal; // whispers should always be readable
|
|
|
|
//Player rPlayer = Global.ObjAccessor.FindPlayer(receiver);
|
|
|
|
Global.ScriptMgr.OnPlayerChat(this, ChatMsg.Whisper, language, text, target);
|
|
|
|
ChatPkt data = new ChatPkt();
|
|
data.Initialize(ChatMsg.Whisper, language, this, this, text);
|
|
target.SendPacket(data);
|
|
|
|
// rest stuff shouldn't happen in case of addon message
|
|
if (isAddonMessage)
|
|
return;
|
|
|
|
data.Initialize(ChatMsg.WhisperInform, language, target, target, text);
|
|
SendPacket(data);
|
|
|
|
if (!isAcceptWhispers() && !IsGameMaster() && !target.IsGameMaster())
|
|
{
|
|
SetAcceptWhispers(true);
|
|
SendSysMessage(CypherStrings.CommandWhisperon);
|
|
}
|
|
|
|
// announce afk or dnd message
|
|
if (target.isAFK())
|
|
SendSysMessage(CypherStrings.PlayerAfk, target.GetName(), target.autoReplyMsg);
|
|
else if (target.isDND())
|
|
SendSysMessage(CypherStrings.PlayerDnd, target.GetName(), target.autoReplyMsg);
|
|
}
|
|
|
|
public override void Whisper(uint textId, Player target, bool isBossWhisper = false)
|
|
{
|
|
if (!target)
|
|
return;
|
|
|
|
BroadcastTextRecord bct = CliDB.BroadcastTextStorage.LookupByKey(textId);
|
|
if (bct == null)
|
|
{
|
|
Log.outError(LogFilter.Unit, "WorldObject.MonsterWhisper: `broadcast_text` was not {0} found", textId);
|
|
return;
|
|
}
|
|
|
|
LocaleConstant locale = target.GetSession().GetSessionDbLocaleIndex();
|
|
ChatPkt packet = new ChatPkt();
|
|
packet.Initialize(ChatMsg.Whisper, Language.Universal, this, target, Global.DB2Mgr.GetBroadcastTextValue(bct, locale, GetGender()));
|
|
target.SendPacket(packet);
|
|
}
|
|
#endregion
|
|
|
|
public void ClearWhisperWhiteList() { WhisperList.Clear(); }
|
|
public void AddWhisperWhiteList(ObjectGuid guid) { WhisperList.Add(guid); }
|
|
public bool IsInWhisperWhiteList(ObjectGuid guid) { return WhisperList.Contains(guid); }
|
|
public void RemoveFromWhisperWhiteList(ObjectGuid guid) { WhisperList.Remove(guid); }
|
|
|
|
public void SetFallInformation(uint time, float z)
|
|
{
|
|
m_lastFallTime = time;
|
|
m_lastFallZ = z;
|
|
}
|
|
|
|
public byte getCinematic() { return m_cinematic; }
|
|
public void setCinematic(byte cine) { m_cinematic = cine; }
|
|
|
|
public uint GetMovie() { return m_movie; }
|
|
public void SetMovie(uint movie) { m_movie = movie; }
|
|
|
|
public void SendCinematicStart(uint CinematicSequenceId)
|
|
{
|
|
TriggerCinematic packet = new TriggerCinematic();
|
|
packet.CinematicID = CinematicSequenceId;
|
|
SendPacket(packet);
|
|
|
|
CinematicSequencesRecord sequence = CliDB.CinematicSequencesStorage.LookupByKey(CinematicSequenceId);
|
|
if (sequence != null)
|
|
_cinematicMgr.SetActiveCinematicCamera(sequence.Camera[0]);
|
|
}
|
|
public void SendMovieStart(uint movieId)
|
|
{
|
|
SetMovie(movieId);
|
|
TriggerMovie packet = new TriggerMovie();
|
|
packet.MovieID = movieId;
|
|
SendPacket(packet);
|
|
}
|
|
|
|
public override void SetObjectScale(float scale)
|
|
{
|
|
base.SetObjectScale(scale);
|
|
SetFloatValue(UnitFields.BoundingRadius, scale * SharedConst.DefaultWorldObjectSize);
|
|
SetFloatValue(UnitFields.CombatReach, scale * SharedConst.DefaultCombatReach);
|
|
if (IsInWorld)
|
|
SendMovementSetCollisionHeight(scale * GetCollisionHeight(IsMounted()));
|
|
}
|
|
|
|
void SetXP(uint xp)
|
|
{
|
|
SetUInt32Value(PlayerFields.Xp, xp);
|
|
|
|
int playerLevelDelta = 0;
|
|
|
|
// If XP < 50%, player should see scaling creature with -1 level except for level max
|
|
if (getLevel() < SharedConst.MaxLevel && xp < (GetUInt32Value(PlayerFields.NextLevelXp) / 2))
|
|
playerLevelDelta = -1;
|
|
|
|
SetInt32Value(PlayerFields.ScalingLevelDelta, playerLevelDelta);
|
|
}
|
|
|
|
public void GiveXP(uint xp, Unit victim, float group_rate = 1.0f)
|
|
{
|
|
if (xp < 1)
|
|
return;
|
|
|
|
if (!IsAlive() && GetBattlegroundId() == 0)
|
|
return;
|
|
|
|
if (HasFlag(PlayerFields.Flags, PlayerFlags.NoXPGain))
|
|
return;
|
|
|
|
if (victim != null && victim.IsTypeId(TypeId.Unit) && !victim.ToCreature().hasLootRecipient())
|
|
return;
|
|
|
|
uint level = getLevel();
|
|
|
|
Global.ScriptMgr.OnGivePlayerXP(this, xp, victim);
|
|
|
|
// XP to money conversion processed in Player.RewardQuest
|
|
if (level >= WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
|
return;
|
|
|
|
uint bonus_xp = 0;
|
|
bool recruitAFriend = GetsRecruitAFriendBonus(true);
|
|
|
|
// RaF does NOT stack with rested experience
|
|
if (recruitAFriend)
|
|
bonus_xp = 2 * xp; // xp + bonus_xp must add up to 3 * xp for RaF; calculation for quests done client-side
|
|
else
|
|
bonus_xp = victim != null ? _restMgr.GetRestBonusFor(RestTypes.XP, xp) : 0; // XP resting bonus
|
|
|
|
LogXPGain packet = new LogXPGain();
|
|
packet.Victim = victim ? victim.GetGUID() : ObjectGuid.Empty;
|
|
packet.Original = (int)(xp + bonus_xp);
|
|
packet.Reason = victim ? PlayerLogXPReason.Kill : PlayerLogXPReason.NoKill;
|
|
packet.Amount = (int)xp;
|
|
packet.GroupBonus = group_rate;
|
|
packet.ReferAFriend = recruitAFriend;
|
|
SendPacket(packet);
|
|
|
|
uint curXP = GetUInt32Value(PlayerFields.Xp);
|
|
uint nextLvlXP = GetUInt32Value(PlayerFields.NextLevelXp);
|
|
uint newXP = curXP + xp + bonus_xp;
|
|
|
|
while (newXP >= nextLvlXP && level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
|
{
|
|
newXP -= nextLvlXP;
|
|
|
|
if (level < WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel))
|
|
GiveLevel(level + 1);
|
|
|
|
level = getLevel();
|
|
nextLvlXP = GetUInt32Value(PlayerFields.NextLevelXp);
|
|
}
|
|
|
|
SetXP(newXP);
|
|
}
|
|
|
|
public void HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply)
|
|
{
|
|
if (modGroup >= BaseModGroup.End || modType >= BaseModType.End)
|
|
{
|
|
Log.outError(LogFilter.Spells, "ERROR in HandleBaseModValue(): non existed BaseModGroup of wrong BaseModType!");
|
|
return;
|
|
}
|
|
|
|
switch (modType)
|
|
{
|
|
case BaseModType.FlatMod:
|
|
m_auraBaseMod[(int)modGroup][(int)modType] += apply ? amount : -amount;
|
|
break;
|
|
case BaseModType.PCTmod:
|
|
MathFunctions.ApplyPercentModFloatVar(ref m_auraBaseMod[(int)modGroup][(int)modType], amount, apply);
|
|
break;
|
|
}
|
|
|
|
if (!CanModifyStats())
|
|
return;
|
|
|
|
switch (modGroup)
|
|
{
|
|
case BaseModGroup.CritPercentage:
|
|
UpdateCritPercentage(WeaponAttackType.BaseAttack);
|
|
break;
|
|
case BaseModGroup.RangedCritPercentage:
|
|
UpdateCritPercentage(WeaponAttackType.RangedAttack);
|
|
break;
|
|
case BaseModGroup.OffhandCritPercentage:
|
|
UpdateCritPercentage(WeaponAttackType.OffAttack);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void AddComboPoints(sbyte count, Spell spell = null)
|
|
{
|
|
if (count == 0)
|
|
return;
|
|
|
|
sbyte comboPoints = spell != null ? spell.m_comboPointGain : (sbyte)GetPower(PowerType.ComboPoints);
|
|
|
|
// without combo points lost (duration checked in aura)
|
|
RemoveAurasByType(AuraType.RetainComboPoints);
|
|
|
|
comboPoints += (sbyte)count;
|
|
|
|
if (comboPoints > 5)
|
|
comboPoints = 5;
|
|
else if (comboPoints < 0)
|
|
comboPoints = 0;
|
|
|
|
if (spell == null)
|
|
SetPower(PowerType.ComboPoints, comboPoints);
|
|
else
|
|
spell.m_comboPointGain = comboPoints;
|
|
}
|
|
public void GainSpellComboPoints(sbyte count)
|
|
{
|
|
if (count == 0)
|
|
return;
|
|
|
|
sbyte cp = (sbyte)GetPower(PowerType.ComboPoints);
|
|
cp += count;
|
|
|
|
if (cp > 5)
|
|
cp = 5;
|
|
else if (cp < 0)
|
|
cp = 0;
|
|
|
|
SetPower(PowerType.ComboPoints, cp);
|
|
}
|
|
public void ClearComboPoints()
|
|
{
|
|
// without combopoints lost (duration checked in aura)
|
|
RemoveAurasByType(AuraType.RetainComboPoints);
|
|
|
|
SetPower(PowerType.ComboPoints, 0);
|
|
}
|
|
public byte GetComboPoints() { return (byte)GetPower(PowerType.ComboPoints); }
|
|
|
|
public byte GetDrunkValue() { return GetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetInebriation); }
|
|
public void SetDrunkValue(byte newDrunkValue, uint itemId = 0)
|
|
{
|
|
bool isSobering = newDrunkValue < GetDrunkValue();
|
|
DrunkenState oldDrunkenState = GetDrunkenstateByValue(GetDrunkValue());
|
|
if (newDrunkValue > 100)
|
|
newDrunkValue = 100;
|
|
|
|
// select drunk percent or total SPELL_AURA_MOD_FAKE_INEBRIATE amount, whichever is higher for visibility updates
|
|
int drunkPercent = Math.Max(newDrunkValue, GetTotalAuraModifier(AuraType.ModFakeInebriate));
|
|
if (drunkPercent != 0)
|
|
{
|
|
m_invisibilityDetect.AddFlag(InvisibilityType.Drunk);
|
|
m_invisibilityDetect.SetValue(InvisibilityType.Drunk, drunkPercent);
|
|
}
|
|
else if (!HasAuraType(AuraType.ModFakeInebriate) && newDrunkValue == 0)
|
|
m_invisibilityDetect.DelFlag(InvisibilityType.Drunk);
|
|
|
|
DrunkenState newDrunkenState = GetDrunkenstateByValue(newDrunkValue);
|
|
SetByteValue(PlayerFields.Bytes3, PlayerFieldOffsets.Bytes3OffsetInebriation, newDrunkValue);
|
|
UpdateObjectVisibility();
|
|
|
|
if (!isSobering)
|
|
m_drunkTimer = 0; // reset sobering timer
|
|
|
|
if (newDrunkenState == oldDrunkenState)
|
|
return;
|
|
|
|
CrossedInebriationThreshold data = new CrossedInebriationThreshold();
|
|
data.Guid = GetGUID();
|
|
data.Threshold = (uint)newDrunkenState;
|
|
data.ItemID = itemId;
|
|
|
|
SendMessageToSet(data, true);
|
|
}
|
|
public static DrunkenState GetDrunkenstateByValue(byte value)
|
|
{
|
|
if (value >= 90)
|
|
return DrunkenState.Smashed;
|
|
if (value >= 50)
|
|
return DrunkenState.Drunk;
|
|
if (value != 0)
|
|
return DrunkenState.Tipsy;
|
|
return DrunkenState.Sober;
|
|
}
|
|
|
|
public uint GetDeathTimer() { return m_deathTimer; }
|
|
public bool ActivateTaxiPathTo(List<uint> nodes, Creature npc = null, uint spellid = 0, uint preferredMountDisplay = 0)
|
|
{
|
|
if (nodes.Count < 2)
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.NoSuchPath);
|
|
return false;
|
|
}
|
|
|
|
// not let cheating with start flight in time of logout process || while in combat || has type state: stunned || has type state: root
|
|
if (GetSession().isLogingOut() || IsInCombat() || HasUnitState(UnitState.Stunned) || HasUnitState(UnitState.Root))
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerBusy);
|
|
return false;
|
|
}
|
|
|
|
if (HasFlag(UnitFields.Flags, UnitFlags.RemoveClientControl))
|
|
return false;
|
|
|
|
// taximaster case
|
|
if (npc != null)
|
|
{
|
|
// not let cheating with start flight mounted
|
|
if (IsMounted())
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerAlreadyMounted);
|
|
return false;
|
|
}
|
|
|
|
if (IsInDisallowedMountForm())
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerShapeshifted);
|
|
return false;
|
|
}
|
|
|
|
// not let cheating with start flight in time of logout process || if casting not finished || while in combat || if not use Spell's with EffectSendTaxi
|
|
if (IsNonMeleeSpellCast(false))
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.PlayerBusy);
|
|
return false;
|
|
}
|
|
}
|
|
// cast case or scripted call case
|
|
else
|
|
{
|
|
RemoveAurasByType(AuraType.Mounted);
|
|
|
|
if (IsInDisallowedMountForm())
|
|
RemoveAurasByType(AuraType.ModShapeshift);
|
|
|
|
Spell spell = GetCurrentSpell(CurrentSpellTypes.Generic);
|
|
if (spell != null)
|
|
if (spell.m_spellInfo.Id != spellid)
|
|
InterruptSpell(CurrentSpellTypes.Generic, false);
|
|
|
|
InterruptSpell(CurrentSpellTypes.AutoRepeat, false);
|
|
|
|
spell = GetCurrentSpell(CurrentSpellTypes.Channeled);
|
|
if (spell != null)
|
|
if (spell.m_spellInfo.Id != spellid)
|
|
InterruptSpell(CurrentSpellTypes.Channeled, true);
|
|
}
|
|
|
|
uint sourcenode = nodes[0];
|
|
|
|
// starting node too far away (cheat?)
|
|
var node = CliDB.TaxiNodesStorage.LookupByKey(sourcenode);
|
|
if (node == null)
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.NoSuchPath);
|
|
return false;
|
|
}
|
|
|
|
// check node starting pos data set case if provided
|
|
if (node.Pos.X != 0.0f || node.Pos.Y != 0.0f || node.Pos.Z != 0.0f)
|
|
{
|
|
if (node.MapID != GetMapId() || !IsInDist(node.Pos.X, node.Pos.Y, node.Pos.Z, 2 * SharedConst.InteractionDistance))
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.TooFarAway);
|
|
return false;
|
|
}
|
|
}
|
|
// node must have pos if taxi master case (npc != NULL)
|
|
else if (npc != null)
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.UnspecifiedServerError);
|
|
return false;
|
|
}
|
|
|
|
// Prepare to flight start now
|
|
|
|
// stop combat at start taxi flight if any
|
|
CombatStop();
|
|
|
|
StopCastingCharm();
|
|
StopCastingBindSight();
|
|
ExitVehicle();
|
|
|
|
// stop trade (client cancel trade at taxi map open but cheating tools can be used for reopen it)
|
|
TradeCancel(true);
|
|
|
|
// clean not finished taxi path if any
|
|
m_taxi.ClearTaxiDestinations();
|
|
|
|
// 0 element current node
|
|
m_taxi.AddTaxiDestination(sourcenode);
|
|
|
|
// fill destinations path tail
|
|
uint sourcepath = 0;
|
|
uint totalcost = 0;
|
|
uint firstcost = 0;
|
|
|
|
uint prevnode = sourcenode;
|
|
uint lastnode = 0;
|
|
|
|
for (int i = 1; i < nodes.Count; ++i)
|
|
{
|
|
uint path, cost;
|
|
|
|
lastnode = nodes[i];
|
|
Global.ObjectMgr.GetTaxiPath(prevnode, lastnode, out path, out cost);
|
|
|
|
if (path == 0)
|
|
{
|
|
m_taxi.ClearTaxiDestinations();
|
|
return false;
|
|
}
|
|
|
|
totalcost += cost;
|
|
if (i == 1)
|
|
firstcost = cost;
|
|
|
|
if (prevnode == sourcenode)
|
|
sourcepath = path;
|
|
|
|
m_taxi.AddTaxiDestination(lastnode);
|
|
|
|
prevnode = lastnode;
|
|
}
|
|
|
|
// get mount model (in case non taximaster (npc == NULL) allow more wide lookup)
|
|
//
|
|
// Hack-Fix for Alliance not being able to use Acherus taxi. There is
|
|
// only one mount ID for both sides. Probably not good to use 315 in case DBC nodes
|
|
// change but I couldn't find a suitable alternative. OK to use class because only DK
|
|
// can use this taxi.
|
|
uint mount_display_id;
|
|
if (node.Flags.HasAnyFlag(TaxiNodeFlags.UseFavoriteMount) && preferredMountDisplay != 0)
|
|
mount_display_id = preferredMountDisplay;
|
|
else
|
|
mount_display_id = Global.ObjectMgr.GetTaxiMountDisplayId(sourcenode, GetTeam(), npc == null || (sourcenode == 315 && GetClass() == Class.Deathknight));
|
|
|
|
// in spell case allow 0 model
|
|
if ((mount_display_id == 0 && spellid == 0) || sourcepath == 0)
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.UnspecifiedServerError);
|
|
m_taxi.ClearTaxiDestinations();
|
|
return false;
|
|
}
|
|
|
|
ulong money = GetMoney();
|
|
if (npc != null)
|
|
{
|
|
float discount = GetReputationPriceDiscount(npc);
|
|
totalcost = (uint)Math.Ceiling(totalcost * discount);
|
|
firstcost = (uint)Math.Ceiling(firstcost * discount);
|
|
m_taxi.SetFlightMasterFactionTemplateId(npc.getFaction());
|
|
}
|
|
else
|
|
m_taxi.SetFlightMasterFactionTemplateId(0);
|
|
|
|
if (money < totalcost)
|
|
{
|
|
GetSession().SendActivateTaxiReply(ActivateTaxiReply.NotEnoughMoney);
|
|
m_taxi.ClearTaxiDestinations();
|
|
return false;
|
|
}
|
|
|
|
//Checks and preparations done, DO FLIGHT
|
|
UpdateCriteria(CriteriaTypes.FlightPathsTaken, 1);
|
|
|
|
if (WorldConfig.GetBoolValue(WorldCfg.InstantTaxi))
|
|
{
|
|
var lastPathNode = CliDB.TaxiNodesStorage.LookupByKey(nodes[nodes.Count - 1]);
|
|
m_taxi.ClearTaxiDestinations();
|
|
ModifyMoney(-totalcost);
|
|
UpdateCriteria(CriteriaTypes.GoldSpentForTravelling, totalcost);
|
|
TeleportTo(lastPathNode.MapID, lastPathNode.Pos.X, lastPathNode.Pos.Y, lastPathNode.Pos.Z, GetOrientation());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
ModifyMoney(-firstcost);
|
|
UpdateCriteria(CriteriaTypes.GoldSpentForTravelling, firstcost);
|
|
GetSession().SendActivateTaxiReply();
|
|
GetSession().SendDoFlight(mount_display_id, sourcepath);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool ActivateTaxiPathTo(uint taxi_path_id, uint spellid = 0)
|
|
{
|
|
var entry = CliDB.TaxiPathStorage.LookupByKey(taxi_path_id);
|
|
if (entry == null)
|
|
return false;
|
|
|
|
List<uint> nodes = new List<uint>();
|
|
|
|
nodes.Add(entry.From);
|
|
nodes.Add(entry.To);
|
|
|
|
return ActivateTaxiPathTo(nodes, null, spellid);
|
|
}
|
|
|
|
public void CleanupAfterTaxiFlight()
|
|
{
|
|
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
|
|
Dismount();
|
|
RemoveFlag(UnitFields.Flags, UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight);
|
|
getHostileRefManager().setOnlineOfflineState(true);
|
|
}
|
|
|
|
public void ContinueTaxiFlight()
|
|
{
|
|
uint sourceNode = m_taxi.GetTaxiSource();
|
|
if (sourceNode == 0)
|
|
return;
|
|
|
|
Log.outDebug(LogFilter.Unit, "WORLD: Restart character {0} taxi flight", GetGUID().ToString());
|
|
|
|
uint mountDisplayId = Global.ObjectMgr.GetTaxiMountDisplayId(sourceNode, GetTeam(), true);
|
|
if (mountDisplayId == 0)
|
|
return;
|
|
|
|
uint path = m_taxi.GetCurrentTaxiPath();
|
|
|
|
// search appropriate start path node
|
|
uint startNode = 0;
|
|
|
|
var nodeList = CliDB.TaxiPathNodesByPath[path];
|
|
|
|
float distPrev = MapConst.MapSize * MapConst.MapSize;
|
|
float distNext = GetExactDistSq(nodeList[0].Loc.X, nodeList[0].Loc.Y, nodeList[0].Loc.Z);
|
|
|
|
for (int i = 1; i < nodeList.Length; ++i)
|
|
{
|
|
var node = nodeList[i];
|
|
var prevNode = nodeList[i - 1];
|
|
|
|
// skip nodes at another map
|
|
if (node.MapID != GetMapId())
|
|
continue;
|
|
|
|
distPrev = distNext;
|
|
|
|
distNext = GetExactDistSq(node.Loc.X, node.Loc.Y, node.Loc.Z);
|
|
|
|
float distNodes =
|
|
(node.Loc.X - prevNode.Loc.X) * (node.Loc.X - prevNode.Loc.X) +
|
|
(node.Loc.Y - prevNode.Loc.Y) * (node.Loc.Y - prevNode.Loc.Y) +
|
|
(node.Loc.Z - prevNode.Loc.Z) * (node.Loc.Z - prevNode.Loc.Z);
|
|
|
|
if (distNext + distPrev < distNodes)
|
|
{
|
|
startNode = (uint)i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
GetSession().SendDoFlight(mountDisplayId, path, startNode);
|
|
}
|
|
|
|
public byte GetGrantableLevels() { return m_grantableLevels; }
|
|
public void SetGrantableLevels(int val) { m_grantableLevels = (byte)val; }
|
|
public bool GetsRecruitAFriendBonus(bool forXP)
|
|
{
|
|
bool recruitAFriend = false;
|
|
if (getLevel() <= WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel) || !forXP)
|
|
{
|
|
Group group = GetGroup();
|
|
if (group)
|
|
{
|
|
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
|
{
|
|
Player player = refe.GetSource();
|
|
if (!player)
|
|
continue;
|
|
|
|
if (!player.IsAtRecruitAFriendDistance(this))
|
|
continue; // member (alive or dead) or his corpse at req. distance
|
|
|
|
if (forXP)
|
|
{
|
|
// level must be allowed to get RaF bonus
|
|
if (player.getLevel() > WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevel))
|
|
continue;
|
|
|
|
// level difference must be small enough to get RaF bonus, UNLESS we are lower level
|
|
if (player.getLevel() < getLevel())
|
|
if (getLevel() - player.getLevel() > WorldConfig.GetIntValue(WorldCfg.MaxRecruitAFriendBonusPlayerLevelDifference))
|
|
continue;
|
|
}
|
|
|
|
bool ARecruitedB = (player.GetSession().GetRecruiterId() == GetSession().GetAccountId());
|
|
bool BRecruitedA = (GetSession().GetRecruiterId() == player.GetSession().GetAccountId());
|
|
if (ARecruitedB || BRecruitedA)
|
|
{
|
|
recruitAFriend = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return recruitAFriend;
|
|
}
|
|
|
|
bool IsAtRecruitAFriendDistance(WorldObject pOther)
|
|
{
|
|
if (!pOther)
|
|
return false;
|
|
WorldObject player = GetCorpse();
|
|
if (!player || IsAlive())
|
|
player = this;
|
|
|
|
if (player.GetMapId() != pOther.GetMapId() || player.GetInstanceId() != pOther.GetInstanceId())
|
|
return false;
|
|
|
|
return pOther.GetDistance(player) <= WorldConfig.GetFloatValue(WorldCfg.MaxRecruitAFriendDistance);
|
|
}
|
|
|
|
public bool IsBeingTeleported() { return mSemaphoreTeleport_Near || mSemaphoreTeleport_Far; }
|
|
public bool IsBeingTeleportedNear() { return mSemaphoreTeleport_Near; }
|
|
public bool IsBeingTeleportedFar() { return mSemaphoreTeleport_Far; }
|
|
public bool IsBeingTeleportedSeamlessly() { return IsBeingTeleportedFar() && m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless); }
|
|
public void SetSemaphoreTeleportNear(bool semphsetting) { mSemaphoreTeleport_Near = semphsetting; }
|
|
public void SetSemaphoreTeleportFar(bool semphsetting) { mSemaphoreTeleport_Far = semphsetting; }
|
|
|
|
//new
|
|
public uint DoRandomRoll(uint minimum, uint maximum)
|
|
{
|
|
Contract.Assert(maximum <= 10000);
|
|
|
|
uint roll = RandomHelper.URand(minimum, maximum);
|
|
|
|
RandomRoll randomRoll = new RandomRoll();
|
|
randomRoll.Min = (int)minimum;
|
|
randomRoll.Max = (int)maximum;
|
|
randomRoll.Result = (int)roll;
|
|
randomRoll.Roller = GetGUID();
|
|
randomRoll.RollerWowAccount = GetSession().GetAccountGUID();
|
|
|
|
Group group = GetGroup();
|
|
if (group)
|
|
group.BroadcastPacket(randomRoll, false);
|
|
else
|
|
SendPacket(randomRoll);
|
|
|
|
return roll;
|
|
}
|
|
|
|
public bool IsVisibleGloballyFor(Player u)
|
|
{
|
|
if (u == null)
|
|
return false;
|
|
|
|
// Always can see self
|
|
if (u.GetGUID() == GetGUID())
|
|
return true;
|
|
|
|
// Visible units, always are visible for all players
|
|
if (IsVisible())
|
|
return true;
|
|
|
|
// GMs are visible for higher gms (or players are visible for gms)
|
|
if (!Global.AccountMgr.IsPlayerAccount(u.GetSession().GetSecurity()))
|
|
return GetSession().GetSecurity() <= u.GetSession().GetSecurity();
|
|
|
|
// non faction visibility non-breakable for non-GMs
|
|
return false;
|
|
}
|
|
|
|
public float GetReputationPriceDiscount(Creature creature)
|
|
{
|
|
return GetReputationPriceDiscount(creature.GetFactionTemplateEntry());
|
|
}
|
|
|
|
public float GetReputationPriceDiscount(FactionTemplateRecord factionTemplate)
|
|
{
|
|
if (factionTemplate == null || factionTemplate.Faction == 0)
|
|
return 1.0f;
|
|
|
|
ReputationRank rank = GetReputationRank(factionTemplate.Faction);
|
|
if (rank <= ReputationRank.Neutral)
|
|
return 1.0f;
|
|
|
|
return 1.0f - 0.05f * (rank - ReputationRank.Neutral);
|
|
}
|
|
public bool IsSpellFitByClassAndRace(uint spell_id)
|
|
{
|
|
uint racemask = getRaceMask();
|
|
uint classmask = getClassMask();
|
|
|
|
var bounds = Global.SpellMgr.GetSkillLineAbilityMapBounds(spell_id);
|
|
|
|
if (bounds.Empty())
|
|
return true;
|
|
|
|
foreach (var _spell_idx in bounds)
|
|
{
|
|
// skip wrong race skills
|
|
if (_spell_idx.RaceMask != 0 && (_spell_idx.RaceMask & racemask) == 0)
|
|
continue;
|
|
|
|
// skip wrong class skills
|
|
if (_spell_idx.ClassMask != 0 && (_spell_idx.ClassMask & classmask) == 0)
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//New shit
|
|
void InitPrimaryProfessions()
|
|
{
|
|
SetFreePrimaryProfessions(WorldConfig.GetUIntValue(WorldCfg.MaxPrimaryTradeSkill));
|
|
}
|
|
public uint GetFreePrimaryProfessionPoints() { return GetUInt32Value(PlayerFields.CharacterPoints); }
|
|
void SetFreePrimaryProfessions(ushort profs) { SetUInt32Value(PlayerFields.CharacterPoints, profs); }
|
|
public bool HaveAtClient(WorldObject u)
|
|
{
|
|
bool one = u.GetGUID() == GetGUID();
|
|
bool two = m_clientGUIDs.Contains(u.GetGUID());
|
|
|
|
return one || two;
|
|
}
|
|
public bool HasTitle(CharTitlesRecord title) { return HasTitle(title.MaskID); }
|
|
public bool HasTitle(uint bitIndex)
|
|
{
|
|
if (bitIndex > PlayerConst.MaxTitleIndex)
|
|
return false;
|
|
|
|
int fieldIndexOffset = (int)bitIndex / 32;
|
|
uint flag = (uint)(1 << ((int)bitIndex % 32));
|
|
return HasFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag);
|
|
}
|
|
public void SetTitle(CharTitlesRecord title, bool lost = false)
|
|
{
|
|
int fieldIndexOffset = (int)title.MaskID / 32;
|
|
uint flag = (uint)(1 << (int)(title.MaskID % 32));
|
|
|
|
if (lost)
|
|
{
|
|
if (!HasFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag))
|
|
return;
|
|
|
|
RemoveFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag);
|
|
}
|
|
else
|
|
{
|
|
if (HasFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag))
|
|
return;
|
|
|
|
SetFlag(PlayerFields.KnownTitles + fieldIndexOffset, flag);
|
|
}
|
|
|
|
TitleEarned packet = new TitleEarned(lost ? ServerOpcodes.TitleLost : ServerOpcodes.TitleEarned);
|
|
packet.Index = title.MaskID;
|
|
SendPacket(packet);
|
|
}
|
|
|
|
public void SetViewpoint(WorldObject target, bool apply)
|
|
{
|
|
if (apply)
|
|
{
|
|
Log.outDebug(LogFilter.Maps, "Player.CreateViewpoint: Player {0} create seer {1} (TypeId: {2}).", GetName(), target.GetEntry(), target.GetTypeId());
|
|
|
|
if (!AddGuidValue(PlayerFields.Farsight, target.GetGUID()))
|
|
{
|
|
Log.outFatal(LogFilter.Player, "Player.CreateViewpoint: Player {0} cannot add new viewpoint!", GetName());
|
|
return;
|
|
|
|
}
|
|
|
|
// farsight dynobj or puppet may be very far away
|
|
UpdateVisibilityOf(target);
|
|
|
|
if (target.isTypeMask(TypeMask.Unit) && target != GetVehicleBase())
|
|
target.ToUnit().AddPlayerToVision(this);
|
|
SetSeer(target);
|
|
}
|
|
else
|
|
{
|
|
Log.outDebug(LogFilter.Maps, "Player.CreateViewpoint: Player {0} remove seer", GetName());
|
|
|
|
if (!RemoveGuidValue(PlayerFields.Farsight, target.GetGUID()))
|
|
{
|
|
Log.outFatal(LogFilter.Player, "Player.CreateViewpoint: Player {0} cannot remove current viewpoint!", GetName());
|
|
return;
|
|
}
|
|
|
|
if (target.isTypeMask(TypeMask.Unit) && target != GetVehicleBase())
|
|
target.ToUnit().RemovePlayerFromVision(this);
|
|
|
|
//must immediately set seer back otherwise may crash
|
|
SetSeer(this);
|
|
}
|
|
}
|
|
public WorldObject GetViewpoint()
|
|
{
|
|
ObjectGuid guid = GetGuidValue(PlayerFields.Farsight);
|
|
if (!guid.IsEmpty())
|
|
return Global.ObjAccessor.GetObjectByTypeMask(this, guid, TypeMask.Seer);
|
|
return null;
|
|
}
|
|
|
|
public void SetClientControl(Unit target, bool allowMove)
|
|
{
|
|
ControlUpdate packet = new ControlUpdate();
|
|
packet.Guid = target.GetGUID();
|
|
packet.On = allowMove;
|
|
SendPacket(packet);
|
|
|
|
if (this != target)
|
|
SetViewpoint(target, allowMove);
|
|
|
|
if (allowMove)
|
|
SetMover(target);
|
|
}
|
|
public void SetMover(Unit target)
|
|
{
|
|
m_unitMovedByMe.m_playerMovingMe = null;
|
|
m_unitMovedByMe = target;
|
|
m_unitMovedByMe.m_playerMovingMe = this;
|
|
|
|
MoveSetActiveMover packet = new MoveSetActiveMover();
|
|
packet.MoverGUID = target.GetGUID();
|
|
SendPacket(packet);
|
|
}
|
|
|
|
public Item GetWeaponForAttack(WeaponAttackType attackType, bool useable = false)
|
|
{
|
|
byte slot;
|
|
switch (attackType)
|
|
{
|
|
case WeaponAttackType.BaseAttack:
|
|
slot = EquipmentSlot.MainHand;
|
|
break;
|
|
case WeaponAttackType.OffAttack:
|
|
slot = EquipmentSlot.OffHand;
|
|
break;
|
|
case WeaponAttackType.RangedAttack:
|
|
slot = EquipmentSlot.MainHand;
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
Item item = null;
|
|
if (useable)
|
|
item = GetUseableItemByPos(InventorySlots.Bag0, slot);
|
|
else
|
|
item = GetItemByPos(InventorySlots.Bag0, slot);
|
|
if (item == null || item.GetTemplate().GetClass() != ItemClass.Weapon)
|
|
return null;
|
|
|
|
if (!useable)
|
|
return item;
|
|
|
|
if (item.IsBroken())
|
|
return null;
|
|
|
|
return item;
|
|
}
|
|
public static WeaponAttackType GetAttackBySlot(byte slot, InventoryType inventoryType)
|
|
{
|
|
switch (slot)
|
|
{
|
|
case EquipmentSlot.MainHand:
|
|
return inventoryType != InventoryType.Ranged && inventoryType != InventoryType.RangedRight ? WeaponAttackType.BaseAttack : WeaponAttackType.RangedAttack;
|
|
case EquipmentSlot.OffHand:
|
|
return WeaponAttackType.OffAttack;
|
|
default:
|
|
return WeaponAttackType.Max;
|
|
}
|
|
}
|
|
public void AutoUnequipOffhandIfNeed(bool force = false)
|
|
{
|
|
Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);
|
|
if (offItem == null)
|
|
return;
|
|
|
|
ItemTemplate offtemplate = offItem.GetTemplate();
|
|
|
|
// unequip offhand weapon if player doesn't have dual wield anymore
|
|
if (!CanDualWield() && ((offItem.GetTemplate().GetInventoryType() == InventoryType.WeaponOffhand && !offItem.GetTemplate().GetFlags3().HasAnyFlag(ItemFlags3.AlwaysAllowDualWield))
|
|
|| offItem.GetTemplate().GetInventoryType() == InventoryType.Weapon))
|
|
force = true;
|
|
|
|
// need unequip offhand for 2h-weapon without TitanGrip (in any from hands)
|
|
if (!force && (CanTitanGrip() || (offtemplate.GetInventoryType() != InventoryType.Weapon2Hand && !IsTwoHandUsed())))
|
|
return;
|
|
|
|
List<ItemPosCount> off_dest = new List<ItemPosCount>();
|
|
InventoryResult off_msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, off_dest, offItem, false);
|
|
if (off_msg == InventoryResult.Ok)
|
|
{
|
|
RemoveItem(InventorySlots.Bag0, EquipmentSlot.OffHand, true);
|
|
StoreItem(off_dest, offItem, true);
|
|
}
|
|
else
|
|
{
|
|
MoveItemFromInventory(InventorySlots.Bag0, EquipmentSlot.OffHand, true);
|
|
SQLTransaction trans = new SQLTransaction();
|
|
offItem.DeleteFromInventoryDB(trans); // deletes item from character's inventory
|
|
offItem.SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
|
|
|
|
string subject = Global.ObjectMgr.GetCypherString(CypherStrings.NotEquippedItem);
|
|
new MailDraft(subject, "There were problems with equipping one or several items").AddItem(offItem).SendMailTo(trans, this, new MailSender(this, MailStationery.Gm), MailCheckMask.Copied);
|
|
|
|
DB.Characters.CommitTransaction(trans);
|
|
}
|
|
}
|
|
|
|
public WorldLocation GetTeleportDest()
|
|
{
|
|
return teleportDest;
|
|
}
|
|
public WorldLocation GetHomebind()
|
|
{
|
|
return homebind;
|
|
}
|
|
public WorldLocation GetRecall()
|
|
{
|
|
return m_recall_location;
|
|
}
|
|
|
|
public bool CanTameExoticPets() { return IsGameMaster() || HasAuraType(AuraType.AllowTamePetType); }
|
|
|
|
void SendAttackSwingDeadTarget() { SendPacket(new AttackSwingError(AttackSwingErr.DeadTarget)); }
|
|
void SendAttackSwingCantAttack() { SendPacket(new AttackSwingError(AttackSwingErr.CantAttack)); }
|
|
public void SendAttackSwingNotInRange() { SendPacket(new AttackSwingError(AttackSwingErr.NotInRange)); }
|
|
void SendAttackSwingBadFacingAttack() { SendPacket(new AttackSwingError(AttackSwingErr.BadFacing)); }
|
|
public void SendAttackSwingCancelAttack() { SendPacket(new CancelCombat()); }
|
|
public void SendAutoRepeatCancel(Unit target)
|
|
{
|
|
CancelAutoRepeat cancelAutoRepeat = new CancelAutoRepeat();
|
|
cancelAutoRepeat.Guid = target.GetGUID(); // may be it's target guid
|
|
SendMessageToSet(cancelAutoRepeat, false);
|
|
}
|
|
|
|
public void SendUpdatePhasing()
|
|
{
|
|
if (!IsInWorld)
|
|
return;
|
|
|
|
RebuildTerrainSwaps(); // to set default map swaps
|
|
|
|
GetSession().SendSetPhaseShift(GetPhases(), GetTerrainSwaps(), GetWorldMapAreaSwaps());
|
|
}
|
|
|
|
public override void BuildCreateUpdateBlockForPlayer(UpdateData data, Player target)
|
|
{
|
|
if (target == this)
|
|
{
|
|
for (byte i = 0; i < EquipmentSlot.End; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].BuildCreateUpdateBlockForPlayer(data, target);
|
|
}
|
|
|
|
for (byte i = InventorySlots.BagStart; i < InventorySlots.BankBagEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].BuildCreateUpdateBlockForPlayer(data, target);
|
|
}
|
|
|
|
for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].BuildCreateUpdateBlockForPlayer(data, target);
|
|
}
|
|
|
|
for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i)
|
|
{
|
|
if (m_items[i] == null)
|
|
continue;
|
|
|
|
m_items[i].BuildCreateUpdateBlockForPlayer(data, target);
|
|
}
|
|
}
|
|
|
|
base.BuildCreateUpdateBlockForPlayer(data, target);
|
|
}
|
|
|
|
//Helpers
|
|
public void ADD_GOSSIP_ITEM(GossipOptionIcon icon, string message, uint sender, uint action) { PlayerTalkClass.GetGossipMenu().AddMenuItem(-1, icon, message, sender, action, "", 0); }
|
|
public void ADD_GOSSIP_ITEM_DB(uint menuId, uint menuItemId, uint sender, uint action) { PlayerTalkClass.GetGossipMenu().AddMenuItem(menuId, menuItemId, sender, action); }
|
|
public void ADD_GOSSIP_ITEM_EXTENDED(GossipOptionIcon icon, string message, uint sender, uint action, string boxmessage, uint boxmoney, bool coded) { PlayerTalkClass.GetGossipMenu().AddMenuItem(-1, icon, message, sender, action, boxmessage, boxmoney, coded); }
|
|
|
|
// This fuction Sends the current menu to show to client, a - NPCTEXTID(uint32), b - npc guid(uint64)
|
|
public void SEND_GOSSIP_MENU(uint titleId, ObjectGuid objGUID) { PlayerTalkClass.SendGossipMenu(titleId, objGUID); }
|
|
|
|
// Closes the Menu
|
|
public void CLOSE_GOSSIP_MENU() { PlayerTalkClass.SendCloseGossip(); }
|
|
}
|
|
}
|