mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-18 16:38:42 +01:00
*Backport some contents from TC2.
--HG-- branch : trunk
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -10,12 +10,12 @@
|
||||
*
|
||||
* 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
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "Common.h"
|
||||
@@ -74,6 +74,8 @@ Group::~Group()
|
||||
for(uint8 i = 0; i < TOTAL_DIFFICULTIES; i++)
|
||||
for(BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr)
|
||||
itr->second.save->RemoveGroup(this);
|
||||
|
||||
// Sub group counters clean up
|
||||
if (m_subGroupsCounts)
|
||||
delete[] m_subGroupsCounts;
|
||||
}
|
||||
@@ -302,8 +304,7 @@ uint32 Group::RemoveMember(const uint64 &guid, const uint8 &method)
|
||||
{
|
||||
bool leaderChanged = _removeMember(guid);
|
||||
|
||||
Player *player = objmgr.GetPlayer( guid ); // FG: TODO: could be removed, its just here for consistency
|
||||
if (player)
|
||||
if(Player *player = objmgr.GetPlayer( guid ))
|
||||
{
|
||||
WorldPacket data;
|
||||
|
||||
@@ -1048,6 +1049,7 @@ bool Group::_removeMember(const uint64 &guid)
|
||||
if (slot != m_memberSlots.end())
|
||||
{
|
||||
SubGroupCounterDecrease(slot->group);
|
||||
|
||||
m_memberSlots.erase(slot);
|
||||
}
|
||||
|
||||
@@ -1207,6 +1209,7 @@ void Group::ChangeMembersGroup(const uint64 &guid, const uint8 &group)
|
||||
if(!isRaidGroup())
|
||||
return;
|
||||
Player *player = objmgr.GetPlayer(guid);
|
||||
|
||||
if (!player)
|
||||
{
|
||||
uint8 prevSubGroup;
|
||||
@@ -1218,6 +1221,7 @@ void Group::ChangeMembersGroup(const uint64 &guid, const uint8 &group)
|
||||
SendUpdate();
|
||||
}
|
||||
else
|
||||
// This methods handles itself groupcounter decrease
|
||||
ChangeMembersGroup(player, group);
|
||||
}
|
||||
|
||||
@@ -1366,7 +1370,7 @@ uint32 Group::CanJoinBattleGroundQueue(uint32 bgTypeId, uint32 bgQueueType, uint
|
||||
void Roll::targetObjectBuildLink()
|
||||
{
|
||||
// called from link()
|
||||
this->getTarget()->addLootValidatorRef(this);
|
||||
getTarget()->addLootValidatorRef(this);
|
||||
}
|
||||
|
||||
void Group::SetDifficulty(uint8 difficulty)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -124,7 +124,6 @@ void WorldSession::HandleGroupInviteOpcode( WorldPacket & recv_data )
|
||||
SendPartyResult(PARTY_OP_INVITE, "", PARTY_RESULT_YOU_NOT_LEADER);
|
||||
return;
|
||||
}
|
||||
|
||||
// not have place
|
||||
if(group->IsFull())
|
||||
{
|
||||
@@ -203,7 +202,8 @@ void WorldSession::HandleGroupAcceptOpcode( WorldPacket & /*recv_data*/ )
|
||||
// forming a new group, create it
|
||||
if(!group->IsCreated())
|
||||
{
|
||||
if(leader) group->RemoveInvite(leader);
|
||||
if( leader )
|
||||
group->RemoveInvite(leader);
|
||||
group->Create(group->GetLeaderGUID(), group->GetLeaderName());
|
||||
objmgr.AddGroup(group);
|
||||
}
|
||||
@@ -533,6 +533,7 @@ void WorldSession::HandleGroupChangeSubGroupOpcode( WorldPacket & recv_data )
|
||||
{
|
||||
CHECK_PACKET_SIZE(recv_data,1+1);
|
||||
|
||||
// we will get correct pointer for group here, so we don't have to check if group is BG raid
|
||||
Group *group = GetPlayer()->GetGroup();
|
||||
if(!group)
|
||||
return;
|
||||
@@ -725,9 +726,6 @@ void WorldSession::BuildPartyMemberStatsChangedPacket(Player *player, WorldPacke
|
||||
uint32 updatedAura=player->GetUInt32Value(uint16(UNIT_FIELD_AURA + i));
|
||||
*data << uint16(updatedAura);
|
||||
*data << uint8(1);
|
||||
//TODO: find a safe place to do this cleanup
|
||||
//if(!updatedAura)
|
||||
//player->UnsetAuraUpdateMask(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -810,9 +808,6 @@ void WorldSession::BuildPartyMemberStatsChangedPacket(Player *player, WorldPacke
|
||||
uint32 updatedAura=pet->GetUInt32Value(uint16(UNIT_FIELD_AURA + i));
|
||||
*data << uint16(updatedAura);
|
||||
*data << uint8(1);
|
||||
//TODO: find a safe place to do this cleanup
|
||||
//if(!updatedAura)
|
||||
//pet->UnsetAuraUpdateMask(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -1212,18 +1212,19 @@ void Guild::LoadGuildBankFromDB()
|
||||
|
||||
delete result;
|
||||
|
||||
// 0 1 2 3
|
||||
result = CharacterDatabase.PQuery("SELECT TabId, SlotId, item_guid, item_entry FROM guild_bank_item WHERE guildid='%u' ORDER BY TabId", Id);
|
||||
// data needs to be at first place for Item::LoadFromDB
|
||||
// 0 1 2 3 4
|
||||
result = CharacterDatabase.PQuery("SELECT data, TabId, SlotId, item_guid, item_entry FROM guild_bank_item JOIN item_instance ON item_guid = guid WHERE guildid='%u' ORDER BY TabId", Id);
|
||||
if(!result)
|
||||
return;
|
||||
|
||||
do
|
||||
{
|
||||
Field *fields = result->Fetch();
|
||||
uint8 TabId = fields[0].GetUInt8();
|
||||
uint8 SlotId = fields[1].GetUInt8();
|
||||
uint32 ItemGuid = fields[2].GetUInt32();
|
||||
uint32 ItemEntry = fields[3].GetUInt32();
|
||||
uint8 TabId = fields[1].GetUInt8();
|
||||
uint8 SlotId = fields[2].GetUInt8();
|
||||
uint32 ItemGuid = fields[3].GetUInt32();
|
||||
uint32 ItemEntry = fields[4].GetUInt32();
|
||||
|
||||
if (TabId >= purchased_tabs || TabId >= GUILD_BANK_MAX_TABS)
|
||||
{
|
||||
@@ -1246,7 +1247,7 @@ void Guild::LoadGuildBankFromDB()
|
||||
}
|
||||
|
||||
Item *pItem = NewItemOrBag(proto);
|
||||
if(!pItem->LoadFromDB(ItemGuid, 0))
|
||||
if(!pItem->LoadFromDB(ItemGuid, 0, result))
|
||||
{
|
||||
CharacterDatabase.PExecute("DELETE FROM guild_bank_item WHERE guildid='%u' AND TabId='%u' AND SlotId='%u'", Id, uint32(TabId), uint32(SlotId));
|
||||
sLog.outError("Item GUID %u not found in item_instance, deleting from Guild Bank!", ItemGuid);
|
||||
|
||||
@@ -628,8 +628,8 @@ void Map::RelocationNotify()
|
||||
if(unit->m_Notified || !unit->IsInWorld() || unit->GetMapId() != GetId())
|
||||
continue;
|
||||
|
||||
unit->m_IsInNotifyList = false;
|
||||
unit->m_Notified = true;
|
||||
unit->m_IsInNotifyList = false;
|
||||
|
||||
if(unit->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
@@ -1480,8 +1480,8 @@ bool Map::CheckGridIntegrity(Creature* c, bool moved) const
|
||||
Cell xy_cell(xy_val);
|
||||
if(xy_cell != cur_cell)
|
||||
{
|
||||
sLog.outDebug("ERROR: %s (GUID: %u) X: %f Y: %f (%s) in grid[%u,%u]cell[%u,%u] instead grid[%u,%u]cell[%u,%u]",
|
||||
(c->GetTypeId()==TYPEID_PLAYER ? "Player" : "Creature"),c->GetGUIDLow(),
|
||||
sLog.outDebug("Creature (GUIDLow: %u) X: %f Y: %f (%s) in grid[%u,%u]cell[%u,%u] instead grid[%u,%u]cell[%u,%u]",
|
||||
c->GetGUIDLow(),
|
||||
c->GetPositionX(),c->GetPositionY(),(moved ? "final" : "original"),
|
||||
cur_cell.GridX(), cur_cell.GridY(), cur_cell.CellX(), cur_cell.CellY(),
|
||||
xy_cell.GridX(), xy_cell.GridY(), xy_cell.CellX(), xy_cell.CellY());
|
||||
@@ -2188,12 +2188,14 @@ void BattleGroundMap::UnloadAll()
|
||||
{
|
||||
while(HavePlayers())
|
||||
{
|
||||
Player * plr = m_mapRefManager.getFirst()->getSource();
|
||||
if(plr) (plr)->TeleportTo(plr->m_homebindMapId, plr->m_homebindX, plr->m_homebindY, plr->m_homebindZ, plr->GetOrientation());
|
||||
// TeleportTo removes the player from this map (if the map exists) -> calls BattleGroundMap::Remove -> invalidates the iterator.
|
||||
// just in case, remove the player from the list explicitly here as well to prevent a possible infinite loop
|
||||
// note that this remove is not needed if the code works well in other places
|
||||
plr->GetMapRef().unlink();
|
||||
if(Player * plr = m_mapRefManager.getFirst()->getSource())
|
||||
{
|
||||
plr->TeleportTo(plr->m_homebindMapId, plr->m_homebindX, plr->m_homebindY, plr->m_homebindZ, plr->GetOrientation());
|
||||
// TeleportTo removes the player from this map (if the map exists) -> calls BattleGroundMap::Remove -> invalidates the iterator.
|
||||
// just in case, remove the player from the list explicitly here as well to prevent a possible infinite loop
|
||||
// note that this remove is not needed if the code works well in other places
|
||||
plr->GetMapRef().unlink();
|
||||
}
|
||||
}
|
||||
|
||||
Map::UnloadAll();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -80,8 +80,8 @@ Object::Object( )
|
||||
|
||||
Object::~Object( )
|
||||
{
|
||||
if(m_objectUpdated)
|
||||
ObjectAccessor::Instance().RemoveUpdateObject(this);
|
||||
//if(m_objectUpdated)
|
||||
// ObjectAccessor::Instance().RemoveUpdateObject(this);
|
||||
|
||||
if(m_uint32Values)
|
||||
{
|
||||
@@ -89,9 +89,11 @@ Object::~Object( )
|
||||
{
|
||||
///- Do NOT call RemoveFromWorld here, if the object is a player it will crash
|
||||
sLog.outError("Object::~Object - guid="I64FMTD", typeid=%d deleted but still in world!!", GetGUID(), GetTypeId());
|
||||
//assert(0);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
assert(!m_objectUpdated);
|
||||
|
||||
//DEBUG_LOG("Object desctr 1 check (%p)",(void*)this);
|
||||
delete [] m_uint32Values;
|
||||
delete [] m_uint32Values_mirror;
|
||||
@@ -146,14 +148,8 @@ void Object::BuildCreateUpdateBlockForPlayer(UpdateData *data, Player *target) c
|
||||
|
||||
/** lower flag1 **/
|
||||
if(target == this) // building packet for oneself
|
||||
{
|
||||
flags |= UPDATEFLAG_SELF;
|
||||
|
||||
/*** temporary reverted - until real source of stack corruption will not found
|
||||
updatetype = UPDATETYPE_CREATE_OBJECT2;
|
||||
****/
|
||||
}
|
||||
|
||||
if(flags & UPDATEFLAG_HASPOSITION)
|
||||
{
|
||||
// UPDATETYPE_CREATE_OBJECT2 dynamic objects, corpses...
|
||||
@@ -571,7 +567,6 @@ void Object::_BuildValuesUpdate(uint8 updatetype, ByteBuffer * data, UpdateMask
|
||||
if( updateMask->GetBit( index ) )
|
||||
{
|
||||
// remove custom flag before send
|
||||
|
||||
if( index == UNIT_NPC_FLAGS )
|
||||
*data << uint32(m_uint32Values[ index ] & ~(UNIT_NPC_FLAG_GUARD + UNIT_NPC_FLAG_OUTDOORPVP));
|
||||
// FIXME: Some values at server stored in float format but must be sent to client in uint32 format
|
||||
@@ -1046,7 +1041,7 @@ void Object::RemoveByteFlag( uint16 index, uint8 offset, uint8 oldFlag )
|
||||
|
||||
bool Object::PrintIndexError(uint32 index, bool set) const
|
||||
{
|
||||
sLog.outError("ERROR: Attempt %s non-existed value field: %u (count: %u) for object typeid: %u type mask: %u",(set ? "set value to" : "get value from"),index,m_valuesCount,GetTypeId(),m_objectType);
|
||||
sLog.outError("Attempt %s non-existed value field: %u (count: %u) for object typeid: %u type mask: %u",(set ? "set value to" : "get value from"),index,m_valuesCount,GetTypeId(),m_objectType);
|
||||
|
||||
// assert must fail after function call
|
||||
return false;
|
||||
@@ -1130,7 +1125,7 @@ uint32 WorldObject::GetAreaId() const
|
||||
|
||||
InstanceData* WorldObject::GetInstanceData()
|
||||
{
|
||||
Map *map = MapManager::Instance().GetMap(m_mapId, this);
|
||||
Map *map = GetMap();
|
||||
return map->IsDungeon() ? ((InstanceMap*)map)->GetInstanceData() : NULL;
|
||||
}
|
||||
|
||||
@@ -1242,6 +1237,10 @@ float WorldObject::GetAngle( const float x, const float y ) const
|
||||
|
||||
bool WorldObject::HasInArc(const float arcangle, const WorldObject* obj) const
|
||||
{
|
||||
// always have self in arc
|
||||
if(obj == this)
|
||||
return true;
|
||||
|
||||
float arc = arcangle;
|
||||
|
||||
// move arc to range 0.. 2*pi
|
||||
@@ -1346,13 +1345,13 @@ void Object::ForceValuesUpdateAtIndex(uint32 i)
|
||||
{
|
||||
m_uint32Values_mirror[i] = GetUInt32Value(i) + 1; // makes server think the field changed
|
||||
if(m_inWorld)
|
||||
{
|
||||
{
|
||||
if(!m_objectUpdated)
|
||||
{
|
||||
{
|
||||
ObjectAccessor::Instance().AddUpdateObject(this);
|
||||
m_objectUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Trinity
|
||||
@@ -1733,18 +1732,20 @@ GameObject* WorldObject::SummonGameObject(uint32 entry, float x, float y, float
|
||||
{
|
||||
if(!IsInWorld())
|
||||
return NULL;
|
||||
Map * map = GetMap();
|
||||
if(!map)
|
||||
return NULL;
|
||||
|
||||
GameObjectInfo const* goinfo = objmgr.GetGameObjectInfo(entry);
|
||||
if(!goinfo)
|
||||
{
|
||||
sLog.outErrorDb("Gameobject template %u not found in database!", entry);
|
||||
return NULL;
|
||||
}
|
||||
Map *map = GetMap();
|
||||
GameObject *go = new GameObject();
|
||||
if(!go->Create(objmgr.GenerateLowGuid(HIGHGUID_GAMEOBJECT),entry,map,x,y,z,ang,rotation0,rotation1,rotation2,rotation3,100,1))
|
||||
{
|
||||
delete go;
|
||||
return NULL;
|
||||
}
|
||||
go->SetRespawnTime(respawnTime);
|
||||
if(GetTypeId()==TYPEID_PLAYER || GetTypeId()==TYPEID_UNIT) //not sure how to handle this
|
||||
((Unit*)this)->AddGameObject(go);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -123,6 +123,8 @@ class TRINITY_DLL_SPEC Object
|
||||
if(m_inWorld)
|
||||
return;
|
||||
|
||||
assert(m_uint32Values);
|
||||
|
||||
m_inWorld = true;
|
||||
|
||||
// synchronize values mirror with values array (changes will send in updatecreate opcode any way
|
||||
@@ -133,10 +135,10 @@ class TRINITY_DLL_SPEC Object
|
||||
if(!m_inWorld)
|
||||
return;
|
||||
|
||||
// if we remove from world then sending changes not required
|
||||
if(m_uint32Values)
|
||||
ClearUpdateMask(true);
|
||||
m_inWorld = false;
|
||||
|
||||
// if we remove from world then sending changes not required
|
||||
ClearUpdateMask(true);
|
||||
}
|
||||
|
||||
const uint64& GetGUID() const { return GetUInt64Value(0); }
|
||||
@@ -300,8 +302,6 @@ class TRINITY_DLL_SPEC Object
|
||||
|
||||
uint16 GetValuesCount() const { return m_valuesCount; }
|
||||
|
||||
void InitValues() { _InitValues(); }
|
||||
|
||||
virtual bool hasQuest(uint32 /* quest_id */) const { return false; }
|
||||
virtual bool hasInvolvedQuest(uint32 /* quest_id */) const { return false; }
|
||||
|
||||
@@ -419,7 +419,6 @@ class TRINITY_DLL_SPEC WorldObject : public Object
|
||||
void GetRandomPoint( float x, float y, float z, float distance, float &rand_x, float &rand_y, float &rand_z ) const;
|
||||
|
||||
void SetMapId(uint32 newMap) { m_mapId = newMap; }
|
||||
|
||||
uint32 GetMapId() const { return m_mapId; }
|
||||
|
||||
uint32 GetZoneId() const;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -197,9 +197,9 @@ Unit::Unit()
|
||||
m_ShapeShiftFormSpellId = 0;
|
||||
m_canModifyStats = false;
|
||||
|
||||
for (int i = 0; i < MAX_SPELL_IMMUNITY; i++)
|
||||
for (int i = 0; i < MAX_SPELL_IMMUNITY; ++i)
|
||||
m_spellImmune[i].clear();
|
||||
for (int i = 0; i < UNIT_MOD_END; i++)
|
||||
for (int i = 0; i < UNIT_MOD_END; ++i)
|
||||
{
|
||||
m_auraModifiersGroup[i][BASE_VALUE] = 0.0f;
|
||||
m_auraModifiersGroup[i][BASE_PCT] = 1.0f;
|
||||
@@ -214,7 +214,7 @@ Unit::Unit()
|
||||
m_weaponDamage[i][MINDAMAGE] = BASE_MINDAMAGE;
|
||||
m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE;
|
||||
}
|
||||
for (int i = 0; i < MAX_STATS; i++)
|
||||
for (int i = 0; i < MAX_STATS; ++i)
|
||||
m_createStats[i] = 0.0f;
|
||||
|
||||
m_attacking = NULL;
|
||||
@@ -260,6 +260,10 @@ Unit::~Unit()
|
||||
RemoveAllDynObjects();
|
||||
|
||||
if(m_charmInfo) delete m_charmInfo;
|
||||
|
||||
assert(!m_attacking);
|
||||
assert(m_attackers.empty());
|
||||
assert(m_sharedVision.empty());
|
||||
}
|
||||
|
||||
void Unit::Update( uint32 p_time )
|
||||
@@ -1109,419 +1113,6 @@ void Unit::CastSpell(GameObject *go, uint32 spellId, bool triggered, Item *castI
|
||||
spell->prepare(&targets, triggeredByAura);
|
||||
}
|
||||
|
||||
/*
|
||||
void Unit::DealFlatDamage(Unit *pVictim, SpellEntry const *spellInfo, uint32 *damage, CleanDamage *cleanDamage, bool *crit, bool isTriggeredSpell)
|
||||
{
|
||||
// TODO this in only generic way, check for exceptions
|
||||
DEBUG_LOG("DealFlatDamage (BEFORE) >> DMG:%u", *damage);
|
||||
|
||||
// Per-damage class calculation
|
||||
switch (spellInfo->DmgClass)
|
||||
{
|
||||
// Melee and Ranged Spells
|
||||
case SPELL_DAMAGE_CLASS_RANGED:
|
||||
case SPELL_DAMAGE_CLASS_MELEE:
|
||||
{
|
||||
// Calculate physical outcome
|
||||
MeleeHitOutcome outcome = RollPhysicalOutcomeAgainst(pVictim, BASE_ATTACK, spellInfo);
|
||||
|
||||
//Used to store the Hit Outcome
|
||||
cleanDamage->hitOutCome = outcome;
|
||||
|
||||
// Return miss/evade first (sends miss message)
|
||||
switch(outcome)
|
||||
{
|
||||
case MELEE_HIT_EVADE:
|
||||
{
|
||||
SendAttackStateUpdate(HITINFO_MISS, pVictim, 1, GetSpellSchoolMask(spellInfo), 0, 0,0,VICTIMSTATE_EVADES,0);
|
||||
*damage = 0;
|
||||
return;
|
||||
}
|
||||
case MELEE_HIT_MISS:
|
||||
{
|
||||
SendAttackStateUpdate(HITINFO_MISS, pVictim, 1, GetSpellSchoolMask(spellInfo), 0, 0,0,VICTIMSTATE_NORMAL,0);
|
||||
*damage = 0;
|
||||
|
||||
if(GetTypeId()== TYPEID_PLAYER)
|
||||
((Player*)this)->UpdateWeaponSkill(BASE_ATTACK);
|
||||
|
||||
CastMeleeProcDamageAndSpell(pVictim,0,GetSpellSchoolMask(spellInfo),BASE_ATTACK,MELEE_HIT_MISS,spellInfo,isTriggeredSpell);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hitinfo, Victimstate
|
||||
uint32 hitInfo = HITINFO_NORMALSWING;
|
||||
VictimState victimState = VICTIMSTATE_NORMAL;
|
||||
|
||||
// Physical Damage
|
||||
if ( GetSpellSchoolMask(spellInfo) & SPELL_SCHOOL_MASK_NORMAL )
|
||||
{
|
||||
// apply spellmod to Done damage
|
||||
if(Player* modOwner = GetSpellModOwner())
|
||||
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_DAMAGE, *damage);
|
||||
|
||||
//Calculate armor mitigation
|
||||
uint32 damageAfterArmor = CalcArmorReducedDamage(pVictim, *damage);
|
||||
|
||||
// random durability for main hand weapon (ABSORB)
|
||||
if(damageAfterArmor < *damage)
|
||||
if(pVictim->GetTypeId() == TYPEID_PLAYER)
|
||||
if (roll_chance_f(sWorld.getRate(RATE_DURABILITY_LOSS_ABSORB)))
|
||||
((Player*)pVictim)->DurabilityPointLossForEquipSlot(EquipmentSlots(urand(EQUIPMENT_SLOT_START,EQUIPMENT_SLOT_BACK)));
|
||||
|
||||
cleanDamage->damage += *damage - damageAfterArmor;
|
||||
*damage = damageAfterArmor;
|
||||
}
|
||||
// Magical Damage
|
||||
else
|
||||
{
|
||||
// Calculate damage bonus
|
||||
*damage = SpellDamageBonus(pVictim, spellInfo, *damage, SPELL_DIRECT_DAMAGE);
|
||||
}
|
||||
|
||||
// Classify outcome
|
||||
switch (outcome)
|
||||
{
|
||||
case MELEE_HIT_BLOCK_CRIT:
|
||||
case MELEE_HIT_CRIT:
|
||||
{
|
||||
uint32 bonusDmg = *damage;
|
||||
|
||||
// Apply crit_damage bonus
|
||||
if(Player* modOwner = GetSpellModOwner())
|
||||
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CRIT_DAMAGE_BONUS, bonusDmg);
|
||||
|
||||
uint32 creatureTypeMask = pVictim->GetCreatureTypeMask();
|
||||
AuraList const& mDamageDoneVersus = GetAurasByType(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS);
|
||||
for(AuraList::const_iterator i = mDamageDoneVersus.begin();i != mDamageDoneVersus.end(); ++i)
|
||||
if(creatureTypeMask & uint32((*i)->GetModifier()->m_miscvalue))
|
||||
bonusDmg = uint32(bonusDmg * ((*i)->GetModifierValue()+100.0f)/100.0f);
|
||||
|
||||
*damage += bonusDmg;
|
||||
|
||||
// Resilience - reduce crit damage
|
||||
if (pVictim->GetTypeId()==TYPEID_PLAYER)
|
||||
{
|
||||
uint32 resilienceReduction = ((Player*)pVictim)->GetMeleeCritDamageReduction(*damage);
|
||||
cleanDamage->damage += resilienceReduction;
|
||||
*damage -= resilienceReduction;
|
||||
}
|
||||
|
||||
*crit = true;
|
||||
hitInfo |= HITINFO_CRITICALHIT;
|
||||
|
||||
ModifyAuraState(AURA_STATE_CRIT, true);
|
||||
StartReactiveTimer( REACTIVE_CRIT );
|
||||
|
||||
if(getClass()==CLASS_HUNTER)
|
||||
{
|
||||
ModifyAuraState(AURA_STATE_HUNTER_CRIT_STRIKE, true);
|
||||
StartReactiveTimer( REACTIVE_HUNTER_CRIT );
|
||||
}
|
||||
|
||||
if ( outcome == MELEE_HIT_BLOCK_CRIT )
|
||||
{
|
||||
uint32 blocked_amount = uint32(pVictim->GetShieldBlockValue());
|
||||
if (blocked_amount >= *damage)
|
||||
{
|
||||
hitInfo |= HITINFO_SWINGNOHITSOUND;
|
||||
victimState = VICTIMSTATE_BLOCKS;
|
||||
cleanDamage->damage += *damage; // To Help Calculate Rage
|
||||
*damage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// To Help Calculate Rage
|
||||
cleanDamage->damage += blocked_amount;
|
||||
*damage = *damage - blocked_amount;
|
||||
}
|
||||
|
||||
pVictim->ModifyAuraState(AURA_STATE_DEFENSE, true);
|
||||
pVictim->StartReactiveTimer( REACTIVE_DEFENSE );
|
||||
|
||||
if(pVictim->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
// Update defense
|
||||
((Player*)pVictim)->UpdateDefense();
|
||||
|
||||
// random durability for main hand weapon (BLOCK)
|
||||
if (roll_chance_f(sWorld.getRate(RATE_DURABILITY_LOSS_BLOCK)))
|
||||
((Player*)pVictim)->DurabilityPointLossForEquipSlot(EQUIPMENT_SLOT_OFFHAND);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MELEE_HIT_PARRY:
|
||||
{
|
||||
cleanDamage->damage += *damage; // To Help Calculate Rage
|
||||
*damage = 0;
|
||||
victimState = VICTIMSTATE_PARRY;
|
||||
|
||||
// Counter-attack ( explained in Unit::DoAttackDamage() )
|
||||
if(pVictim->GetTypeId()==TYPEID_PLAYER || !(((Creature*)pVictim)->GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN) )
|
||||
{
|
||||
// Get attack timers
|
||||
float offtime = float(pVictim->getAttackTimer(OFF_ATTACK));
|
||||
float basetime = float(pVictim->getAttackTimer(BASE_ATTACK));
|
||||
|
||||
// Reduce attack time
|
||||
if (pVictim->haveOffhandWeapon() && offtime < basetime)
|
||||
{
|
||||
float percent20 = pVictim->GetAttackTime(OFF_ATTACK) * 0.20;
|
||||
float percent60 = 3 * percent20;
|
||||
if(offtime > percent20 && offtime <= percent60)
|
||||
{
|
||||
pVictim->setAttackTimer(OFF_ATTACK, uint32(percent20));
|
||||
}
|
||||
else if(offtime > percent60)
|
||||
{
|
||||
offtime -= 2 * percent20;
|
||||
pVictim->setAttackTimer(OFF_ATTACK, uint32(offtime));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float percent20 = pVictim->GetAttackTime(BASE_ATTACK) * 0.20;
|
||||
float percent60 = 3 * percent20;
|
||||
if(basetime > percent20 && basetime <= percent60)
|
||||
{
|
||||
pVictim->setAttackTimer(BASE_ATTACK, uint32(percent20));
|
||||
}
|
||||
else if(basetime > percent60)
|
||||
{
|
||||
basetime -= 2 * percent20;
|
||||
pVictim->setAttackTimer(BASE_ATTACK, uint32(basetime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(pVictim->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
// Update victim defense ?
|
||||
((Player*)pVictim)->UpdateDefense();
|
||||
|
||||
// random durability for main hand weapon (PARRY)
|
||||
if (roll_chance_f(sWorld.getRate(RATE_DURABILITY_LOSS_PARRY)))
|
||||
((Player*)pVictim)->DurabilityPointLossForEquipSlot(EQUIPMENT_SLOT_MAINHAND);
|
||||
}
|
||||
|
||||
// Set parry flags
|
||||
pVictim->HandleEmoteCommand(EMOTE_ONESHOT_PARRYUNARMED);
|
||||
|
||||
// Mongoose bite - set only Counterattack here
|
||||
if (pVictim->getClass() == CLASS_HUNTER)
|
||||
{
|
||||
pVictim->ModifyAuraState(AURA_STATE_HUNTER_PARRY,true);
|
||||
pVictim->StartReactiveTimer( REACTIVE_HUNTER_PARRY );
|
||||
}
|
||||
else
|
||||
{
|
||||
pVictim->ModifyAuraState(AURA_STATE_DEFENSE, true);
|
||||
pVictim->StartReactiveTimer( REACTIVE_DEFENSE );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MELEE_HIT_DODGE:
|
||||
{
|
||||
if(pVictim->GetTypeId() == TYPEID_PLAYER)
|
||||
((Player*)pVictim)->UpdateDefense();
|
||||
|
||||
cleanDamage->damage += *damage; // To Help Calculate Rage
|
||||
*damage = 0;
|
||||
hitInfo |= HITINFO_SWINGNOHITSOUND;
|
||||
victimState = VICTIMSTATE_DODGE;
|
||||
|
||||
// Set dodge flags
|
||||
pVictim->HandleEmoteCommand(EMOTE_ONESHOT_PARRYUNARMED);
|
||||
|
||||
// Overpower
|
||||
if (GetTypeId() == TYPEID_PLAYER && getClass() == CLASS_WARRIOR)
|
||||
{
|
||||
((Player*)this)->AddComboPoints(pVictim, 1);
|
||||
StartReactiveTimer( REACTIVE_OVERPOWER );
|
||||
}
|
||||
|
||||
// Riposte
|
||||
if (pVictim->getClass() != CLASS_ROGUE)
|
||||
{
|
||||
pVictim->ModifyAuraState(AURA_STATE_DEFENSE, true);
|
||||
pVictim->StartReactiveTimer( REACTIVE_DEFENSE );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MELEE_HIT_BLOCK:
|
||||
{
|
||||
uint32 blocked_amount = uint32(pVictim->GetShieldBlockValue());
|
||||
if (blocked_amount >= *damage)
|
||||
{
|
||||
hitInfo |= HITINFO_SWINGNOHITSOUND;
|
||||
victimState = VICTIMSTATE_BLOCKS;
|
||||
cleanDamage->damage += *damage; // To Help Calculate Rage
|
||||
*damage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// To Help Calculate Rage
|
||||
cleanDamage->damage += blocked_amount;
|
||||
*damage = *damage - blocked_amount;
|
||||
}
|
||||
|
||||
pVictim->ModifyAuraState(AURA_STATE_DEFENSE, true);
|
||||
pVictim->StartReactiveTimer( REACTIVE_DEFENSE );
|
||||
|
||||
if(pVictim->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
// Update defense
|
||||
((Player*)pVictim)->UpdateDefense();
|
||||
|
||||
// random durability for main hand weapon (BLOCK)
|
||||
if (roll_chance_f(sWorld.getRate(RATE_DURABILITY_LOSS_BLOCK)))
|
||||
((Player*)pVictim)->DurabilityPointLossForEquipSlot(EQUIPMENT_SLOT_OFFHAND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MELEE_HIT_EVADE: // already processed early
|
||||
case MELEE_HIT_MISS: // already processed early
|
||||
case MELEE_HIT_GLANCING:
|
||||
case MELEE_HIT_CRUSHING:
|
||||
case MELEE_HIT_NORMAL:
|
||||
break;
|
||||
}
|
||||
|
||||
// do all damage=0 cases here
|
||||
if(*damage == 0)
|
||||
CastMeleeProcDamageAndSpell(pVictim,0,GetSpellSchoolMask(spellInfo),BASE_ATTACK,outcome,spellInfo,isTriggeredSpell);
|
||||
|
||||
break;
|
||||
}
|
||||
// Magical Attacks
|
||||
case SPELL_DAMAGE_CLASS_NONE:
|
||||
case SPELL_DAMAGE_CLASS_MAGIC:
|
||||
{
|
||||
// Calculate damage bonus
|
||||
*damage = SpellDamageBonus(pVictim, spellInfo, *damage, SPELL_DIRECT_DAMAGE);
|
||||
|
||||
*crit = isSpellCrit(pVictim, spellInfo, GetSpellSchoolMask(spellInfo), BASE_ATTACK);
|
||||
if (*crit)
|
||||
{
|
||||
*damage = SpellCriticalBonus(spellInfo, *damage, pVictim);
|
||||
|
||||
// Resilience - reduce crit damage
|
||||
if (pVictim && pVictim->GetTypeId()==TYPEID_PLAYER)
|
||||
{
|
||||
uint32 damage_reduction = ((Player *)pVictim)->GetSpellCritDamageReduction(*damage);
|
||||
if(*damage > damage_reduction)
|
||||
*damage -= damage_reduction;
|
||||
else
|
||||
*damage = 0;
|
||||
}
|
||||
|
||||
cleanDamage->hitOutCome = MELEE_HIT_CRIT;
|
||||
}
|
||||
// spell proc all magic damage==0 case in this function
|
||||
if(*damage == 0)
|
||||
{
|
||||
// Procflags
|
||||
uint32 procAttacker = PROC_FLAG_HIT_SPELL;
|
||||
uint32 procVictim = (PROC_FLAG_STRUCK_SPELL|PROC_FLAG_TAKE_DAMAGE);
|
||||
|
||||
ProcDamageAndSpell(pVictim, procAttacker, procVictim, 0, GetSpellSchoolMask(spellInfo), spellInfo, isTriggeredSpell);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this in only generic way, check for exceptions
|
||||
DEBUG_LOG("DealFlatDamage (AFTER) >> DMG:%u", *damage);
|
||||
}
|
||||
|
||||
uint32 Unit::SpellNonMeleeDamageLog(Unit *pVictim, uint32 spellID, uint32 damage, bool isTriggeredSpell, bool useSpellDamage)
|
||||
{
|
||||
if(!this || !pVictim)
|
||||
return 0;
|
||||
if(!isAlive() || !pVictim->isAlive())
|
||||
return 0;
|
||||
|
||||
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID);
|
||||
if(!spellInfo)
|
||||
return 0;
|
||||
|
||||
CleanDamage cleanDamage = CleanDamage(0, BASE_ATTACK, MELEE_HIT_NORMAL);
|
||||
bool crit = false;
|
||||
|
||||
if (useSpellDamage)
|
||||
DealFlatDamage(pVictim, spellInfo, &damage, &cleanDamage, &crit, isTriggeredSpell);
|
||||
|
||||
// If we actually dealt some damage (spell proc's for 0 damage (normal and magic) called in DealFlatDamage)
|
||||
if(damage > 0)
|
||||
{
|
||||
// Calculate absorb & resists
|
||||
uint32 absorb = 0;
|
||||
uint32 resist = 0;
|
||||
|
||||
CalcAbsorbResist(pVictim,GetSpellSchoolMask(spellInfo), SPELL_DIRECT_DAMAGE, damage, &absorb, &resist);
|
||||
|
||||
//No more damage left, target absorbed and/or resisted all damage
|
||||
if (damage > absorb + resist)
|
||||
damage -= absorb + resist; //Remove Absorbed and Resisted from damage actually dealt
|
||||
else
|
||||
{
|
||||
uint32 HitInfo = HITINFO_SWINGNOHITSOUND;
|
||||
|
||||
if (absorb)
|
||||
HitInfo |= HITINFO_ABSORB;
|
||||
if (resist)
|
||||
{
|
||||
HitInfo |= HITINFO_RESIST;
|
||||
ProcDamageAndSpell(pVictim, PROC_FLAG_TARGET_RESISTS, PROC_FLAG_RESIST_SPELL, 0, GetSpellSchoolMask(spellInfo), spellInfo,isTriggeredSpell);
|
||||
}
|
||||
|
||||
//Send resist
|
||||
SendAttackStateUpdate(HitInfo, pVictim, 1, GetSpellSchoolMask(spellInfo), damage, absorb,resist,VICTIMSTATE_NORMAL,0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Deal damage done
|
||||
damage = DealDamage(pVictim, damage, &cleanDamage, SPELL_DIRECT_DAMAGE, GetSpellSchoolMask(spellInfo), spellInfo, true);
|
||||
|
||||
// Send damage log
|
||||
sLog.outDetail("SpellNonMeleeDamageLog: %u (TypeId: %u) attacked %u (TypeId: %u) for %u dmg inflicted by %u,absorb is %u,resist is %u",
|
||||
GetGUIDLow(), GetTypeId(), pVictim->GetGUIDLow(), pVictim->GetTypeId(), damage, spellID, absorb,resist);
|
||||
|
||||
// Actual log sent to client
|
||||
SendSpellNonMeleeDamageLog(pVictim, spellID, damage + resist, GetSpellSchoolMask(spellInfo), absorb, resist, false, 0, crit);
|
||||
|
||||
// Procflags
|
||||
uint32 procAttacker = PROC_FLAG_HIT_SPELL;
|
||||
uint32 procVictim = (PROC_FLAG_STRUCK_SPELL|PROC_FLAG_TAKE_DAMAGE);
|
||||
|
||||
if (crit)
|
||||
{
|
||||
procAttacker |= PROC_FLAG_CRIT_SPELL;
|
||||
procVictim |= PROC_FLAG_STRUCK_CRIT_SPELL;
|
||||
}
|
||||
|
||||
ProcDamageAndSpell(pVictim, procAttacker, procVictim, damage, GetSpellSchoolMask(spellInfo), spellInfo, isTriggeredSpell);
|
||||
|
||||
return damage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// all spell proc for 0 normal and magic damage called in DealFlatDamage
|
||||
|
||||
//Check for rage
|
||||
if(cleanDamage.damage)
|
||||
// Rage from damage received.
|
||||
if(pVictim->GetTypeId() == TYPEID_PLAYER && (pVictim->getPowerType() == POWER_RAGE))
|
||||
((Player*)pVictim)->RewardRage(cleanDamage.damage, 0, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Obsolete func need remove, here only for comotability vs another patches
|
||||
uint32 Unit::SpellNonMeleeDamageLog(Unit *pVictim, uint32 spellID, uint32 damage, bool isTriggeredSpell, bool useSpellDamage)
|
||||
{
|
||||
@@ -11255,11 +10846,6 @@ uint32 Unit::GetCreatePowers( Powers power ) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Unit::AddToWorld()
|
||||
{
|
||||
WorldObject::AddToWorld();
|
||||
}
|
||||
|
||||
void Unit::RemoveFromWorld()
|
||||
{
|
||||
// cleanup
|
||||
@@ -11274,20 +10860,23 @@ void Unit::RemoveFromWorld()
|
||||
|
||||
void Unit::CleanupsBeforeDelete()
|
||||
{
|
||||
if(m_uint32Values) // only for fully created object
|
||||
{
|
||||
RemoveAllAuras();
|
||||
InterruptNonMeleeSpells(true);
|
||||
m_Events.KillAllEvents(false); // non-delatable (currently casted spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
|
||||
CombatStop();
|
||||
ClearComboPointHolders();
|
||||
DeleteThreatList();
|
||||
getHostilRefManager().setOnlineOfflineState(false);
|
||||
RemoveAllGameObjects();
|
||||
RemoveAllDynObjects();
|
||||
GetMotionMaster()->Clear(false); // remove different non-standard movement generators.
|
||||
}
|
||||
RemoveFromWorld();
|
||||
assert(m_uint32Values);
|
||||
|
||||
//A unit may be in removelist and not in world, but it is still in grid
|
||||
//and may have some references during delete
|
||||
RemoveAllAuras();
|
||||
InterruptNonMeleeSpells(true);
|
||||
m_Events.KillAllEvents(false); // non-delatable (currently casted spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
|
||||
CombatStop();
|
||||
ClearComboPointHolders();
|
||||
DeleteThreatList();
|
||||
getHostilRefManager().setOnlineOfflineState(false);
|
||||
RemoveAllGameObjects();
|
||||
RemoveAllDynObjects();
|
||||
GetMotionMaster()->Clear(false); // remove different non-standard movement generators.
|
||||
|
||||
if(IsInWorld())
|
||||
RemoveFromWorld();
|
||||
}
|
||||
|
||||
void Unit::UpdateCharmAI()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* 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
|
||||
@@ -22,12 +22,11 @@
|
||||
\ingroup u2w
|
||||
*/
|
||||
|
||||
#include "WorldSocket.h"
|
||||
#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
|
||||
#include "Common.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "Opcodes.h"
|
||||
#include "WorldSocket.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include "Player.h"
|
||||
@@ -374,7 +373,7 @@ void WorldSession::LogoutPlayer(bool Save)
|
||||
// the player may not be in the world when logging out
|
||||
// e.g if he got disconnected during a transfer to another map
|
||||
// calls to GetMap in this case may cause crashes
|
||||
if(_player->IsInWorld()) MapManager::Instance().GetMap(_player->GetMapId(), _player)->Remove(_player, false);
|
||||
if(_player->IsInWorld()) _player->GetMap()->Remove(_player, false);
|
||||
// RemoveFromWorld does cleanup that requires the player to be in the accessor
|
||||
ObjectAccessor::Instance().RemoveObject(_player);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user