mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
6578 lines
166 KiB
C++
6578 lines
166 KiB
C++
|
|
#include "StdAfx.h"
|
|
#include "WeenieObject.h"
|
|
#include "PhysicsObj.h"
|
|
#include "ObjectMsgs.h"
|
|
#include "World.h"
|
|
#include "Lifestone.h"
|
|
#include "UseManager.h"
|
|
#include "SpellcastingManager.h"
|
|
#include "AttackManager.h"
|
|
#include "WeenieFactory.h"
|
|
|
|
#include "BinaryWriter.h"
|
|
#include "ChatMsgs.h"
|
|
|
|
#include "Player.h"
|
|
#include "Client.h"
|
|
#include "ClientCommands.h"
|
|
#include "MonsterAI.h"
|
|
#include "AllegianceManager.h"
|
|
#include "SpellcastingManager.h"
|
|
#include "GameEventManager.h"
|
|
#include "EmoteManager.h"
|
|
#include "WorldLandBlock.h"
|
|
#include "InferredPortalData.h"
|
|
#include "MovementManager.h"
|
|
#include "Movement.h"
|
|
#include "WClassID.h"
|
|
#include "InferredPortalData.h"
|
|
#include "Config.h"
|
|
#include "House.h"
|
|
|
|
CWeenieObject::CWeenieObject()
|
|
{
|
|
SetID(0);
|
|
|
|
SetName("Weenie");
|
|
|
|
m_Qualities.SetBool(ATTACKABLE_BOOL, TRUE);
|
|
m_Qualities.SetBool(STUCK_BOOL, FALSE);
|
|
|
|
SetSetupID(0x0200026B);
|
|
|
|
m_Qualities.id = 0x0001;
|
|
SetIcon(0x06001036);
|
|
SetItemType(TYPE_GEM);
|
|
|
|
weenie_obj = GetPointer<CWeenieObject>();
|
|
|
|
_last_update_pos = Timer::cur_time;
|
|
|
|
InitPhysicsTemporary();
|
|
}
|
|
|
|
CWeenieObject::~CWeenieObject()
|
|
{
|
|
DEBUG_DATA << "Deleted object: " << GetName();
|
|
WINLOG(Data, Normal, (std::string("Deleted object: ") + GetName() + "\n").c_str());
|
|
|
|
CleanupPhysicsTemporary();
|
|
|
|
SafeDelete(m_UseManager);
|
|
SafeDelete(m_SpellcastingManager);
|
|
SafeDelete(m_EmoteManager);
|
|
SafeDelete(m_AttackManager);
|
|
|
|
if (_combatTable)
|
|
{
|
|
CombatManeuverTable::Release(_combatTable);
|
|
_combatTable = NULL;
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::InitPhysicsObj()
|
|
{
|
|
if (_phys_obj.lock())
|
|
{
|
|
// already initialized
|
|
return;
|
|
}
|
|
|
|
DWORD setupID = 0;
|
|
m_Qualities.InqDataID(SETUP_DID, setupID);
|
|
|
|
#if 0
|
|
_phys_obj = CPhysicsObj::makeObject(setupID, GetID(), TRUE);
|
|
|
|
if (!_phys_obj.lock())
|
|
return;
|
|
#else
|
|
_phys_obj = GetPointer<CWeenieObject>();
|
|
|
|
int physicsState = 0;
|
|
if (m_Qualities.InqInt(PHYSICS_STATE_INT, physicsState))
|
|
{
|
|
m_PhysicsState = physicsState;
|
|
}
|
|
|
|
if (!InitObjectBegin(GetID(), TRUE))
|
|
{
|
|
SERVER_ERROR << "Failed creating physics object!";
|
|
}
|
|
|
|
|
|
|
|
BOOL bCreateAtAll = FALSE;
|
|
BOOL bCreateParts = FALSE;
|
|
|
|
#if PHATSDK_IS_SERVER
|
|
DWORD dataType = setupID & 0xFF000000;
|
|
|
|
if (dataType == 0x01000000)
|
|
{
|
|
CGfxObj *object = CGfxObj::Get(setupID);
|
|
|
|
if (object)
|
|
{
|
|
bCreateAtAll = TRUE;
|
|
|
|
if (object->physics_bsp)
|
|
{
|
|
bCreateParts = TRUE;
|
|
}
|
|
|
|
CGfxObj::Release(object);
|
|
}
|
|
}
|
|
else if (dataType == 0x02000000)
|
|
{
|
|
CSetup *object = CSetup::Get(setupID);
|
|
|
|
if (object)
|
|
{
|
|
bCreateAtAll = TRUE;
|
|
|
|
if (object->has_physics_bsp)
|
|
{
|
|
bCreateParts = TRUE;
|
|
}
|
|
|
|
CSetup::Release(object);
|
|
}
|
|
}
|
|
#else
|
|
bCreateParts = TRUE;
|
|
#endif
|
|
|
|
if (!InitPartArrayObject(setupID, bCreateParts))
|
|
{
|
|
SERVER_ERROR << "Failed creating parts array for physics object with setup" << setupID;
|
|
}
|
|
|
|
InitObjectEnd(); // SetPlacementFrameInternal(0x65);
|
|
#endif
|
|
|
|
DWORD motionTableDID = 0;
|
|
if (m_Qualities.InqDataID(MOTION_TABLE_DID, motionTableDID) && motionTableDID)
|
|
{
|
|
_phys_obj.lock()->SetMotionTableID(motionTableDID);
|
|
}
|
|
|
|
/*
|
|
int physicsState = 0;
|
|
if (m_Qualities.InqInt(PHYSICS_STATE_INT, physicsState))
|
|
{
|
|
_phys_obj.lock()->set_state(physicsState, FALSE);
|
|
CacheHasPhysicsBSP();
|
|
}
|
|
*/
|
|
|
|
CacheHasPhysicsBSP();
|
|
|
|
Position initialPosition;
|
|
if (AsPlayer() && m_Qualities.InqPosition(LOCATION_POSITION, initialPosition) && initialPosition.objcell_id)
|
|
{
|
|
m_Position = initialPosition;
|
|
}
|
|
else if (m_Qualities.InqPosition(INSTANTIATION_POSITION, initialPosition))
|
|
{
|
|
m_Position = initialPosition;
|
|
}
|
|
|
|
double defaultScale = 1.0;
|
|
if (m_Qualities.InqFloat(DEFAULT_SCALE_FLOAT, defaultScale))
|
|
{
|
|
_phys_obj.lock()->SetScaleStatic(defaultScale);
|
|
}
|
|
|
|
double fTranslucency = 1.0;
|
|
if (m_Qualities.InqFloat(TRANSLUCENCY_FLOAT, fTranslucency))
|
|
{
|
|
_phys_obj.lock()->SetTranslucencyInternal(fTranslucency);
|
|
}
|
|
|
|
double fFriction = 0.95f;
|
|
if (m_Qualities.InqFloat(FRICTION_FLOAT, fFriction))
|
|
{
|
|
_phys_obj.lock()->m_fFriction = fFriction;
|
|
}
|
|
|
|
double fElasticity = 0.05f;
|
|
if (m_Qualities.InqFloat(ELASTICITY_FLOAT, fElasticity))
|
|
{
|
|
_phys_obj.lock()->m_fElasticity = fElasticity;
|
|
}
|
|
|
|
int placement_id = 0;
|
|
if (m_Qualities.InqInt(PLACEMENT_POSITION_INT, placement_id, TRUE))
|
|
{
|
|
_phys_obj.lock()->SetPlacementFrame(placement_id, FALSE);
|
|
}
|
|
|
|
if (DWORD combatTable = m_Qualities.GetDID(COMBAT_TABLE_DID, 0))
|
|
{
|
|
_combatTable = CombatManeuverTable::Get(combatTable);
|
|
}
|
|
}
|
|
|
|
double CWeenieObject::GetHealthPercent()
|
|
{
|
|
unsigned int maxHealth = GetMaxHealth();
|
|
if (!maxHealth)
|
|
return 0.0f;
|
|
|
|
return GetHealth() / (double)maxHealth;
|
|
}
|
|
|
|
unsigned int CWeenieObject::GetHealth()
|
|
{
|
|
DWORD value = 0;
|
|
m_Qualities.InqAttribute2nd(HEALTH_ATTRIBUTE_2ND, value, FALSE);
|
|
return value;
|
|
}
|
|
|
|
unsigned int CWeenieObject::GetMaxHealth()
|
|
{
|
|
DWORD value = 0;
|
|
m_Qualities.InqAttribute2nd(MAX_HEALTH_ATTRIBUTE_2ND, value, FALSE);
|
|
return value;
|
|
}
|
|
|
|
void CWeenieObject::SetHealth(unsigned int value, bool bSendUpdate)
|
|
{
|
|
if (value > GetMaxHealth())
|
|
value = GetMaxHealth();
|
|
|
|
m_Qualities.SetAttribute2nd(HEALTH_ATTRIBUTE_2ND, value);
|
|
|
|
if (bSendUpdate)
|
|
NotifyAttribute2ndStatUpdated(HEALTH_ATTRIBUTE_2ND);
|
|
}
|
|
|
|
unsigned int CWeenieObject::GetStamina()
|
|
{
|
|
DWORD value = 0;
|
|
m_Qualities.InqAttribute2nd(STAMINA_ATTRIBUTE_2ND, value, FALSE);
|
|
return value;
|
|
}
|
|
|
|
unsigned int CWeenieObject::GetMaxStamina()
|
|
{
|
|
DWORD value = 0;
|
|
m_Qualities.InqAttribute2nd(MAX_STAMINA_ATTRIBUTE_2ND, value, FALSE);
|
|
return value;
|
|
}
|
|
|
|
void CWeenieObject::SetStamina(unsigned int value, bool bSendUpdate)
|
|
{
|
|
if (value > GetMaxStamina())
|
|
value = GetMaxStamina();
|
|
|
|
m_Qualities.SetAttribute2nd(STAMINA_ATTRIBUTE_2ND, value);
|
|
|
|
if (bSendUpdate)
|
|
NotifyAttribute2ndStatUpdated(STAMINA_ATTRIBUTE_2ND);
|
|
}
|
|
|
|
unsigned int CWeenieObject::GetMana()
|
|
{
|
|
DWORD value = 0;
|
|
m_Qualities.InqAttribute2nd(MANA_ATTRIBUTE_2ND, value, FALSE);
|
|
return value;
|
|
}
|
|
|
|
unsigned int CWeenieObject::GetMaxMana()
|
|
{
|
|
DWORD value = 0;
|
|
m_Qualities.InqAttribute2nd(MAX_MANA_ATTRIBUTE_2ND, value, FALSE);
|
|
return value;
|
|
}
|
|
|
|
void CWeenieObject::SetMana(unsigned int value, bool bSendUpdate)
|
|
{
|
|
if (value > GetMaxMana())
|
|
value = GetMaxMana();
|
|
|
|
m_Qualities.SetAttribute2nd(MANA_ATTRIBUTE_2ND, value);
|
|
|
|
if (bSendUpdate)
|
|
NotifyAttribute2ndStatUpdated(MANA_ATTRIBUTE_2ND);
|
|
}
|
|
|
|
void CWeenieObject::SetMaxVitals(bool bSendUpdate)
|
|
{
|
|
SetHealth(GetMaxHealth(), bSendUpdate);
|
|
SetStamina(GetMaxStamina(), bSendUpdate);
|
|
SetMana(GetMaxMana(), bSendUpdate);
|
|
}
|
|
|
|
int CWeenieObject::AdjustHealth(int amount)
|
|
{
|
|
// amount can be negative or positive
|
|
int maxHealth = GetMaxHealth();
|
|
int oldHealth = GetHealth();
|
|
int newHealth = oldHealth + amount;
|
|
|
|
if (newHealth <= 0)
|
|
newHealth = 0;
|
|
else if (newHealth >= maxHealth)
|
|
newHealth = maxHealth;
|
|
|
|
int adjustment = newHealth - oldHealth;
|
|
SetHealth(newHealth);
|
|
|
|
return adjustment;
|
|
}
|
|
|
|
int CWeenieObject::AdjustStamina(int amount)
|
|
{
|
|
// amount can be negative or positive
|
|
int maxStamina = GetMaxStamina();
|
|
int oldStamina = GetStamina();
|
|
int newStamina = oldStamina + amount;
|
|
|
|
if (newStamina <= 0)
|
|
newStamina = 0;
|
|
else if (newStamina >= maxStamina)
|
|
newStamina = maxStamina;
|
|
|
|
int adjustment = newStamina - oldStamina;
|
|
SetStamina(newStamina);
|
|
|
|
return adjustment;
|
|
}
|
|
|
|
int CWeenieObject::AdjustMana(int amount)
|
|
{
|
|
// amount can be negative or positive
|
|
int maxMana = GetMaxMana();
|
|
int oldMana = GetMana();
|
|
int newMana = oldMana + amount;
|
|
|
|
if (newMana <= 0)
|
|
newMana = 0;
|
|
else if (newMana >= maxMana)
|
|
newMana = maxMana;
|
|
|
|
int adjustment = newMana - oldMana;
|
|
SetMana(newMana);
|
|
|
|
return adjustment;
|
|
}
|
|
|
|
bool CWeenieObject::IsDead()
|
|
{
|
|
if (GetHealth() > 0)
|
|
return false;
|
|
|
|
// we'll consider objects with no health are considered invincible for now
|
|
if (!GetMaxHealth())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CWeenieObject::IsInPortalSpace()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void CWeenieObject::OnMotionDone(DWORD motion, BOOL success)
|
|
{
|
|
if (m_UseManager)
|
|
m_UseManager->OnMotionDone(motion, success);
|
|
|
|
if (m_SpellcastingManager)
|
|
m_SpellcastingManager->HandleMotionDone(motion, success);
|
|
|
|
if (m_AttackManager)
|
|
m_AttackManager->OnMotionDone(motion, success);
|
|
|
|
std::shared_ptr<CWeenieObject> pParent;
|
|
|
|
if (motion == Motion_Reload)
|
|
{
|
|
switch (get_minterp()->InqStyle())
|
|
{
|
|
case Motion_ThrownWeaponCombat:
|
|
case Motion_AtlatlCombat:
|
|
case Motion_CrossbowCombat:
|
|
case Motion_BowCombat:
|
|
{
|
|
std::shared_ptr<CWeenieObject> ammo;
|
|
if (get_minterp()->InqStyle() == Motion_ThrownWeaponCombat)
|
|
ammo = GetWieldedCombat(COMBAT_USE_MISSILE); //for thrown weapons the ammo is the weapon itself.
|
|
else
|
|
ammo = GetWieldedCombat(COMBAT_USE_AMMO);
|
|
|
|
if (ammo)
|
|
{
|
|
if (ammo->parent.lock() != GetPointer<CWeenieObject>())
|
|
{
|
|
ammo->m_Qualities.SetInt(PARENT_LOCATION_INT, PARENT_ENUM::PARENT_RIGHT_HAND);
|
|
ammo->set_parent(GetPointer<CWeenieObject>(), PARENT_ENUM::PARENT_RIGHT_HAND);
|
|
ammo->SetPlacementFrame(Placement::RightHandCombat, FALSE);
|
|
|
|
if (m_bWorldIsAware)
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> owner = GetWorldTopLevelOwner())
|
|
{
|
|
if (owner->GetBlock())
|
|
{
|
|
owner->GetBlock()->ExchangePVS(ammo, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
BinaryWriter *writer = item->CreateMessage();
|
|
g_pWorld->BroadcastPVS(dwCell, writer->GetData(), writer->GetSize(), OBJECT_MSG, 0, FALSE);
|
|
delete writer;
|
|
*/
|
|
|
|
BinaryWriter Blah;
|
|
Blah.Write<DWORD>(0xF749);
|
|
Blah.Write<DWORD>(GetID());
|
|
Blah.Write<DWORD>(ammo->GetID());
|
|
Blah.Write<DWORD>(PARENT_ENUM::PARENT_RIGHT_HAND);
|
|
Blah.Write<DWORD>(Placement::RightHandCombat);
|
|
Blah.Write<WORD>(GetPhysicsObj()->_instance_timestamp);
|
|
Blah.Write<WORD>(++ammo->_position_timestamp);
|
|
g_pWorld->BroadcastPVS(GetLandcell(), Blah.GetData(), Blah.GetSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
DoForcedStopCompletely();
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> ammo;
|
|
if (get_minterp()->InqStyle() == Motion_ThrownWeaponCombat)
|
|
ammo = GetWieldedCombat(COMBAT_USE_MISSILE); //for thrown weapons the ammo is the weapon itself.
|
|
else
|
|
ammo = GetWieldedCombat(COMBAT_USE_AMMO);
|
|
|
|
if (ammo)
|
|
{
|
|
if (!HasInterpActions() && get_minterp()->interpreted_state.forward_command != Motion_Reload && !IsDead())
|
|
{
|
|
switch (get_minterp()->InqStyle())
|
|
{
|
|
default:
|
|
case Motion_NonCombat:
|
|
case Motion_SwordCombat:
|
|
case Motion_HandCombat:
|
|
case Motion_SwordShieldCombat:
|
|
case Motion_Magic:
|
|
{
|
|
if (ammo->parent.lock() == GetPointer<CWeenieObject>())
|
|
{
|
|
if (m_bWorldIsAware)
|
|
{
|
|
ammo->_position_timestamp++;
|
|
|
|
BinaryWriter Blah;
|
|
Blah.Write<DWORD>(0xF74A);
|
|
Blah.Write<DWORD>(ammo->GetID());
|
|
Blah.Write<WORD>(ammo->_instance_timestamp);
|
|
Blah.Write<WORD>(ammo->_position_timestamp);
|
|
g_pWorld->BroadcastPVS(GetLandcell(), Blah.GetData(), Blah.GetSize());
|
|
}
|
|
|
|
ammo->m_Qualities.SetInt(PARENT_LOCATION_INT, 0);
|
|
ammo->unset_parent();
|
|
ammo->leave_world();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Motion_ThrownWeaponCombat:
|
|
case Motion_AtlatlCombat:
|
|
case Motion_CrossbowCombat:
|
|
case Motion_BowCombat:
|
|
{
|
|
if (ammo->parent.lock() != GetPointer<CWeenieObject>())
|
|
{
|
|
MovementParameters params;
|
|
get_minterp()->DoMotion(Motion_Reload, ¶ms);
|
|
|
|
_server_control_timestamp++;
|
|
last_move_was_autonomous = false;
|
|
|
|
Animation_Update();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::OnDeath(DWORD killer_id)
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> pKiller = g_pWorld->FindObject(killer_id))
|
|
{
|
|
pKiller->ChanceExecuteEmoteSet(GetID(), KillTaunt_EmoteCategory);
|
|
}
|
|
|
|
if (m_UseManager)
|
|
m_UseManager->OnDeath(killer_id);
|
|
|
|
if (m_SpellcastingManager)
|
|
m_SpellcastingManager->OnDeath(killer_id);
|
|
|
|
if (m_EmoteManager)
|
|
m_EmoteManager->OnDeath(killer_id);
|
|
|
|
if (m_AttackManager)
|
|
m_AttackManager->OnDeath(killer_id);
|
|
|
|
ChanceExecuteEmoteSet(killer_id, Death_EmoteCategory);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
{
|
|
PackableList<DWORD> removed;
|
|
|
|
if (m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (auto it = m_Qualities._enchantment_reg->_add_list->begin(); it != m_Qualities._enchantment_reg->_add_list->end();)
|
|
{
|
|
if (it->_duration != -1.0)
|
|
{
|
|
removed.push_back(it->_id);
|
|
it = m_Qualities._enchantment_reg->_add_list->erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (auto it = m_Qualities._enchantment_reg->_mult_list->begin(); it != m_Qualities._enchantment_reg->_mult_list->end();)
|
|
{
|
|
if (it->_duration != -1.0)
|
|
{
|
|
removed.push_back(it->_id);
|
|
it = m_Qualities._enchantment_reg->_mult_list->erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removed.size())
|
|
{
|
|
// m_Qualities._enchantment_reg->PurgeEnchantments();
|
|
|
|
BinaryWriter expireMessage;
|
|
expireMessage.Write<DWORD>(0x2C8);
|
|
removed.Pack(&expireMessage);
|
|
|
|
SendNetMessage(&expireMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
m_Qualities.SetInt(NUM_DEATHS_INT, InqIntQuality(NUM_DEATHS_INT, 0) + 1);
|
|
NotifyIntStatUpdated(NUM_DEATHS_INT);
|
|
}
|
|
|
|
void CWeenieObject::Revive()
|
|
{
|
|
last_move_was_autonomous = false;
|
|
StopCompletely(0);
|
|
_server_control_timestamp++;
|
|
|
|
ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
|
|
|
|
TeleportToSpawn();
|
|
|
|
assert(get_minterp()->interpreted_state.forward_command != GetCommandID(0x11));
|
|
|
|
Animation_Update();
|
|
|
|
SetHealth((int)(GetMaxHealth() * InqFloatQuality(HEALTH_UPON_RESURRECTION_FLOAT, 1.0f)));
|
|
SetStamina((int)(GetMaxStamina() * InqFloatQuality(STAMINA_UPON_RESURRECTION_FLOAT, 1.0f)));
|
|
SetMana((int)(GetMaxMana() * InqFloatQuality(MANA_UPON_RESURRECTION_FLOAT, 1.0f)));
|
|
}
|
|
|
|
bool CWeenieObject::TeleportToSpawn()
|
|
{
|
|
extern bool g_bReviveOverride;
|
|
extern Position g_RevivePosition;
|
|
|
|
if (g_bReviveOverride)
|
|
{
|
|
Movement_Teleport(
|
|
g_RevivePosition
|
|
/*Position(0x7B610007, Vector(23.343912f, 159.351471f, 10.004999f), Quaternion(-0.171325f, 0.000000f, 0.000000f, 0.985215f))*/
|
|
/*
|
|
Position(0x7C65002B,
|
|
Vector(141.572693f, 59.415398f, 9.802724f),
|
|
Quaternion(0.907020f, 0.000000f, 0.000000f, 0.421088f))
|
|
*/,
|
|
true);
|
|
return true;
|
|
}
|
|
|
|
if (TeleportToLifestone())
|
|
return true;
|
|
|
|
Position initialPos;
|
|
if (m_Qualities.InqPosition(INSTANTIATION_POSITION, initialPos) && initialPos.objcell_id)
|
|
{
|
|
Movement_Teleport(initialPos, true);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CWeenieObject::Attach(CWorldLandBlock *pBlock)
|
|
{
|
|
m_pBlock = pBlock;
|
|
}
|
|
|
|
void CWeenieObject::Detach()
|
|
{
|
|
m_pBlock = NULL;
|
|
}
|
|
|
|
CWorldLandBlock *CWeenieObject::GetBlock()
|
|
{
|
|
return m_pBlock;
|
|
}
|
|
|
|
void CWeenieObject::ReleaseFromBlock()
|
|
{
|
|
if (CWorldLandBlock *pBlock = GetBlock())
|
|
pBlock->Release(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
void CWeenieObject::TryIdentify(std::shared_ptr<CWeenieObject> source)
|
|
{
|
|
bool success = false;
|
|
bool bShowLevel = false;
|
|
|
|
if (source == GetPointer<CWeenieObject>())
|
|
{
|
|
success = true;
|
|
}
|
|
else if (AsPlayer() && source->AsPlayer())
|
|
{
|
|
if (!(AsPlayer()->GetCharacterOptions() & UseDeception_CharacterOption))
|
|
success = true;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
DWORD deceptionSkill = 0;
|
|
|
|
if (InqType() & TYPE_ITEM)
|
|
{
|
|
deceptionSkill = (DWORD)m_Qualities.GetInt(RESIST_ITEM_APPRAISAL_INT, 0);
|
|
}
|
|
else
|
|
{
|
|
m_Qualities.InqSkill(DECEPTION_SKILL, deceptionSkill, FALSE);
|
|
|
|
if (IsCreature() && !_IsPlayer())
|
|
{
|
|
DWORD focus = 0, self = 0;
|
|
m_Qualities.InqAttribute(FOCUS_ATTRIBUTE, focus, FALSE);
|
|
m_Qualities.InqAttribute(SELF_ATTRIBUTE, self, FALSE);
|
|
deceptionSkill += (focus + self) / 4;
|
|
}
|
|
|
|
float dist = DistanceTo(source, true);
|
|
if (dist < 60.0)
|
|
{
|
|
dist = max(0.0f, dist);
|
|
deceptionSkill *= 0.75 + ((dist / 60.0) * 0.25);
|
|
}
|
|
}
|
|
|
|
if (deceptionSkill > 0)
|
|
{
|
|
STypeSkill sourceSkillType = STypeSkill::UNDEF_SKILL;
|
|
|
|
DWORD itemType = InqType();
|
|
|
|
if (_IsPlayer())
|
|
{
|
|
sourceSkillType = STypeSkill::PERSONAL_APPRAISAL_SKILL;
|
|
}
|
|
else if (itemType & TYPE_CREATURE)
|
|
{
|
|
sourceSkillType = STypeSkill::CREATURE_APPRAISAL_SKILL;
|
|
}
|
|
else if (itemType & TYPE_VESTEMENTS)
|
|
{
|
|
sourceSkillType = STypeSkill::ARMOR_APPRAISAL_SKILL;
|
|
}
|
|
else if (itemType & TYPE_WEAPON_OR_CASTER)
|
|
{
|
|
sourceSkillType = STypeSkill::WEAPON_APPRAISAL_SKILL;
|
|
}
|
|
else if (itemType & TYPE_ITEM)
|
|
{
|
|
sourceSkillType = STypeSkill::ITEM_APPRAISAL_SKILL;
|
|
|
|
if (m_Qualities._spell_book && !m_Qualities._spell_book->_spellbook.empty())
|
|
{
|
|
sourceSkillType = STypeSkill::MAGIC_ITEM_APPRAISAL_SKILL;
|
|
}
|
|
}
|
|
|
|
if (sourceSkillType != STypeSkill::UNDEF_SKILL)
|
|
{
|
|
DWORD appraiseSkill = 0;
|
|
source->m_Qualities.InqSkill(sourceSkillType, appraiseSkill, FALSE);
|
|
|
|
if (IsCreature())
|
|
appraiseSkill += 50;
|
|
|
|
if (AppraisalSkillCheck(appraiseSkill + 50, deceptionSkill))
|
|
{
|
|
bShowLevel = true;
|
|
|
|
if (AppraisalSkillCheck(appraiseSkill, deceptionSkill))
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
OnIdentifyAttempted(source);
|
|
|
|
if (success)
|
|
{
|
|
Identify(source);
|
|
}
|
|
else
|
|
{
|
|
source->SendNetMessage(IdentifyObjectFail(GetPointer<CWeenieObject>(), bShowLevel), PRIVATE_MSG, TRUE, TRUE);
|
|
|
|
if (AsPlayer())
|
|
{
|
|
SendText(csprintf("%s tried and failed to assess you!", source->GetName().c_str()), LTT_APPRAISAL_CHANNEL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::Identify(std::shared_ptr<CWeenieObject> source, DWORD overrideId)
|
|
{
|
|
source->SendNetMessage(IdentifyObject(source, GetPointer<CWeenieObject>(), overrideId), PRIVATE_MSG, TRUE, TRUE);
|
|
}
|
|
|
|
float CWeenieObject::DistanceTo(std::shared_ptr<CWeenieObject> other, bool bUseSpheres)
|
|
{
|
|
if (!_phys_obj.lock() || !other || !other->_phys_obj.lock())
|
|
return FLT_MAX;
|
|
|
|
if (GetPointer<CWeenieObject>() == other)
|
|
return 0.0;
|
|
|
|
float dist = _phys_obj.lock()->DistanceTo(other->_phys_obj.lock());
|
|
|
|
if (bUseSpheres) // && InValidCell() && other->InValidCell())
|
|
{
|
|
dist -= GetRadius() + other->GetRadius();
|
|
if (dist < F_EPSILON)
|
|
dist = 0.0;
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
float CWeenieObject::DistanceSquared(std::shared_ptr<CWeenieObject> other)
|
|
{
|
|
if (!_phys_obj.lock() || !other || !other->_phys_obj.lock())
|
|
return FLT_MAX;
|
|
|
|
if (GetPointer<CWeenieObject>() == other)
|
|
return 0.0;
|
|
|
|
return _phys_obj.lock()->DistanceSquared(other->_phys_obj.lock());
|
|
}
|
|
|
|
float CWeenieObject::HeadingTo(std::shared_ptr<CWeenieObject> target, bool relative)
|
|
{
|
|
if (GetPointer<CWeenieObject>() == target || target->IsContained())
|
|
return 0.0f;
|
|
|
|
float headingToTarget = m_Position.heading_diff(target->m_Position);
|
|
headingToTarget = fabs(headingToTarget);
|
|
|
|
if (relative)
|
|
{
|
|
if (headingToTarget >= 180.0f)
|
|
headingToTarget = 360.0f - headingToTarget;
|
|
}
|
|
else
|
|
return headingToTarget;
|
|
|
|
return headingToTarget;
|
|
}
|
|
|
|
float CWeenieObject::HeadingFrom(std::shared_ptr<CWeenieObject> target, bool relative)
|
|
{
|
|
if (target != NULL)
|
|
return target->HeadingTo(GetPointer<CWeenieObject>());
|
|
return 0.0;
|
|
}
|
|
|
|
float CWeenieObject::HeadingTo(DWORD targetId, bool relative)
|
|
{
|
|
if (!targetId || targetId == GetID())
|
|
return 0.0;
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindObject(targetId);
|
|
if (!target)
|
|
return 0.0;
|
|
|
|
return HeadingTo(target, relative);
|
|
}
|
|
|
|
float CWeenieObject::HeadingFrom(DWORD targetId, bool relative)
|
|
{
|
|
if (!targetId || targetId == GetID())
|
|
return 0.0;
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindObject(targetId);
|
|
if (!target)
|
|
return 0.0;
|
|
|
|
return target->HeadingTo(GetPointer<CWeenieObject>(), relative);
|
|
}
|
|
|
|
DWORD CWeenieObject::GetLandcell()
|
|
{
|
|
return m_Position.objcell_id; // return _phys_obj.lock() ? _phys_obj.lock()->m_Position.objcell_id : 0;
|
|
}
|
|
|
|
void CWeenieObject::EnsureLink(std::shared_ptr<CWeenieObject> source)
|
|
{
|
|
if (m_Qualities._generator_table)
|
|
{
|
|
source->m_Qualities.SetInstanceID(GENERATOR_IID, GetID());
|
|
}
|
|
else
|
|
{
|
|
// probably should check if it's zero or not
|
|
source->m_Qualities.SetInstanceID(ACTIVATION_TARGET_IID, GetID());
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyGeneratedDeath(std::shared_ptr<CWeenieObject> weenie)
|
|
{
|
|
OnGeneratedDeath(weenie);
|
|
}
|
|
|
|
void CWeenieObject::OnGeneratedDeath(std::shared_ptr<CWeenieObject> weenie)
|
|
{
|
|
if (!weenie || !m_Qualities._generator_registry)
|
|
return;
|
|
|
|
DWORD weenie_id = weenie->GetID();
|
|
|
|
GeneratorRegistryNode *node = m_Qualities._generator_registry->_registry.lookup(weenie_id);
|
|
|
|
if (node)
|
|
{
|
|
GeneratorRegistryNode oldNode = *node;
|
|
m_Qualities._generator_registry->_registry.remove(weenie_id);
|
|
|
|
double delay = -1.0;
|
|
if (m_Qualities._generator_table)
|
|
{
|
|
if (InqIIDQuality(GENERATOR_IID, 0)) // we have a generator
|
|
{
|
|
std::vector<std::shared_ptr<CWeenieObject> > rotList;
|
|
bool hasValidChildren = false;
|
|
if (m_Qualities._generator_registry->_registry.size() > 0)
|
|
{
|
|
for each(auto entry in m_Qualities._generator_registry->_registry)
|
|
{
|
|
std::shared_ptr<CWeenieObject> weenie = g_pWorld->FindObject(entry.second.m_objectId);
|
|
|
|
if (weenie && (weenie->IsCreature() || !weenie->IsStuck())) //stuck objects(chests and decorations usually) do not prevent the generator from being finished.
|
|
hasValidChildren = true;
|
|
else if (weenie)
|
|
rotList.push_back(weenie);
|
|
}
|
|
}
|
|
|
|
if (!hasValidChildren)
|
|
{
|
|
//we're the child of a generator and all our children have been destroyed/picked up.
|
|
//so we're done and should cease to exist.
|
|
g_pWorld->RemoveEntity(GetPointer<CWeenieObject>());
|
|
|
|
//make the leftovers rot.
|
|
for each (auto entry in rotList)
|
|
{
|
|
entry->m_Qualities.SetInstanceID(GENERATOR_IID, 0);
|
|
entry->_timeToRot = Timer::cur_time + 300.0; //in 5 minutes
|
|
entry->_beganRot = false;
|
|
entry->m_Qualities.SetFloat(TIME_TO_ROT_FLOAT, _timeToRot);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// find GetPointer<CWeenieObject>() profile slot
|
|
for (auto &profile : m_Qualities._generator_table->_profile_list)
|
|
{
|
|
if (profile.slot == oldNode.slot &&
|
|
(profile.whenCreate == RegenerationType::Death_RegenerationType ||
|
|
profile.whenCreate == RegenerationType::Destruction_RegenerationType))
|
|
{
|
|
delay = profile.delay * g_pConfig->RespawnTimeMultiplier();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delay >= 0)
|
|
{
|
|
if (!m_Qualities._generator_queue)
|
|
m_Qualities._generator_queue = new GeneratorQueue();
|
|
|
|
GeneratorQueueNode queueNode;
|
|
queueNode.slot = oldNode.slot;
|
|
queueNode.when = Timer::cur_time + delay;
|
|
m_Qualities._generator_queue->_queue.push_back(queueNode);
|
|
|
|
if (_nextRegen < 0.0)
|
|
_nextRegen = Timer::cur_time + (InqFloatQuality(REGENERATION_INTERVAL_FLOAT, 0.0, TRUE) * g_pConfig->RespawnTimeMultiplier());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyGeneratedPickedUp(std::shared_ptr<CWeenieObject> weenie)
|
|
{
|
|
OnGeneratedPickedUp(weenie);
|
|
weenie->m_Qualities.RemoveInstanceID(GENERATOR_IID);
|
|
}
|
|
|
|
void CWeenieObject::OnGeneratedPickedUp(std::shared_ptr<CWeenieObject> weenie)
|
|
{
|
|
if (!weenie || !m_Qualities._generator_registry)
|
|
return;
|
|
|
|
DWORD weenie_id = weenie->GetID();
|
|
|
|
GeneratorRegistryNode *node = m_Qualities._generator_registry->_registry.lookup(weenie_id);
|
|
|
|
if (node)
|
|
{
|
|
GeneratorRegistryNode oldNode = *node;
|
|
m_Qualities._generator_registry->_registry.remove(weenie_id);
|
|
|
|
double delay = -1.0;
|
|
if (m_Qualities._generator_table)
|
|
{
|
|
if (InqIIDQuality(GENERATOR_IID, 0)) // we have a generator
|
|
{
|
|
std::vector<std::shared_ptr<CWeenieObject> > rotList;
|
|
bool hasValidChildren = false;
|
|
if (m_Qualities._generator_registry->_registry.size() > 0)
|
|
{
|
|
for each(auto entry in m_Qualities._generator_registry->_registry)
|
|
{
|
|
std::shared_ptr<CWeenieObject> weenie = g_pWorld->FindObject(entry.second.m_objectId);
|
|
|
|
if (weenie && (weenie->IsCreature() || !weenie->IsStuck())) //stuck objects(chests and decorations usually) do not prevent the generator from being finished.
|
|
hasValidChildren = true;
|
|
else if (weenie)
|
|
rotList.push_back(weenie);
|
|
}
|
|
}
|
|
|
|
if (!hasValidChildren)
|
|
{
|
|
//we're the child of a generator and all our children have been destroyed/picked up.
|
|
//so we're done and should cease to exist.
|
|
g_pWorld->RemoveEntity(GetPointer<CWeenieObject>());
|
|
|
|
//make the leftovers rot.
|
|
for each (auto entry in rotList)
|
|
{
|
|
entry->m_Qualities.SetInstanceID(GENERATOR_IID, 0);
|
|
entry->_timeToRot = Timer::cur_time + 300.0; //in 5 minutes
|
|
entry->_beganRot = false;
|
|
entry->m_Qualities.SetFloat(TIME_TO_ROT_FLOAT, _timeToRot);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// find GetPointer<CWeenieObject>() profile slot
|
|
for (auto &profile : m_Qualities._generator_table->_profile_list)
|
|
{
|
|
if (profile.slot == oldNode.slot && profile.whenCreate == RegenerationType::PickUp_RegenerationType || profile.whenCreate == Destruction_RegenerationType)
|
|
{
|
|
delay = profile.delay * g_pConfig->RespawnTimeMultiplier();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delay >= 0)
|
|
{
|
|
if (!m_Qualities._generator_queue)
|
|
m_Qualities._generator_queue = new GeneratorQueue();
|
|
|
|
GeneratorQueueNode queueNode;
|
|
queueNode.slot = oldNode.slot;
|
|
queueNode.when = Timer::cur_time + delay;
|
|
m_Qualities._generator_queue->_queue.push_back(queueNode);
|
|
|
|
if (_nextRegen < 0.0)
|
|
_nextRegen = Timer::cur_time + (InqFloatQuality(REGENERATION_INTERVAL_FLOAT, 0.0, TRUE) * g_pConfig->RespawnTimeMultiplier());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SpeakLocal(const char *text, LogTextType ltt)
|
|
{
|
|
if (HasOwner())
|
|
return;
|
|
|
|
extern bool g_bSilence;
|
|
if (g_bSilence && !IsAdmin())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (AsPlayer())
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > results;
|
|
g_pWorld->EnumNearby(GetPosition(), 30.0f, &results);
|
|
|
|
for (auto weenie : results)
|
|
{
|
|
if (weenie == GetPointer<CWeenieObject>())
|
|
continue;
|
|
|
|
weenie->ChanceExecuteEmoteSet(GetID(), HearChat_EmoteCategory);
|
|
}
|
|
}
|
|
|
|
BinaryWriter *LC = LocalChat(text, GetName().c_str(), GetID(), ltt);
|
|
g_pWorld->BroadcastPVS(GetLandcell(), LC->GetData(), LC->GetSize(), PRIVATE_MSG);
|
|
delete LC;
|
|
}
|
|
|
|
void CWeenieObject::EmoteLocal(const char* szText)
|
|
{
|
|
if (HasOwner())
|
|
return;
|
|
|
|
extern bool g_bSilence;
|
|
if (g_bSilence && !IsAdmin())
|
|
{
|
|
return;
|
|
}
|
|
|
|
BinaryWriter *EL = EmoteChat(szText, GetName().c_str(), GetID());
|
|
g_pWorld->BroadcastPVS(GetLandcell(), EL->GetData(), EL->GetSize(), PRIVATE_MSG);
|
|
delete EL;
|
|
}
|
|
|
|
void CWeenieObject::ActionLocal(const char* szText)
|
|
{
|
|
if (HasOwner())
|
|
return;
|
|
|
|
extern bool g_bSilence;
|
|
if (g_bSilence && !IsAdmin())
|
|
{
|
|
return;
|
|
}
|
|
|
|
BinaryWriter *AL = ActionChat(szText, GetName().c_str(), GetID());
|
|
g_pWorld->BroadcastPVS(GetLandcell(), AL->GetData(), AL->GetSize(), PRIVATE_MSG);
|
|
delete AL;
|
|
}
|
|
|
|
EmoteManager *CWeenieObject::MakeEmoteManager()
|
|
{
|
|
if (!m_EmoteManager)
|
|
{
|
|
m_EmoteManager = new EmoteManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
return m_EmoteManager;
|
|
}
|
|
|
|
UseManager *CWeenieObject::MakeUseManager()
|
|
{
|
|
if (!m_UseManager)
|
|
{
|
|
m_UseManager = new UseManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
return m_UseManager;
|
|
}
|
|
|
|
CSpellcastingManager *CWeenieObject::MakeSpellcastingManager()
|
|
{
|
|
if (!m_SpellcastingManager)
|
|
{
|
|
m_SpellcastingManager = new CSpellcastingManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
return m_SpellcastingManager;
|
|
}
|
|
|
|
AttackManager *CWeenieObject::MakeAttackManager()
|
|
{
|
|
if (!m_AttackManager)
|
|
{
|
|
m_AttackManager = new AttackManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
return m_AttackManager;
|
|
}
|
|
|
|
BYTE CWeenieObject::GetNextStatTimestamp(StatType statType, int statIndex)
|
|
{
|
|
DWORD key = (statType << 16) | statIndex;
|
|
|
|
BYTE nextStat = m_StatSequences[key] + 1;
|
|
m_StatSequences[key] = nextStat;
|
|
|
|
return nextStat;
|
|
}
|
|
|
|
void CWeenieObject::NotifyObjectCreated(bool bPrivate)
|
|
{
|
|
BinaryWriter *CM = CreateMessage();
|
|
if (CM)
|
|
{
|
|
if (bPrivate)
|
|
SendNetMessage(&CM, OBJECT_MSG, FALSE, FALSE);
|
|
else
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), CM->GetData(), CM->GetSize(), OBJECT_MSG, 0);
|
|
delete CM;
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyObjectUpdated(bool bPrivate)
|
|
{
|
|
BinaryWriter *UM = UpdateMessage();
|
|
if (UM)
|
|
{
|
|
if (bPrivate)
|
|
SendNetMessage(&UM, OBJECT_MSG, FALSE, FALSE);
|
|
else
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), UM->GetData(), UM->GetSize(), OBJECT_MSG, 0);
|
|
delete UM;
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyIntStatUpdated(STypeInt key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(Int_StatType, key);
|
|
|
|
int value;
|
|
if (m_Qualities.InqInt(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2CD);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<int>(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2CE);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<int>(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1D1);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1D2);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyInt64StatUpdated(STypeInt64 key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(Int64_StatType, key);
|
|
|
|
__int64 value;
|
|
if (m_Qualities.InqInt64(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2CF);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<__int64>(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2D0);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<__int64>(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2B8);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2B9);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyBoolStatUpdated(STypeBool key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(Bool_StatType, key);
|
|
|
|
BOOL value;
|
|
if (m_Qualities.InqBool(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2D1);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<int>(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2D2);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<int>(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1D3);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1D4);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyFloatStatUpdated(STypeFloat key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(Float_StatType, key);
|
|
|
|
double value;
|
|
if (m_Qualities.InqFloat(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2D3);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<double>(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2D4);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<double>(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1D5);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1D6);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyStringStatUpdated(STypeString key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(String_StatType, key);
|
|
|
|
std::string value;
|
|
if (m_Qualities.InqString(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2D5);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.WriteString(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2D6);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.WriteString(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1D7);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1D8);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyDIDStatUpdated(STypeDID key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(DID_StatType, key);
|
|
|
|
DWORD value;
|
|
if (m_Qualities.InqDataID(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2D7);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<DWORD>(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2D8);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<DWORD>(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1D9);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1DA);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyIIDStatUpdated(STypeIID key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(IID_StatType, key);
|
|
|
|
DWORD value;
|
|
if (m_Qualities.InqInstanceID(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2D9);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<DWORD>(value);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2DA);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
statNotify.Write<DWORD>(value);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1DB);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1DC);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyPositionStatUpdated(STypePosition key, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(Position_StatType, key);
|
|
|
|
Position value;
|
|
if (m_Qualities.InqPosition(key, value))
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x2DB);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
value.Pack(&statNotify);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x2DC);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
value.Pack(&statNotify);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrivate)
|
|
{
|
|
statNotify.Write<DWORD>(0x1DD);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(key);
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
statNotify.Write<DWORD>(0x1DE);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(key);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::NotifyStackSizeUpdated(bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter msg;
|
|
msg.Write<DWORD>(0x197);
|
|
msg.Write<BYTE>(GetNextStatTimestamp(Int_StatType, STACK_SIZE_INT));
|
|
msg.Write<DWORD>(GetID());
|
|
msg.Write<DWORD>(InqIntQuality(STACK_SIZE_INT, 1));
|
|
msg.Write<DWORD>(InqIntQuality(VALUE_INT, 0));
|
|
|
|
if (bPrivate)
|
|
SendNetMessage(&msg, PRIVATE_MSG, FALSE, FALSE);
|
|
else
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), msg.GetData(), msg.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
void CWeenieObject::NotifyContainedItemRemoved(DWORD objectId, bool bPrivate)
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter msg;
|
|
msg.Write<DWORD>(0x24);
|
|
msg.Write<DWORD>(objectId);
|
|
|
|
if (bPrivate)
|
|
SendNetMessage(&msg, PRIVATE_MSG, FALSE, FALSE);
|
|
else
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), msg.GetData(), msg.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
void CWeenieObject::NotifyObjectRemoved()
|
|
{
|
|
if (!m_bWorldIsAware)
|
|
return;
|
|
|
|
BinaryWriter msg;
|
|
msg.Write<DWORD>(0xF747);
|
|
msg.Write<DWORD>(GetID());
|
|
msg.Write<DWORD>(_instance_timestamp);
|
|
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), msg.GetData(), msg.GetSize());
|
|
}
|
|
|
|
void CWeenieObject::CopyIntStat(STypeInt key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyIntStat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyIntStat(STypeInt key, CACQualities *from)
|
|
{
|
|
int value = 0;
|
|
if (from->InqInt(key, value, TRUE, TRUE))
|
|
m_Qualities.SetInt(key, value);
|
|
}
|
|
|
|
void CWeenieObject::CopyInt64Stat(STypeInt64 key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyInt64Stat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyInt64Stat(STypeInt64 key, CACQualities *from)
|
|
{
|
|
__int64 value = 0;
|
|
if (from->InqInt64(key, value))
|
|
m_Qualities.SetInt64(key, value);
|
|
}
|
|
|
|
void CWeenieObject::CopyBoolStat(STypeBool key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyBoolStat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyBoolStat(STypeBool key, CACQualities *from)
|
|
{
|
|
int value = 0;
|
|
if (from->InqBool(key, value))
|
|
m_Qualities.SetBool(key, value);
|
|
}
|
|
|
|
void CWeenieObject::CopyFloatStat(STypeFloat key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyFloatStat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyFloatStat(STypeFloat key, CACQualities *from)
|
|
{
|
|
double value = 0;
|
|
if (from->InqFloat(key, value, TRUE))
|
|
m_Qualities.SetFloat(key, value);
|
|
}
|
|
|
|
void CWeenieObject::CopyStringStat(STypeString key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyStringStat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyStringStat(STypeString key, CACQualities *from)
|
|
{
|
|
std::string value;
|
|
if (from->InqString(key, value))
|
|
m_Qualities.SetString(key, value);
|
|
}
|
|
|
|
void CWeenieObject::CopyDIDStat(STypeDID key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyDIDStat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyDIDStat(STypeDID key, CACQualities *from)
|
|
{
|
|
DWORD value;
|
|
if (from->InqDataID(key, value))
|
|
m_Qualities.SetDataID(key, value);
|
|
}
|
|
|
|
void CWeenieObject::CopyPositionStat(STypePosition key, std::shared_ptr<CWeenieObject> from)
|
|
{
|
|
CopyPositionStat(key, &from->m_Qualities);
|
|
}
|
|
|
|
void CWeenieObject::CopyPositionStat(STypePosition key, CACQualities *from)
|
|
{
|
|
Position value;
|
|
if (from->InqPosition(key, value))
|
|
m_Qualities.SetPosition(key, value);
|
|
}
|
|
|
|
void CWeenieObject::NotifyAttributeStatUpdated(STypeAttribute key)
|
|
{
|
|
BinaryWriter statNotify;
|
|
statNotify.Write<DWORD>(0x2E3);
|
|
statNotify.Write<BYTE>(GetNextStatTimestamp(Attribute_StatType, key));
|
|
statNotify.Write<DWORD>(key);
|
|
|
|
Attribute attrib;
|
|
m_Qualities.InqAttribute(key, attrib);
|
|
statNotify.Write(&attrib);
|
|
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
void CWeenieObject::NotifyAttribute2ndStatUpdated(STypeAttribute2nd key)
|
|
{
|
|
BinaryWriter statNotify;
|
|
statNotify.Write<DWORD>(0x2E7);
|
|
statNotify.Write<BYTE>(GetNextStatTimestamp(Attribute_2nd_StatType, key));
|
|
statNotify.Write<DWORD>(key);
|
|
|
|
SecondaryAttribute attrib2nd;
|
|
m_Qualities.InqAttribute2nd(key, attrib2nd);
|
|
statNotify.Write(&attrib2nd);
|
|
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
void CWeenieObject::NotifySkillStatUpdated(STypeSkill key)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
BinaryWriter statNotify;
|
|
statNotify.Write<DWORD>(0x2DD);
|
|
statNotify.Write<BYTE>(GetNextStatTimestamp(Skill_StatType, key));
|
|
statNotify.Write<DWORD>(key);
|
|
|
|
Skill skill;
|
|
m_Qualities.InqSkill(key, skill);
|
|
statNotify.Write(&skill);
|
|
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
void CWeenieObject::NotifySkillAdvancementClassUpdated(STypeSkill key)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
BinaryWriter statNotify;
|
|
statNotify.Write<DWORD>(0x2E1);
|
|
statNotify.Write<BYTE>(GetNextStatTimestamp(Skill_StatType, key));
|
|
statNotify.Write<DWORD>(key);
|
|
|
|
SKILL_ADVANCEMENT_CLASS sac;
|
|
m_Qualities.InqSkillAdvancementClass(key, sac);
|
|
statNotify.Write<int>(sac);
|
|
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
void CWeenieObject::NotifyEnchantmentUpdated(Enchantment *enchant)
|
|
{
|
|
BinaryWriter statNotify;
|
|
statNotify.Write<DWORD>(0x2C2);
|
|
enchant->Pack(&statNotify);
|
|
|
|
SendNetMessage(&statNotify, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
DWORD CWeenieObject::GetCostToRaiseSkill(STypeSkill key)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
Skill skill;
|
|
m_Qualities.InqSkill(key, skill);
|
|
|
|
if (skill._sac < TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
{
|
|
SkillTable *pSkillTable = SkillSystem::GetSkillTable();
|
|
|
|
if (!pSkillTable)
|
|
return 0;
|
|
|
|
const SkillBase *pSkillBase = pSkillTable->GetSkillBase(key);
|
|
if (pSkillBase != NULL)
|
|
{
|
|
return pSkillBase->_trained_cost;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DWORD maxLevel;
|
|
|
|
switch (skill._sac)
|
|
{
|
|
default:
|
|
return 0;
|
|
case TRAINED_SKILL_ADVANCEMENT_CLASS:
|
|
maxLevel = ExperienceSystem::GetMaxTrainedSkillLevel();
|
|
break;
|
|
case SPECIALIZED_SKILL_ADVANCEMENT_CLASS:
|
|
maxLevel = ExperienceSystem::GetMaxSpecializedSkillLevel();
|
|
break;
|
|
}
|
|
|
|
if (skill._level_from_pp < maxLevel)
|
|
{
|
|
DWORD expRequired = ExperienceSystem::ExperienceToSkillLevel(skill._sac, skill._level_from_pp + 1);
|
|
expRequired -= skill._pp;
|
|
return expRequired;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const char* CWeenieObject::GetGenderString()
|
|
{
|
|
int value;
|
|
if (m_Qualities.InqInt(GENDER_INT, value))
|
|
{
|
|
switch (value)
|
|
{
|
|
case 1:
|
|
return "Male";
|
|
case 2:
|
|
return "Female";
|
|
}
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
const char* CWeenieObject::GetRaceString()
|
|
{
|
|
return "[race]";
|
|
}
|
|
|
|
const char* CWeenieObject::GetTitleString()
|
|
{
|
|
return "[title]";
|
|
}
|
|
|
|
DWORD CWeenieObject::GetXPForKillLevel(int level)
|
|
{
|
|
double xp = 380.5804 + 63.92362*level + 3.543397*pow(level, 2) - 0.05233995*pow(level, 3) + 0.0007008949*pow(level, 4);
|
|
|
|
DWORD xpvalout;
|
|
|
|
if (xp < 0)
|
|
xpvalout = 0;
|
|
else if (xp >= UINT_MAX)
|
|
xpvalout = UINT_MAX;
|
|
else
|
|
xpvalout = (DWORD)xp;
|
|
|
|
// xp /= 5.0; // normally things are way too easy, let's tone it down a bit
|
|
|
|
return xpvalout;
|
|
}
|
|
|
|
void CWeenieObject::GivePerksForKill(std::shared_ptr<CWeenieObject> pKilled)
|
|
{
|
|
if (!pKilled || !pKilled->IsCreature() || pKilled->_IsPlayer())
|
|
return;
|
|
|
|
int level = pKilled->InqIntQuality(LEVEL_INT, 0);
|
|
if (level <= 0)
|
|
return;
|
|
|
|
int xpForKill = 0;
|
|
|
|
if (!pKilled->m_Qualities.InqInt(XP_OVERRIDE_INT, xpForKill, 0, FALSE))
|
|
xpForKill = (int)GetXPForKillLevel(level);
|
|
|
|
xpForKill = (int)(xpForKill * g_pConfig->KillXPMultiplier(level));
|
|
|
|
if (xpForKill < 0)
|
|
xpForKill = 0;
|
|
|
|
GiveSharedXP(xpForKill, false);
|
|
}
|
|
|
|
void CWeenieObject::GiveSharedXP(long long amount, bool showText)
|
|
{
|
|
if (amount <= 0)
|
|
return;
|
|
|
|
Fellowship *f = GetFellowship();
|
|
|
|
if (f)
|
|
f->GiveXP(GetPointer<CWeenieObject>(), amount, showText);
|
|
else
|
|
GiveXP(amount, showText, false);
|
|
}
|
|
|
|
void CWeenieObject::GiveXP(long long amount, bool showText, bool allegianceXP)
|
|
{
|
|
if (amount <= 0)
|
|
return;
|
|
|
|
OnGivenXP(amount, allegianceXP);
|
|
|
|
unsigned __int64 newAvailableXP = (unsigned __int64)InqInt64Quality(AVAILABLE_EXPERIENCE_INT64, 0) + amount;
|
|
unsigned __int64 newTotalXP = (unsigned __int64)InqInt64Quality(TOTAL_EXPERIENCE_INT64, 0) + amount;
|
|
|
|
int currentLevel = InqIntQuality(LEVEL_INT, 1);
|
|
|
|
DWORD skillCredits = 0;
|
|
bool bLeveled = false;
|
|
DWORD64 xpToNextLevel = ExperienceSystem::ExperienceToLevel(currentLevel + 1);
|
|
while (xpToNextLevel <= newTotalXP && currentLevel < ExperienceSystem::GetMaxLevel())
|
|
{
|
|
currentLevel++;
|
|
skillCredits += ExperienceSystem::GetCreditsForLevel(currentLevel);
|
|
bLeveled = true;
|
|
|
|
xpToNextLevel = ExperienceSystem::ExperienceToLevel(currentLevel + 1);
|
|
|
|
if (!xpToNextLevel)
|
|
break;
|
|
}
|
|
|
|
m_Qualities.SetInt64(AVAILABLE_EXPERIENCE_INT64, newAvailableXP);
|
|
NotifyInt64StatUpdated(AVAILABLE_EXPERIENCE_INT64);
|
|
|
|
m_Qualities.SetInt64(TOTAL_EXPERIENCE_INT64, newTotalXP);
|
|
NotifyInt64StatUpdated(TOTAL_EXPERIENCE_INT64);
|
|
|
|
GiveSkillCredits(skillCredits, false);
|
|
|
|
if (bLeveled)
|
|
{
|
|
m_Qualities.SetInt(LEVEL_INT, currentLevel);
|
|
NotifyIntStatUpdated(LEVEL_INT);
|
|
|
|
EmitEffect(PS_LevelUp, 1.0f);
|
|
|
|
if (currentLevel == 275)
|
|
{
|
|
EmitEffect(PS_WeddingBliss, 1.0f);
|
|
}
|
|
|
|
const char *notice = csprintf(
|
|
"You are now level %s! You have %s experience points and %s skill credits available to raise skills and attributes.",
|
|
FormatNumberString(currentLevel).c_str(),
|
|
FormatNumberString(newAvailableXP).c_str(),
|
|
FormatNumberString(InqIntQuality(AVAILABLE_SKILL_CREDITS_INT, 0)).c_str());
|
|
|
|
SendText(notice, LTT_ADVANCEMENT);
|
|
|
|
// restore vitals
|
|
if (!IsDead())
|
|
{
|
|
SetMaxVitals(true);
|
|
}
|
|
}
|
|
|
|
if (showText)
|
|
{
|
|
const char *notice = csprintf(
|
|
"You've earned %s experience.",
|
|
FormatNumberString(amount).c_str());
|
|
|
|
SendText(notice, LTT_DEFAULT);
|
|
}
|
|
|
|
if (!allegianceXP)
|
|
{
|
|
// let allegiance manager pass it up
|
|
g_pAllegianceManager->HandleAllegiancePassup(GetID(), amount, true);
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::TryToUnloadAllegianceXP(bool bShowText)
|
|
{
|
|
AllegianceTreeNode *node = g_pAllegianceManager->GetTreeNode(GetID());
|
|
if (node)
|
|
{
|
|
if (node->_cp_pool_to_unload > 0)
|
|
{
|
|
GiveXP(node->_cp_pool_to_unload, false, true);
|
|
|
|
if (bShowText)
|
|
{
|
|
// GetPointer<CWeenieObject>() was from logging in
|
|
SendText(csprintf("Your Vassals have produced experience points for you.\nTaking your skills as a leader into account, you gain %s xp.",
|
|
FormatNumberString(node->_cp_pool_to_unload).c_str()), LTT_DEFAULT);
|
|
}
|
|
|
|
node->_cp_pool_to_unload = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD CWeenieObject::GiveAttributeXP(STypeAttribute key, DWORD amount)
|
|
{
|
|
Attribute attribute;
|
|
m_Qualities.InqAttribute(key, attribute);
|
|
attribute._cp_spent += amount;
|
|
|
|
DWORD oldLevel = attribute._level_from_cp;
|
|
attribute._level_from_cp = ExperienceSystem::AttributeLevelFromExperience(attribute._cp_spent);
|
|
|
|
m_Qualities.SetAttribute(key, attribute);
|
|
|
|
DWORD raised = attribute._level_from_cp - oldLevel;
|
|
if (raised)
|
|
{
|
|
DWORD newLevel;
|
|
if (m_Qualities.InqAttribute(key, newLevel, TRUE))
|
|
{
|
|
if (_phys_obj.lock())
|
|
_phys_obj.lock()->EmitSound(Sound_RaiseTrait, 1.0, true);
|
|
|
|
SendText(csprintf("Your base %s is now %u!", Attribute::GetAttributeName(key), newLevel), LTT_ADVANCEMENT);
|
|
}
|
|
}
|
|
|
|
NotifyAttributeStatUpdated(key);
|
|
|
|
return raised;
|
|
}
|
|
|
|
const char *GetAttribute2ndName(STypeAttribute2nd key)
|
|
{
|
|
switch (key)
|
|
{
|
|
case MAX_HEALTH_ATTRIBUTE_2ND:
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
return "Health";
|
|
|
|
case MAX_STAMINA_ATTRIBUTE_2ND:
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
return "Stamina";
|
|
|
|
case MAX_MANA_ATTRIBUTE_2ND:
|
|
case MANA_ATTRIBUTE_2ND:
|
|
return "Mana";
|
|
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
DWORD CWeenieObject::GiveAttribute2ndXP(STypeAttribute2nd key, DWORD amount)
|
|
{
|
|
SecondaryAttribute attribute2nd;
|
|
m_Qualities.InqAttribute2nd(key, attribute2nd);
|
|
attribute2nd._cp_spent += amount;
|
|
|
|
DWORD oldLevel = attribute2nd._level_from_cp;
|
|
attribute2nd._level_from_cp = ExperienceSystem::Attribute2ndLevelFromExperience(attribute2nd._cp_spent);
|
|
|
|
m_Qualities.SetAttribute2nd(key, attribute2nd);
|
|
|
|
DWORD raised = attribute2nd._level_from_cp - oldLevel;
|
|
if (raised)
|
|
{
|
|
DWORD newLevel;
|
|
if (m_Qualities.InqAttribute2nd(key, newLevel, TRUE))
|
|
{
|
|
if (_phys_obj.lock())
|
|
_phys_obj.lock()->EmitSound(Sound_RaiseTrait, 1.0, true);
|
|
|
|
SendText(csprintf("Your base %s is now %u!", GetAttribute2ndName(key), newLevel), LTT_ADVANCEMENT);
|
|
}
|
|
}
|
|
|
|
NotifyAttribute2ndStatUpdated(key);
|
|
return raised;
|
|
}
|
|
|
|
DWORD CWeenieObject::GiveSkillAdvancementClass(STypeSkill key, SKILL_ADVANCEMENT_CLASS sac)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
Skill skill;
|
|
m_Qualities.InqSkill(key, skill);
|
|
|
|
// before
|
|
DWORD oldLevel = 0;
|
|
m_Qualities.InqSkill(key, oldLevel, TRUE);
|
|
|
|
skill._sac = sac;
|
|
skill._level_from_pp = ExperienceSystem::SkillLevelFromExperience(skill._sac, skill._pp);
|
|
|
|
// after
|
|
DWORD newLevel;
|
|
m_Qualities.InqSkill(key, newLevel, TRUE);
|
|
|
|
m_Qualities.SetSkill(key, skill);
|
|
|
|
EmitSound(Sound_RaiseTrait, 1.0, true);
|
|
|
|
std::string skillName = "Unknown";
|
|
|
|
if (SkillTable *pSkillTable = SkillSystem::GetSkillTable())
|
|
{
|
|
const SkillBase *pSkillBase = pSkillTable->GetSkillBase(key);
|
|
if (pSkillBase)
|
|
{
|
|
skillName = pSkillBase->_name;
|
|
}
|
|
}
|
|
|
|
SendText(csprintf("You are now trained in %s!", skillName.c_str()), LTT_ADVANCEMENT);
|
|
|
|
DWORD raised = newLevel - oldLevel;
|
|
if (raised)
|
|
{
|
|
SendText(csprintf("Your base %s is now %u!", skillName.c_str(), newLevel), LTT_ADVANCEMENT);
|
|
}
|
|
|
|
NotifySkillStatUpdated(key);
|
|
return raised;
|
|
}
|
|
|
|
DWORD CWeenieObject::GiveSkillXP(STypeSkill key, DWORD amount, bool silent)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
if (amount <= 0)
|
|
return 0;
|
|
|
|
Skill skill;
|
|
m_Qualities.InqSkill(key, skill);
|
|
|
|
if (skill._sac < TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
return 0;
|
|
|
|
skill._pp += amount;
|
|
|
|
DWORD oldLevel = skill._level_from_pp;
|
|
skill._level_from_pp = ExperienceSystem::SkillLevelFromExperience(skill._sac, skill._pp);
|
|
|
|
m_Qualities.SetSkill(key, skill);
|
|
|
|
std::string skillName = "Unknown";
|
|
|
|
if (SkillTable *pSkillTable = SkillSystem::GetSkillTable())
|
|
{
|
|
const SkillBase *pSkillBase = pSkillTable->GetSkillBase(key);
|
|
if (pSkillBase)
|
|
{
|
|
skillName = pSkillBase->_name;
|
|
}
|
|
}
|
|
|
|
if (!silent)
|
|
{
|
|
const char *notice = csprintf(
|
|
"You've earned %s experience in your %s skill.",
|
|
FormatNumberString(amount).c_str(), skillName.c_str());
|
|
|
|
SendText(notice, LTT_ADVANCEMENT);
|
|
}
|
|
|
|
DWORD raised = skill._level_from_pp - oldLevel;
|
|
if (raised)
|
|
{
|
|
// Your base Melee Defense is now 123! color: 13
|
|
|
|
DWORD newLevel;
|
|
if (m_Qualities.InqSkill(key, newLevel, TRUE))
|
|
{
|
|
if (_phys_obj.lock())
|
|
_phys_obj.lock()->EmitSound(Sound_RaiseTrait, 1.0, true);
|
|
|
|
SendText(csprintf("Your base %s is now %u!", skillName.c_str(), newLevel), LTT_ADVANCEMENT);
|
|
}
|
|
}
|
|
|
|
NotifySkillStatUpdated(key);
|
|
|
|
if (key == LEADERSHIP_SKILL || key == LOYALTY_SKILL)
|
|
{
|
|
AllegianceTreeNode *self = g_pAllegianceManager->GetTreeNode(GetID());
|
|
if (self)
|
|
self->UpdateWithWeenie(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
return raised;
|
|
}
|
|
|
|
DWORD CWeenieObject::GiveSkillPoints(STypeSkill key, DWORD amount)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
if (amount <= 0)
|
|
return 0;
|
|
|
|
Skill skill;
|
|
m_Qualities.InqSkill(key, skill);
|
|
|
|
if (skill._sac < TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
return 0;
|
|
|
|
skill._level_from_pp += amount;
|
|
|
|
skill._pp = ExperienceSystem::ExperienceToSkillLevel(skill._sac, skill._level_from_pp);
|
|
|
|
m_Qualities.SetSkill(key, skill);
|
|
|
|
// Your base Melee Defense is now 123! color: 13
|
|
|
|
DWORD newLevel;
|
|
if (m_Qualities.InqSkill(key, newLevel, TRUE))
|
|
{
|
|
std::string skillName = "Unknown";
|
|
|
|
if (SkillTable *pSkillTable = SkillSystem::GetSkillTable())
|
|
{
|
|
const SkillBase *pSkillBase = pSkillTable->GetSkillBase(key);
|
|
if (pSkillBase)
|
|
{
|
|
skillName = pSkillBase->_name;
|
|
}
|
|
}
|
|
|
|
if (_phys_obj.lock())
|
|
_phys_obj.lock()->EmitSound(Sound_RaiseTrait, 1.0, true);
|
|
|
|
SendText(csprintf("Your base %s is now %u!", skillName.c_str(), newLevel), LTT_ADVANCEMENT);
|
|
}
|
|
|
|
NotifySkillStatUpdated(key);
|
|
|
|
if (key == LEADERSHIP_SKILL || key == LOYALTY_SKILL)
|
|
{
|
|
AllegianceTreeNode *self = g_pAllegianceManager->GetTreeNode(GetID());
|
|
if (self)
|
|
self->UpdateWithWeenie(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
return amount;
|
|
}
|
|
|
|
void CWeenieObject::GiveSkillCredits(DWORD amount, bool showText)
|
|
{
|
|
if (amount <= 0)
|
|
return;
|
|
|
|
if (!amount)
|
|
return;
|
|
|
|
DWORD unassignedCredits = 0;
|
|
m_Qualities.InqInt(AVAILABLE_SKILL_CREDITS_INT, *(int *)&unassignedCredits);
|
|
m_Qualities.SetInt(AVAILABLE_SKILL_CREDITS_INT, unassignedCredits + amount);
|
|
NotifyIntStatUpdated(AVAILABLE_SKILL_CREDITS_INT);
|
|
|
|
if (showText)
|
|
{
|
|
if (_phys_obj.lock())
|
|
_phys_obj.lock()->EmitSound(Sound_RaiseTrait, 1.0, true);
|
|
|
|
SendText(csprintf("You have earned %u skill %s!", amount, amount == 1 ? "credit" : "credits"), LTT_ADVANCEMENT);
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SendText(const char* szText, long lColor)
|
|
{
|
|
SendNetMessage(ServerText(szText, lColor), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
|
|
float Calc_BurdenMod(float flBurden)
|
|
{
|
|
if (flBurden < 1.0f)
|
|
return 1.0f;
|
|
|
|
if (flBurden < 2.0f)
|
|
return (2.0f - flBurden);
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
float Calc_AnimSpeed(DWORD runSkill, float fBurden)
|
|
{
|
|
float fBurdenMod = Calc_BurdenMod(fBurden);
|
|
float fSpeed = (float)runSkill;
|
|
|
|
if (fSpeed == 800.0f)
|
|
return (18.0f / 4.0f);
|
|
|
|
fSpeed /= (fSpeed + 200.0f);
|
|
fSpeed *= 11.0f * fBurdenMod;
|
|
return ((fSpeed + 4.0f) / 4.0f);
|
|
}
|
|
|
|
float CWeenieObject::GetBurdenPercent()
|
|
{
|
|
// should base GetPointer<CWeenieObject>() off our total burden and strength
|
|
return 0.0f;
|
|
}
|
|
|
|
bool CWeenieObject::InqJumpVelocity(float extent, float &vz)
|
|
{
|
|
//Client does GetPointer<CWeenieObject>().. but we are the server!
|
|
//if (IsThePlayer())
|
|
//{
|
|
// etc.
|
|
//}
|
|
|
|
return !!m_Qualities.InqJumpVelocity(extent, vz);
|
|
}
|
|
|
|
bool CWeenieObject::InqRunRate(float &rate)
|
|
{
|
|
//Client does GetPointer<CWeenieObject>().. but we are the server!
|
|
//if (IsThePlayer())
|
|
//{
|
|
// etc.
|
|
//}
|
|
|
|
return !!m_Qualities.InqRunRate(rate);
|
|
}
|
|
|
|
bool CWeenieObject::CanJump(float extent)
|
|
{
|
|
//Client does GetPointer<CWeenieObject>().. but we are the server!
|
|
//if (IsThePlayer())
|
|
//{
|
|
// etc.
|
|
//}
|
|
|
|
return !!m_Qualities.CanJump(extent);
|
|
}
|
|
|
|
bool CWeenieObject::JumpStaminaCost(float extent, long &cost)
|
|
{
|
|
// Client does GetPointer<CWeenieObject>()..but we are the server!
|
|
//if (IsThePlayer())
|
|
//{
|
|
// etc.
|
|
//}
|
|
|
|
return !!m_Qualities.JumpStaminaCost(extent, cost);
|
|
}
|
|
|
|
void CWeenieObject::EmitEffect(DWORD effect, float mod)
|
|
{
|
|
if (_phys_obj.lock())
|
|
_phys_obj.lock()->EmitEffect(effect, mod);
|
|
}
|
|
|
|
bool CWeenieObject::TeleportToLifestone()
|
|
{
|
|
Position lifestone;
|
|
if (m_Qualities.InqPosition(SANCTUARY_POSITION, lifestone) && lifestone.objcell_id)
|
|
{
|
|
Movement_Teleport(lifestone, true);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::TeleportToHouse()
|
|
{
|
|
if (std::shared_ptr<CPlayerWeenie> player = AsPlayer())
|
|
{
|
|
DWORD houseId = player->GetAccountHouseId();
|
|
if (houseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(houseId);
|
|
if (houseData->_ownerAccount == player->GetClient()->GetAccountInfo().id)
|
|
{
|
|
if (houseData->_position.objcell_id)
|
|
{
|
|
Movement_Teleport(houseData->_position, false);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::TeleportToMansion()
|
|
{
|
|
AllegianceTreeNode *allegianceNode = g_pAllegianceManager->GetTreeNode(GetID());
|
|
|
|
if (!allegianceNode)
|
|
return false;
|
|
|
|
DWORD allegianceHouseId;
|
|
|
|
std::shared_ptr<CWeenieObject> monarch = g_pWorld->FindObject(allegianceNode->_monarchID);
|
|
if (!monarch)
|
|
{
|
|
monarch = CWeenieObject::Load(allegianceNode->_monarchID);
|
|
if (!monarch)
|
|
return false;
|
|
allegianceHouseId = monarch->InqDIDQuality(HOUSEID_DID, 0);
|
|
}
|
|
else
|
|
allegianceHouseId = monarch->InqDIDQuality(HOUSEID_DID, 0);
|
|
|
|
if (allegianceHouseId)
|
|
{
|
|
CHouseData *houseData = g_pHouseManager->GetHouseData(allegianceHouseId);
|
|
if (houseData && houseData->_ownerId == allegianceNode->_monarchID && (houseData->_houseType == 2 || houseData->_houseType == 3)) //2 = villa, 3 = mansion
|
|
{
|
|
if (houseData->_position.objcell_id)
|
|
{
|
|
Movement_Teleport(houseData->_position, false);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::TeleportToAllegianceHometown()
|
|
{
|
|
AllegianceTreeNode *allegianceNode = g_pAllegianceManager->GetTreeNode(GetID());
|
|
|
|
if (!allegianceNode)
|
|
return false;
|
|
AllegianceInfo *allegianceInfo = g_pAllegianceManager->GetInfo(allegianceNode->_monarchID);
|
|
|
|
if (allegianceInfo && allegianceInfo->_info.m_BindPoint.objcell_id)
|
|
{
|
|
Movement_Teleport(allegianceInfo->_info.m_BindPoint, false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CWeenieObject::SendUseMessage(std::shared_ptr<CWeenieObject> other, unsigned int channel)
|
|
{
|
|
std::string useMessage;
|
|
if (m_Qualities.InqString(USE_MESSAGE_STRING, useMessage))
|
|
{
|
|
other->SendNetMessage(ServerText(useMessage.c_str(), channel), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::ExecuteUseEvent(CUseEventData* useEvent)
|
|
{
|
|
if (m_UseManager && m_UseManager->IsUsing())
|
|
{
|
|
NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
if (IsBusyOrInAction())
|
|
{
|
|
NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
NotifyUseDone(WERROR_NONE);
|
|
return;
|
|
}
|
|
|
|
if (!m_UseManager)
|
|
{
|
|
m_UseManager = new UseManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
useEvent->_manager = m_UseManager;
|
|
useEvent->_weenie = GetPointer<CWeenieObject>();
|
|
|
|
m_UseManager->BeginUse(useEvent);
|
|
}
|
|
|
|
void CWeenieObject::ExecuteAttackEvent(CAttackEventData *attackEvent)
|
|
{
|
|
if (m_AttackManager && m_AttackManager->IsAttacking())
|
|
{
|
|
// NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
if (IsBusyOrInAction())
|
|
{
|
|
NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
NotifyAttackDone();
|
|
return;
|
|
}
|
|
|
|
if (!m_AttackManager)
|
|
{
|
|
m_AttackManager = new AttackManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
attackEvent->_manager = m_AttackManager;
|
|
attackEvent->_weenie = GetPointer<CWeenieObject>();
|
|
|
|
m_AttackManager->BeginAttack(attackEvent);
|
|
}
|
|
|
|
const Position &CWeenieObject::GetPosition()
|
|
{
|
|
/*
|
|
if (!_phys_obj.lock())
|
|
{
|
|
static Position emptyPosition;
|
|
return emptyPosition;
|
|
}
|
|
|
|
return _phys_obj.lock()->m_Position;
|
|
*/
|
|
|
|
return m_Position;
|
|
}
|
|
|
|
void CWeenieObject::CheckForExpiredEnchantments()
|
|
{
|
|
if (m_Qualities._enchantment_reg)
|
|
{
|
|
PackableListWithJson<DWORD> expired;
|
|
m_Qualities._enchantment_reg->GetExpiredEnchantments(&expired);
|
|
|
|
if (expired.size())
|
|
{
|
|
m_Qualities._enchantment_reg->RemoveEnchantments(&expired);
|
|
|
|
if (AsPlayer())
|
|
{
|
|
BinaryWriter expireMessage;
|
|
expireMessage.Write<DWORD>(0x2C5);
|
|
expired.Pack(&expireMessage);
|
|
|
|
SendNetMessage(&expireMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
EmitSound(Sound_SpellExpire, 1.0f, true);
|
|
}
|
|
else if (std::shared_ptr<CWeenieObject> topMostOwner = GetWorldTopLevelOwner())
|
|
{
|
|
if (topMostOwner->AsPlayer())
|
|
{
|
|
for (auto spellExpired : expired)
|
|
{
|
|
const CSpellBase *spellBase = MagicSystem::GetSpellTable()->_spellBaseHash.lookup(spellExpired);
|
|
|
|
if (spellBase)
|
|
{
|
|
topMostOwner->SendText(csprintf("The spell %s on %s has expired.", spellBase->_name.c_str(), GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
topMostOwner->EmitSound(Sound_SpellExpire, 1.0f, true);
|
|
}
|
|
}
|
|
|
|
CheckVitalRanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::WieldedTick()
|
|
{
|
|
InventoryTick();
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (!wielder)
|
|
return;
|
|
|
|
if (_nextManaUse >= 0 && _nextManaUse <= Timer::cur_time)
|
|
{
|
|
double manaRate = 0.0f;
|
|
int currentMana = 0;
|
|
if (m_Qualities.InqFloat(MANA_RATE_FLOAT, manaRate, TRUE) && manaRate != 0.0 && m_Qualities.InqInt(ITEM_CUR_MANA_INT, currentMana, TRUE, FALSE))
|
|
{
|
|
bool hasActiveSpells = false;
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
{
|
|
if (m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
hasActiveSpells = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasActiveSpells && m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
hasActiveSpells = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasActiveSpells && m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
hasActiveSpells = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wielder->m_Qualities._enchantment_reg)
|
|
{
|
|
if (wielder->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (const auto &entry : *wielder->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
hasActiveSpells = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasActiveSpells && wielder->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (const auto &entry : *wielder->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
hasActiveSpells = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasActiveSpells && wielder->m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
for (const auto &entry : *wielder->m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
hasActiveSpells = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasActiveSpells)
|
|
{
|
|
if (currentMana > 0)
|
|
{
|
|
currentMana--;
|
|
m_Qualities.SetInt(ITEM_CUR_MANA_INT, currentMana);
|
|
|
|
if (currentMana == 2)
|
|
{
|
|
if (wielder->AsPlayer())
|
|
wielder->SendText(csprintf("The %s will run out of mana soon.", GetName().c_str()), LTT_MAGIC); //todo: made up message, confirm if it's correct
|
|
}
|
|
|
|
_nextManaUse = Timer::cur_time + (-manaRate * 1000);
|
|
}
|
|
else
|
|
{
|
|
m_Qualities.SetInt(ITEM_CUR_MANA_INT, 0);
|
|
_nextManaUse = -1.0;
|
|
|
|
if (wielder != NULL)
|
|
{
|
|
if (wielder->AsPlayer())
|
|
{
|
|
wielder->SendText(csprintf("The %s has run out of mana.", GetName().c_str()), LTT_MAGIC); //todo: made up message, confirm if it's correct
|
|
wielder->EmitSound(Sound_ItemManaDepleted, 1.0, true);
|
|
}
|
|
|
|
// remove any enchantments
|
|
if (wielder->m_Qualities._enchantment_reg)
|
|
{
|
|
PackableListWithJson<DWORD> spells_to_remove;
|
|
|
|
if (wielder->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (const auto &entry : *wielder->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wielder->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (const auto &entry : *wielder->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wielder->m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
for (const auto &entry : *wielder->m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!spells_to_remove.empty())
|
|
{
|
|
wielder->m_Qualities._enchantment_reg->RemoveEnchantments(&spells_to_remove);
|
|
|
|
if (wielder->AsPlayer())
|
|
{
|
|
BinaryWriter silentRemoveMessage;
|
|
silentRemoveMessage.Write<DWORD>(0x2C8);
|
|
spells_to_remove.Pack(&silentRemoveMessage);
|
|
|
|
wielder->SendNetMessage(&silentRemoveMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
wielder->CheckVitalRanges();
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
{
|
|
PackableListWithJson<DWORD> spells_to_remove;
|
|
|
|
if (m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!spells_to_remove.empty())
|
|
{
|
|
m_Qualities._enchantment_reg->RemoveEnchantments(&spells_to_remove);
|
|
|
|
CheckVitalRanges();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
_nextManaUse = -1.0;
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::InventoryTick()
|
|
{
|
|
if (m_EmoteManager)
|
|
m_EmoteManager->Tick();
|
|
|
|
CheckForExpiredEnchantments();
|
|
}
|
|
|
|
void CWeenieObject::Tick()
|
|
{
|
|
if (m_AttackManager)
|
|
m_AttackManager->Update();
|
|
|
|
if (m_SpellcastingManager)
|
|
m_SpellcastingManager->Update();
|
|
|
|
if (m_EmoteManager)
|
|
m_EmoteManager->Tick();
|
|
|
|
CheckForExpiredEnchantments();
|
|
|
|
if (_nextRegen >= 0 && _nextRegen <= Timer::cur_time)
|
|
{
|
|
if (m_Qualities._generator_queue)
|
|
{
|
|
PackableList<GeneratorQueueNode> &queue = m_Qualities._generator_queue->_queue;
|
|
for (auto entry = queue.begin(); entry != queue.end();)
|
|
{
|
|
if (entry->when <= Timer::cur_time)
|
|
{
|
|
double regenInterval = 0.0;
|
|
if (m_Qualities.InqFloat(REGENERATION_INTERVAL_FLOAT, regenInterval) && regenInterval > 0.0)
|
|
{
|
|
if (m_Qualities._generator_table)
|
|
{
|
|
for (auto &profile : m_Qualities._generator_table->_profile_list)
|
|
{
|
|
if (profile.slot == entry->slot)
|
|
{
|
|
g_pWeenieFactory->GenerateFromTypeOrWcid(GetPointer<CWeenieObject>(), &profile);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//ChanceCreateGeneratorSlot(entry->slot);
|
|
entry = queue.erase(entry);
|
|
continue;
|
|
}
|
|
|
|
entry++;
|
|
}
|
|
}
|
|
|
|
g_pWeenieFactory->AddFromGeneratorTable(GetPointer<CWeenieObject>(), false);
|
|
|
|
int numSpawned = m_Qualities._generator_registry ? (DWORD)m_Qualities._generator_registry->_registry.size() : 0;
|
|
if ((!m_Qualities._generator_queue || m_Qualities._generator_queue->_queue.empty()) && numSpawned >= InqIntQuality(MAX_GENERATED_OBJECTS_INT, 0, TRUE))
|
|
_nextRegen = -1.0;
|
|
else
|
|
_nextRegen = Timer::cur_time + (InqFloatQuality(REGENERATION_INTERVAL_FLOAT, -1.0, TRUE) * g_pConfig->RespawnTimeMultiplier());
|
|
}
|
|
|
|
if (_nextHeartBeat != -1.0 && _nextHeartBeat <= Timer::cur_time)
|
|
{
|
|
if (!IsDead() && !IsInPortalSpace())
|
|
{
|
|
if (_nextHeartBeatEmote != -1.0 && _nextHeartBeatEmote <= Timer::cur_time)
|
|
{
|
|
_nextHeartBeatEmote = Timer::cur_time + Random::GenUInt(2, 15); //add a little variation to avoid synchronization.
|
|
|
|
//_last_update_pos is the time of the last attack/movement/action, basically we don't want to do heartBeat emotes if we're active.
|
|
if (Timer::cur_time > _last_update_pos + 30.0 && m_Qualities._emote_table)
|
|
{
|
|
PackableList<EmoteSet> *emoteSetList = m_Qualities._emote_table->_emote_table.lookup(HeartBeat_EmoteCategory);
|
|
|
|
if (emoteSetList)
|
|
{
|
|
double dice = Random::GenFloat(0.0, 1.0);
|
|
|
|
for (auto &emoteSet : *emoteSetList)
|
|
{
|
|
if (dice < emoteSet.probability)
|
|
{
|
|
if (movement_manager && movement_manager->motion_interpreter)
|
|
{
|
|
if (emoteSet.style == movement_manager->motion_interpreter->interpreted_state.current_style &&
|
|
emoteSet.substyle == movement_manager->motion_interpreter->interpreted_state.forward_command &&
|
|
!movement_manager->motion_interpreter->interpreted_state.turn_command &&
|
|
!movement_manager->motion_interpreter->interpreted_state.sidestep_command)
|
|
{
|
|
MakeEmoteManager()->ExecuteEmoteSet(emoteSet, 0);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CheckRegeneration(InqFloatQuality(HEALTH_RATE_FLOAT, 0.0), HEALTH_ATTRIBUTE_2ND, MAX_HEALTH_ATTRIBUTE_2ND);
|
|
CheckRegeneration(InqFloatQuality(STAMINA_RATE_FLOAT, 0.0), STAMINA_ATTRIBUTE_2ND, MAX_STAMINA_ATTRIBUTE_2ND);
|
|
CheckRegeneration(InqFloatQuality(MANA_RATE_FLOAT, 0.0), MANA_ATTRIBUTE_2ND, MAX_MANA_ATTRIBUTE_2ND);
|
|
}
|
|
|
|
double heartbeatInterval;
|
|
if (m_Qualities.InqFloat(HEARTBEAT_INTERVAL_FLOAT, heartbeatInterval, TRUE))
|
|
_nextHeartBeat = Timer::cur_time + heartbeatInterval;
|
|
else
|
|
_nextHeartBeat = -1.0;
|
|
|
|
m_Qualities.SetFloat(HEARTBEAT_TIMESTAMP_FLOAT, Timer::cur_time);
|
|
}
|
|
|
|
if (_nextReset >= 0)
|
|
{
|
|
if (_nextReset <= Timer::cur_time)
|
|
{
|
|
ResetToInitialState();
|
|
_nextReset = -1.0;
|
|
}
|
|
}
|
|
|
|
if (_timeToRot >= 0 && _timeToRot <= Timer::cur_time)
|
|
{
|
|
if (!HasOwner())
|
|
{
|
|
if (_beganRot)
|
|
{
|
|
if ((_timeToRot + 2.0) <= Timer::cur_time)
|
|
{
|
|
g_pWorld->RemoveEntity(GetPointer<CWeenieObject>());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EmitEffect(PS_Destroy, 1.0f);
|
|
_beganRot = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::CheckRegeneration(double rate, STypeAttribute2nd currentAttrib, STypeAttribute2nd maxAttrib)
|
|
{
|
|
if (rate == 0.0)
|
|
return;
|
|
|
|
if (AsPlayer()) //only players have natural regeneration bonuses
|
|
{
|
|
//some combination of strength and endurance(with endurance being more important) allows one to regenerate hit points at a
|
|
//faster rate the higher one's endurance is. This bonus is in addition to any regeneration spells one may have placed upon themselves.
|
|
//This regeneration bonus caps at around 110%.
|
|
if (currentAttrib == HEALTH_ATTRIBUTE_2ND)
|
|
{
|
|
DWORD strength = 0;
|
|
DWORD endurance = 0;
|
|
m_Qualities.InqAttribute(STRENGTH_ATTRIBUTE, strength, true);
|
|
m_Qualities.InqAttribute(ENDURANCE_ATTRIBUTE, endurance, true);
|
|
float strAndEnd = (float)(strength + (endurance * 2));
|
|
float regenMod = 1.0 + (0.0494 * pow(strAndEnd, 1.179) / 100.0f); //formula deduced from values present in the client pdb.
|
|
regenMod = min(max(regenMod, 1.0), 2.1);
|
|
|
|
rate *= regenMod;
|
|
}
|
|
|
|
if (currentAttrib == HEALTH_ATTRIBUTE_2ND || currentAttrib == STAMINA_ATTRIBUTE_2ND)
|
|
{
|
|
if (get_minterp()->interpreted_state.current_style == Motion_NonCombat)
|
|
{
|
|
switch (get_minterp()->interpreted_state.forward_command)
|
|
{
|
|
default:
|
|
rate *= 1; //out of combat regen
|
|
break;
|
|
case Motion_Crouch:
|
|
rate *= 2;
|
|
break;
|
|
case Motion_Sitting:
|
|
rate *= 2.5;
|
|
break;
|
|
case Motion_Sleeping:
|
|
rate *= 3;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
rate *= 0.5; //in combat regen
|
|
}
|
|
}
|
|
else if (AsMonster())
|
|
{
|
|
// boosted out of combat regen
|
|
if (get_minterp()->interpreted_state.current_style == Motion_NonCombat)
|
|
{
|
|
rate *= 100;
|
|
}
|
|
}
|
|
|
|
switch (currentAttrib)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
rate += accumulatedHealthRegen;
|
|
break;
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
rate += accumulatedStaminaRegen;
|
|
break;
|
|
case MANA_ATTRIBUTE_2ND:
|
|
rate += accumulatedManaRegen;
|
|
break;
|
|
}
|
|
|
|
int regenAmount = floor(rate);
|
|
double accumulated = rate - regenAmount;
|
|
|
|
switch (currentAttrib)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
accumulatedHealthRegen = accumulated;
|
|
break;
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
accumulatedStaminaRegen = accumulated;
|
|
break;
|
|
case MANA_ATTRIBUTE_2ND:
|
|
accumulatedManaRegen = accumulated;
|
|
break;
|
|
}
|
|
|
|
if (regenAmount > 0)
|
|
{
|
|
DWORD currentVital = 0, maxVital = 0;
|
|
if (m_Qualities.InqAttribute2nd(currentAttrib, currentVital, FALSE) && m_Qualities.InqAttribute2nd(maxAttrib, maxVital, FALSE))
|
|
{
|
|
if (currentVital < maxVital)
|
|
{
|
|
currentVital += regenAmount;
|
|
|
|
//currentVital += (DWORD)(ceil(rate) + F_EPSILON); //previously
|
|
|
|
if (currentVital > maxVital)
|
|
{
|
|
currentVital = maxVital;
|
|
}
|
|
|
|
OnRegen(currentAttrib, currentVital);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SetupWeenie()
|
|
{
|
|
// correct the icon
|
|
DWORD clothing_table_id = InqDIDQuality(CLOTHINGBASE_DID, 0);
|
|
DWORD palette_template_key = InqIntQuality(PALETTE_TEMPLATE_INT, 0);
|
|
|
|
if (clothing_table_id && palette_template_key)
|
|
{
|
|
if (!InqBoolQuality(IGNORE_CLO_ICONS_BOOL, FALSE))
|
|
{
|
|
if (ClothingTable *clothingTable = ClothingTable::Get(clothing_table_id))
|
|
{
|
|
if (const CloPaletteTemplate *pPaletteTemplate = clothingTable->_paletteTemplatesHash.lookup(palette_template_key))
|
|
{
|
|
if (pPaletteTemplate->iconID)
|
|
{
|
|
SetIcon(pPaletteTemplate->iconID);
|
|
}
|
|
}
|
|
|
|
ClothingTable::Release(clothingTable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::PostSpawn()
|
|
{
|
|
if (g_pConfig->EverythingUnlocked())
|
|
{
|
|
SetLocked(FALSE);
|
|
}
|
|
|
|
if (!m_Qualities._generator_queue)// Prevents generators that have a generator_registry from reinitializing
|
|
InitCreateGenerator();
|
|
|
|
|
|
double heartbeatInterval;
|
|
if (m_Qualities.InqFloat(HEARTBEAT_INTERVAL_FLOAT, heartbeatInterval, TRUE))
|
|
_nextHeartBeat = Timer::cur_time + heartbeatInterval;
|
|
|
|
if (m_Qualities._emote_table && m_Qualities._emote_table->_emote_table.lookup(HeartBeat_EmoteCategory))
|
|
_nextHeartBeatEmote = Timer::cur_time + Random::GenUInt(2, 15); //We have heartBeat emotes so schedule them.
|
|
}
|
|
|
|
bool CWeenieObject::IsGeneratorSlotReady(int slot)
|
|
{
|
|
if (m_Qualities._generator_registry)
|
|
{
|
|
for (auto &entry : m_Qualities._generator_registry->_registry)
|
|
{
|
|
if (entry.second.slot == slot)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._generator_queue)
|
|
{
|
|
for (auto &entry : m_Qualities._generator_queue->_queue)
|
|
{
|
|
if (entry.slot == slot && entry.when > Timer::cur_time)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CWeenieObject::InitCreateGenerator()
|
|
{
|
|
std::string eventString;
|
|
if (m_Qualities.InqString(GENERATOR_EVENT_STRING, eventString))
|
|
{
|
|
if (g_pGameEventManager->IsEventStarted(eventString.c_str()))
|
|
g_pWeenieFactory->AddFromGeneratorTable(GetPointer<CWeenieObject>(), true);
|
|
}
|
|
else
|
|
g_pWeenieFactory->AddFromGeneratorTable(GetPointer<CWeenieObject>(), true);
|
|
}
|
|
|
|
bool CWeenieObject::IsStorage()
|
|
{
|
|
return (m_Qualities.id == W_STORAGE_CLASS) ? true : false; // "STORAGE"
|
|
}
|
|
|
|
bool CWeenieObject::CanPickup()
|
|
{
|
|
if (IsStuck())
|
|
return false;
|
|
if (IsCreature())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
BinaryWriter *CWeenieObject::CreateMessage()
|
|
{
|
|
m_bWorldIsAware = true;
|
|
return CreateObject(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
BinaryWriter *CWeenieObject::UpdateMessage()
|
|
{
|
|
return UpdateObject(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
void CWeenieObject::RemovePreviousInstance()
|
|
{
|
|
if (_instance_timestamp == 1) // not ideal, but a time saver for now
|
|
return;
|
|
|
|
BinaryWriter message;
|
|
|
|
message.Write<DWORD>(0xF747);
|
|
message.Write<DWORD>(GetID());
|
|
message.Write<DWORD>(_instance_timestamp - 1);
|
|
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), message.GetData(), message.GetSize());
|
|
}
|
|
|
|
void CWeenieObject::UpdateModel()
|
|
{
|
|
if (parent.lock())
|
|
return;
|
|
|
|
BinaryWriter MU;
|
|
|
|
MU.Write<DWORD>(0xF625);
|
|
MU.Write<DWORD>(GetID());
|
|
|
|
ObjDesc objDesc;
|
|
GetObjDesc(objDesc);
|
|
objDesc.Pack(&MU);
|
|
|
|
MU.Write<WORD>(_instance_timestamp);
|
|
MU.Write<WORD>(++_objdesc_timestamp);
|
|
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), MU.GetData(), MU.GetSize());
|
|
}
|
|
|
|
void CWeenieObject::GetObjDesc(ObjDesc &objDesc)
|
|
{
|
|
// Generate
|
|
objDesc.Wipe();
|
|
|
|
if (m_bObjDescOverride)
|
|
{
|
|
objDesc = m_ObjDescOverride;
|
|
return;
|
|
}
|
|
|
|
DWORD basePaletteID;
|
|
if (m_Qualities.InqDataID(PALETTE_BASE_DID, basePaletteID))
|
|
objDesc.paletteID = basePaletteID;
|
|
else
|
|
objDesc.paletteID = 0x0400007E;
|
|
|
|
if (objDesc.paletteID == 0x04000B75)
|
|
objDesc.paletteID = 0x0400007E; // shadows are messed up
|
|
|
|
DWORD clothingBaseID;
|
|
if (m_Qualities.InqDataID(CLOTHINGBASE_DID, clothingBaseID))
|
|
{
|
|
ClothingTable *clothingTable = ClothingTable::Get(clothingBaseID);
|
|
|
|
if (clothingTable)
|
|
{
|
|
ShadePackage shades(InqFloatQuality(SHADE_FLOAT, 0.0));
|
|
|
|
double shade;
|
|
if (m_Qualities.InqFloat(SHADE2_FLOAT, shade))
|
|
shades._val[1] = shade;
|
|
if (m_Qualities.InqFloat(SHADE3_FLOAT, shade))
|
|
shades._val[2] = shade;
|
|
if (m_Qualities.InqFloat(SHADE4_FLOAT, shade))
|
|
shades._val[3] = shade;
|
|
|
|
clothingTable->BuildObjDesc(GetSetupID(), InqIntQuality(PALETTE_TEMPLATE_INT, 0), &shades, &objDesc);
|
|
ClothingTable::Release(clothingTable);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if not using a clothing base, start objDesc from model itself
|
|
// TODO: fix GetPointer<CWeenieObject>() later, quick fix to handle unequipping resetting the model parts
|
|
if (_IsPlayer() && part_array && part_array->setup)
|
|
{
|
|
for (DWORD i = 0; i < part_array->setup->num_parts; i++)
|
|
{
|
|
if (part_array->setup && part_array->setup->parts && part_array->setup->num_parts > 0)
|
|
objDesc.AddAnimPartChange(new AnimPartChange(i, part_array->setup->parts[i]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SetMotionTableID(DWORD motion_table_did)
|
|
{
|
|
m_Qualities.SetDataID(MOTION_TABLE_DID, motion_table_did);
|
|
}
|
|
|
|
void CWeenieObject::SetSetupID(DWORD setup_did)
|
|
{
|
|
m_Qualities.SetDataID(SETUP_DID, setup_did);
|
|
}
|
|
|
|
void CWeenieObject::SetSoundTableID(DWORD sound_table_did)
|
|
{
|
|
m_Qualities.SetDataID(SOUND_TABLE_DID, sound_table_did);
|
|
}
|
|
|
|
void CWeenieObject::SetPETableID(DWORD pe_table_did)
|
|
{
|
|
m_Qualities.SetDataID(PHYSICS_EFFECT_TABLE_DID, pe_table_did);
|
|
}
|
|
|
|
void CWeenieObject::SetScale(float value)
|
|
{
|
|
m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, value);
|
|
}
|
|
|
|
void CWeenieObject::SetIcon(DWORD icon_did)
|
|
{
|
|
m_Qualities.SetDataID(ICON_DID, icon_did);
|
|
}
|
|
|
|
void CWeenieObject::SetItemType(ITEM_TYPE type)
|
|
{
|
|
m_Qualities.SetInt(ITEM_TYPE_INT, type);
|
|
}
|
|
|
|
void CWeenieObject::SetName(const char *name)
|
|
{
|
|
m_Qualities.SetString(NAME_STRING, name);
|
|
}
|
|
|
|
void CWeenieObject::SetShortDescription(const char *text)
|
|
{
|
|
m_Qualities.SetString(SHORT_DESC_STRING, text);
|
|
}
|
|
|
|
void CWeenieObject::SetLongDescription(const char *text)
|
|
{
|
|
m_Qualities.SetString(LONG_DESC_STRING, text);
|
|
}
|
|
|
|
void CWeenieObject::SetRadarBlipColor(RadarBlipEnum color)
|
|
{
|
|
/*
|
|
1 = Lifestone, Pure Blue
|
|
2 = Monster Orange
|
|
3 = NPK White
|
|
4 = Portal Purple
|
|
5 = PK Red
|
|
6 = PKLite Light Pink
|
|
7 = Fellowship Dark Green?
|
|
8 = NPC Light Yellow
|
|
9 = Envoy Light Blue
|
|
10 = Fellowship Bright Green?
|
|
*/
|
|
m_Qualities.SetInt(RADARBLIP_COLOR_INT, (int)color);
|
|
}
|
|
|
|
void CWeenieObject::SetInitialPhysicsState(DWORD physics_state)
|
|
{
|
|
m_Qualities.SetInt(PHYSICS_STATE_INT, physics_state);
|
|
}
|
|
|
|
void CWeenieObject::SetInitialPosition(const Position &position)
|
|
{
|
|
m_Qualities.SetPosition(INSTANTIATION_POSITION, position);
|
|
}
|
|
|
|
void CWeenieObject::SetSpellID(DWORD spell_id)
|
|
{
|
|
m_Qualities.SetDataID(SPELL_DID, spell_id);
|
|
}
|
|
|
|
|
|
DWORD CWeenieObject::GetValue()
|
|
{
|
|
return m_Qualities.GetInt(VALUE_INT, 0);
|
|
}
|
|
|
|
void CWeenieObject::SetValue(DWORD amount)
|
|
{
|
|
m_Qualities.SetInt(VALUE_INT, amount);
|
|
}
|
|
|
|
DWORD CWeenieObject::GetSpellID()
|
|
{
|
|
return m_Qualities.GetDID(SPELL_DID, 0);
|
|
}
|
|
|
|
DWORD CWeenieObject::GetIcon()
|
|
{
|
|
DWORD icon = 0;
|
|
m_Qualities.InqDataID(ICON_DID, icon);
|
|
return icon;
|
|
}
|
|
|
|
std::string CWeenieObject::GetName()
|
|
{
|
|
if (MaterialType type = (MaterialType)InqIntQuality(MATERIAL_TYPE_INT, 0))
|
|
{
|
|
if (type != MaterialType::Leather_MaterialType)
|
|
{
|
|
std::string name;
|
|
MaterialTypeEnumMapper::MaterialTypeToString(type, name);
|
|
name.append(" ");
|
|
name.append(InqStringQuality(NAME_STRING, ""));
|
|
return name;
|
|
}
|
|
}
|
|
std::string name;
|
|
m_Qualities.InqString(NAME_STRING, name);
|
|
return name;
|
|
}
|
|
|
|
std::string CWeenieObject::GetPluralName()
|
|
{
|
|
std::string name;
|
|
if (!m_Qualities.InqString(PLURAL_NAME_STRING, name))
|
|
{
|
|
if (m_Qualities.InqString(NAME_STRING, name))
|
|
name += "s";
|
|
}
|
|
return name;
|
|
}
|
|
|
|
ITEM_TYPE CWeenieObject::GetItemType()
|
|
{
|
|
int type = ITEM_TYPE::TYPE_UNDEF;
|
|
m_Qualities.InqInt(ITEM_TYPE_INT, type);
|
|
return (ITEM_TYPE)type;
|
|
}
|
|
|
|
bool CWeenieObject::IsCreature()
|
|
{
|
|
return (GetItemType() == TYPE_CREATURE);
|
|
}
|
|
|
|
BOOL CWeenieObject::DoCollision(const class EnvCollisionProfile &prof)
|
|
{
|
|
if (_phys_obj.lock() && (_phys_obj.lock()->m_PhysicsState & SCRIPTED_COLLISION_PS))
|
|
{
|
|
_phys_obj.lock()->play_default_script();
|
|
}
|
|
|
|
if (_IsPlayer())
|
|
{
|
|
float jumpVelocity = 0.0f;
|
|
|
|
std::shared_ptr<CWeenieObject> pWeenie = weenie_obj.lock();
|
|
if (pWeenie)
|
|
{
|
|
pWeenie->InqJumpVelocity(1.0, jumpVelocity);
|
|
}
|
|
|
|
float overSpeed = (jumpVelocity + prof.velocity.z) + 4.5; // a little leeway
|
|
|
|
double overSpeedRatio = -(overSpeed / jumpVelocity);
|
|
|
|
if (overSpeedRatio > 0.00)
|
|
{
|
|
int damage = overSpeedRatio * 40.0; // (int)(-overSpeed * 5.0);
|
|
|
|
if (damage > 0)
|
|
{
|
|
DamageEventData damageEvent;
|
|
damageEvent.damage_type = DAMAGE_TYPE::BLUDGEON_DAMAGE_TYPE;
|
|
damageEvent.damage_form = DAMAGE_FORM::DF_IMPACT;
|
|
damageEvent.damageAfterMitigation = damageEvent.damageBeforeMitigation = damage;
|
|
damageEvent.target = GetPointer<CWeenieObject>();
|
|
|
|
// LOG(Temp, Normal, "%f %f HitGround overSpeed: %f overSpeedRatio: %f damage: %d\n",jumpVelocity, fallVelocity, overSpeed, overSpeedRatio, damage);
|
|
if (!ImmuneToDamage(NULL))
|
|
{
|
|
TakeDamage(damageEvent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
BOOL CWeenieObject::DoCollision(const class AtkCollisionProfile &prof)
|
|
{
|
|
if (_phys_obj.lock() && (_phys_obj.lock()->m_PhysicsState & SCRIPTED_COLLISION_PS))
|
|
{
|
|
_phys_obj.lock()->play_default_script();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
BOOL CWeenieObject::DoCollision(const class ObjCollisionProfile &prof)
|
|
{
|
|
if (_phys_obj.lock() && (_phys_obj.lock()->m_PhysicsState & SCRIPTED_COLLISION_PS))
|
|
{
|
|
_phys_obj.lock()->play_default_script();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void CWeenieObject::DoCollisionEnd(DWORD object_id)
|
|
{
|
|
}
|
|
|
|
const char *CWeenieObject::GetLongDescription()
|
|
{
|
|
std::string longDesc;
|
|
m_Qualities.InqString(LONG_DESC_STRING, longDesc);
|
|
return longDesc.c_str();
|
|
}
|
|
|
|
int CWeenieObject::InqIntQuality(STypeInt key, int defaultValue, BOOL raw)
|
|
{
|
|
int value = defaultValue;
|
|
m_Qualities.InqInt(key, value, raw);
|
|
return value;
|
|
}
|
|
|
|
long long CWeenieObject::InqInt64Quality(STypeInt64 key, long long defaultValue)
|
|
{
|
|
long long value = defaultValue;
|
|
m_Qualities.InqInt64(key, value);
|
|
return value;
|
|
}
|
|
|
|
BOOL CWeenieObject::InqBoolQuality(STypeBool key, BOOL defaultValue)
|
|
{
|
|
BOOL value = defaultValue;
|
|
m_Qualities.InqBool(key, value);
|
|
return value;
|
|
}
|
|
|
|
double CWeenieObject::InqFloatQuality(STypeFloat key, double defaultValue, BOOL raw)
|
|
{
|
|
double value = defaultValue;
|
|
m_Qualities.InqFloat(key, value, raw);
|
|
return value;
|
|
}
|
|
|
|
std::string CWeenieObject::InqStringQuality(STypeString key, std::string defaultValue)
|
|
{
|
|
std::string value = defaultValue;
|
|
m_Qualities.InqString(key, value);
|
|
return value;
|
|
}
|
|
|
|
DWORD CWeenieObject::InqDIDQuality(STypeDID key, DWORD defaultValue)
|
|
{
|
|
DWORD value = defaultValue;
|
|
m_Qualities.InqDataID(key, value);
|
|
return value;
|
|
}
|
|
|
|
DWORD CWeenieObject::InqIIDQuality(STypeIID key, DWORD defaultValue)
|
|
{
|
|
DWORD value = defaultValue;
|
|
m_Qualities.InqInstanceID(key, value);
|
|
return value;
|
|
}
|
|
|
|
Position CWeenieObject::InqPositionQuality(STypePosition key, const Position &defaultValue)
|
|
{
|
|
Position value = defaultValue;
|
|
m_Qualities.InqPosition(key, value);
|
|
return value;
|
|
}
|
|
|
|
DAMAGE_TYPE CWeenieObject::InqDamageType()
|
|
{
|
|
return (DAMAGE_TYPE)InqIntQuality(DAMAGE_TYPE_INT, UNDEF_DAMAGE_TYPE);
|
|
}
|
|
|
|
bool CWeenieObject::GetFloatEnchantmentDetails(STypeFloat stype, double defaultValue, EnchantedQualityDetails *enchantmentDetails)
|
|
{
|
|
enchantmentDetails->rawValue = InqFloatQuality(stype, defaultValue, true);
|
|
bool returnValue = m_Qualities.GetFloatEnchantmentDetails(stype, enchantmentDetails);
|
|
enchantmentDetails->CalculateEnchantedValue();
|
|
return returnValue;
|
|
}
|
|
|
|
bool CWeenieObject::GetIntEnchantmentDetails(STypeInt stype, int defaultValue, EnchantedQualityDetails *enchantmentDetails)
|
|
{
|
|
enchantmentDetails->rawValue = InqIntQuality(stype, defaultValue, true);
|
|
bool returnValue = m_Qualities.GetIntEnchantmentDetails(stype, enchantmentDetails);
|
|
enchantmentDetails->CalculateEnchantedValue();
|
|
return returnValue;
|
|
}
|
|
|
|
bool CWeenieObject::GetBodyArmorEnchantmentDetails(unsigned int bodyPart, DAMAGE_TYPE damageType, EnchantedQualityDetails *enchantmentDetails)
|
|
{
|
|
bool returnValue = m_Qualities.GetBodyArmorEnchantmentDetails(bodyPart, damageType, enchantmentDetails);
|
|
enchantmentDetails->CalculateEnchantedValue();
|
|
return returnValue;
|
|
}
|
|
|
|
void CWeenieObject::TryCancelAttack()
|
|
{
|
|
if (m_AttackManager)
|
|
m_AttackManager->Cancel();
|
|
}
|
|
|
|
void CWeenieObject::HandleAttackHook(const AttackCone &cone)
|
|
{
|
|
if (m_AttackManager)
|
|
{
|
|
m_AttackManager->HandleAttackHook(cone);
|
|
}
|
|
}
|
|
|
|
ITEM_TYPE CWeenieObject::InqType()
|
|
{
|
|
return GetItemType(); // clientside from pwd
|
|
}
|
|
|
|
int CWeenieObject::InqCollisionProfile(ObjCollisionProfile &prof)
|
|
{
|
|
prof.wcid = m_Qualities.id;
|
|
prof.itemType = InqType();
|
|
prof.SetCreature(IsCreature());
|
|
prof.SetPlayer(_IsPlayer());
|
|
prof.SetAttackable(IsAttackable() ? TRUE : FALSE);
|
|
prof.SetDoor(IsDoor() ? TRUE : FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
void CWeenieObject::HitGround(float fallVelocity)
|
|
{
|
|
/*
|
|
if (!_IsPlayer())
|
|
return;
|
|
|
|
float jumpVelocity = 0.0f;
|
|
weenie_obj->InqJumpVelocity(1.0, jumpVelocity);
|
|
float overSpeed = (jumpVelocity + fallVelocity) + 4.5; // a little leeway
|
|
|
|
double overSpeedRatio = -(overSpeed / jumpVelocity);
|
|
|
|
if (overSpeedRatio > 0.00)
|
|
{
|
|
int damage = overSpeedRatio * 40.0; // (int)(-overSpeed * 5.0);
|
|
|
|
if (damage <= 0)
|
|
return;
|
|
|
|
DamageEventData damageEvent;
|
|
damageEvent.damage_type = DAMAGE_TYPE::BLUDGEON_DAMAGE_TYPE;
|
|
damageEvent.damage_form = DAMAGE_FORM::DF_IMPACT;
|
|
damageEvent.outputDamage = damageEvent.inputDamage = damage;
|
|
damageEvent.target = GetPointer<CWeenieObject>();
|
|
|
|
// LOG(Temp, Normal, "%f %f HitGround overSpeed: %f overSpeedRatio: %f damage: %d\n",jumpVelocity, fallVelocity, overSpeed, overSpeedRatio, damage);
|
|
if (ImmuneToDamage(NULL))
|
|
return;
|
|
|
|
TakeDamage(damageEvent);
|
|
}
|
|
*/
|
|
}
|
|
|
|
void CWeenieObject::TryCastSpell(DWORD target_id, DWORD spell_id)
|
|
{
|
|
int error = MakeSpellcastingManager()->TryBeginCast(target_id, spell_id);
|
|
|
|
if (error)
|
|
{
|
|
NotifyUseDone(error);
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::DoForcedStopCompletely()
|
|
{
|
|
MakeMovementManager(TRUE);
|
|
StopCompletely(0);
|
|
|
|
_server_control_timestamp++;
|
|
last_move_was_autonomous = false;
|
|
Animation_Update();
|
|
}
|
|
|
|
DWORD CWeenieObject::DoAutonomousMotion(DWORD motion, MovementParameters *params)
|
|
{
|
|
MakeMovementManager(TRUE);
|
|
|
|
DWORD err;
|
|
|
|
if (params)
|
|
{
|
|
err = get_minterp()->DoMotion(motion, params);
|
|
}
|
|
else
|
|
{
|
|
MovementParameters defaultParams;
|
|
defaultParams.action_stamp = ++m_wAnimSequence; // maybe check if motion is & 0x10000000
|
|
err = get_minterp()->DoMotion(motion, &defaultParams);
|
|
}
|
|
|
|
if (!err)
|
|
{
|
|
_server_control_timestamp++;
|
|
last_move_was_autonomous = false;
|
|
Animation_Update();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
DWORD CWeenieObject::DoForcedMotion(DWORD motion, MovementParameters *params)
|
|
{
|
|
MakeMovementManager(TRUE);
|
|
|
|
DWORD err;
|
|
|
|
if (params)
|
|
{
|
|
err = get_minterp()->DoMotion(motion, params);
|
|
}
|
|
else
|
|
{
|
|
MovementParameters defaultParams;
|
|
defaultParams.action_stamp = ++m_wAnimSequence; // maybe check if motion is & 0x10000000
|
|
err = get_minterp()->DoMotion(motion, &defaultParams);
|
|
}
|
|
|
|
if (!err)
|
|
{
|
|
// _server_control_timestamp++;
|
|
last_move_was_autonomous = false;
|
|
Animation_Update();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
bool CWeenieObject::IsCompletelyIdle()
|
|
{
|
|
if (IsBusyOrInAction())
|
|
return false;
|
|
|
|
if (movement_manager)
|
|
{
|
|
if (movement_manager->moveto_manager)
|
|
{
|
|
if (movement_manager->moveto_manager->movement_type != MovementTypes::Invalid)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (movement_manager->motion_interpreter)
|
|
{
|
|
if (movement_manager->motion_interpreter->interpreted_state.sidestep_command ||
|
|
movement_manager->motion_interpreter->interpreted_state.turn_command ||
|
|
movement_manager->motion_interpreter->interpreted_state.forward_command != 0x41000003)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CWeenieObject::IsBusy()
|
|
{
|
|
if (m_UseManager && m_UseManager->IsUsing())
|
|
return true;
|
|
|
|
if (m_SpellcastingManager && m_SpellcastingManager->m_bCasting)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::IsMovingTo()
|
|
{
|
|
if (!movement_manager || !movement_manager->moveto_manager)
|
|
return false;
|
|
|
|
return movement_manager->moveto_manager->movement_type != MovementTypes::Invalid;
|
|
}
|
|
|
|
bool CWeenieObject::IsBusyOrInAction()
|
|
{
|
|
return IsBusy() || HasInterpActions();
|
|
}
|
|
|
|
bool CWeenieObject::HasInterpActions()
|
|
{
|
|
if (!movement_manager)
|
|
return false;
|
|
|
|
if (!movement_manager->motion_interpreter)
|
|
return false;
|
|
|
|
return movement_manager->motion_interpreter->interpreted_state.GetNumActions() > 0;
|
|
}
|
|
|
|
bool CWeenieObject::ImmuneToDamage(std::shared_ptr<CWeenieObject> other)
|
|
{
|
|
if (m_PhysicsState & (HIDDEN_PS | PARTICLE_EMITTER_PS | MISSILE_PS))
|
|
return true;
|
|
|
|
if (!IsCreature())
|
|
return true;
|
|
|
|
if (IsDead())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
BODY_PART_ENUM GetRandomBodyPartByDamageQuadrant(DAMAGE_QUADRANT dq)
|
|
{
|
|
BODY_PART_ENUM result = BP_UNDEF;
|
|
|
|
// do damage processing here...
|
|
if (dq & DQ_LOW)
|
|
{
|
|
switch (Random::GenInt(0, 4))
|
|
{
|
|
case 0: result = BP_FOOT; break;
|
|
case 1: result = BP_LOWER_LEG; break;
|
|
case 2: result = BP_UPPER_LEG; break;
|
|
case 3: result = BP_HAND; break;
|
|
case 4: result = BP_ABDOMEN; break;
|
|
}
|
|
}
|
|
else if (dq & DQ_MEDIUM)
|
|
{
|
|
switch (Random::GenInt(0, 6))
|
|
{
|
|
case 0: result = BP_UPPER_LEG; break;
|
|
case 1: result = BP_ABDOMEN; break;
|
|
case 2: result = BP_CHEST; break;
|
|
case 3: result = BP_HAND; break;
|
|
case 5: result = BP_LOWER_ARM; break;
|
|
case 6: result = BP_UPPER_ARM; break;
|
|
}
|
|
}
|
|
else if (dq & DQ_HIGH)
|
|
{
|
|
switch (Random::GenInt(0, 5))
|
|
{
|
|
case 0: result = BP_CHEST; break;
|
|
case 1: result = BP_HAND; break;
|
|
case 2: result = BP_CHEST; break;
|
|
case 3: result = BP_LOWER_ARM; break;
|
|
case 4: result = BP_UPPER_ARM; break;
|
|
case 5: result = BP_HEAD; break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (Random::GenInt(0, 9))
|
|
{
|
|
case 0: result = BP_CHEST; break;
|
|
case 1: result = BP_HAND; break;
|
|
case 2: result = BP_CHEST; break;
|
|
case 3: result = BP_LOWER_ARM; break;
|
|
case 4: result = BP_UPPER_ARM; break;
|
|
case 5: result = BP_HEAD; break;
|
|
case 6: result = BP_UPPER_LEG; break;
|
|
case 7: result = BP_LOWER_LEG; break;
|
|
case 8: result = BP_FOOT; break;
|
|
case 9: result = BP_ABDOMEN; break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CWeenieObject::TryToDealDamage(DamageEventData &data)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pTarget = data.target.lock();
|
|
if (!pTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pTarget->HandleAggro(GetPointer<CWeenieObject>());
|
|
|
|
if (data.damageBeforeMitigation >= 0)
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> pSource = data.source.lock())
|
|
{
|
|
if (pTarget->ImmuneToDamage(pSource))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.damage_form & DF_PHYSICAL)
|
|
{
|
|
std::list<long> bodyParts;
|
|
if (pTarget->m_Qualities._body)
|
|
{
|
|
int hitHeight;
|
|
if (data.hit_quadrant & DQ_HIGH)
|
|
hitHeight = BODY_HEIGHT::HIGH_BODY_HEIGHT;
|
|
else if (data.hit_quadrant & DQ_MEDIUM)
|
|
hitHeight = BODY_HEIGHT::MEDIUM_BODY_HEIGHT;
|
|
else if (data.hit_quadrant & DQ_LOW)
|
|
hitHeight = BODY_HEIGHT::LOW_BODY_HEIGHT;
|
|
else
|
|
hitHeight = BODY_HEIGHT::MEDIUM_BODY_HEIGHT;
|
|
|
|
//todo: taking into account the rest of the quadrant data(front/sides/etc)
|
|
|
|
for (auto &bp : pTarget->m_Qualities._body->_body_part_table)
|
|
{
|
|
if (bp.second._bh == hitHeight)
|
|
bodyParts.push_back(bp.first);
|
|
}
|
|
}
|
|
|
|
if (bodyParts.size())
|
|
{
|
|
std::list<long>::iterator randomBodyPartIterator = bodyParts.begin();
|
|
std::advance(randomBodyPartIterator, Random::GenInt(0, (int)(bodyParts.size() - 1)));
|
|
data.hitPart = (BODY_PART_ENUM)*randomBodyPartIterator;
|
|
}
|
|
else
|
|
data.hitPart = GetRandomBodyPartByDamageQuadrant(data.hit_quadrant);
|
|
}
|
|
|
|
pTarget->TakeDamage(data);
|
|
}
|
|
|
|
|
|
void GetDeathMessage(DAMAGE_TYPE dt, const std::string &killerName, const std::string &victimName, std::string &killerMessage, std::string &victimMessage, std::string &otherMessage)
|
|
{
|
|
switch (dt)
|
|
{
|
|
case BLUDGEON_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 4))
|
|
{
|
|
case 0:
|
|
killerMessage = victimName + " is utterly destroyed by your attack!";
|
|
victimMessage = "You are utterly destroyed by " + killerName + "'s attack!";
|
|
otherMessage = victimName + " is utterly destroyed by " + killerName + "'s attack!";
|
|
break;
|
|
case 1:
|
|
killerMessage = "You knock " + victimName + " into next Morningthaw!";
|
|
victimMessage = killerName + " knocks you into next Morningthaw!";
|
|
otherMessage = killerName + " knocks " + victimName + " into next Morningthaw!";
|
|
break;
|
|
case 2:
|
|
killerMessage = "You flatten " + victimName + "'s body with the force of your assault!";
|
|
victimMessage = killerName + " knocks you into next Morningthaw!";
|
|
otherMessage = killerName + " knocks " + victimName + " into next Morningthaw!";
|
|
break;
|
|
case 3:
|
|
killerMessage = "You beat " + victimName + " to a lifeless pulp!";
|
|
victimMessage = killerName + " beats you to a lifeless pulp!";
|
|
otherMessage = killerName + " beats " + victimName + " to a lifeless pulp!";
|
|
break;
|
|
case 4:
|
|
killerMessage = "The thunder of crushing " + victimName + " is followed by the deafening silence of death!";
|
|
victimMessage = killerName + "'s thunder of crushing you is followed by the deafening silence of death!";
|
|
otherMessage = killerName + "'s thunder of crushing " + victimName + " is followed by the deafening silence of death!";
|
|
break;
|
|
}
|
|
break;
|
|
case FIRE_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 3))
|
|
{
|
|
case 0:
|
|
killerMessage = victimName + "'s seared corpse smolders before you!";
|
|
victimMessage = "Your seared corpse smolders before " + killerName + "!";
|
|
otherMessage = victimName + "'s seared corpse smolders before " + killerName + "!";
|
|
break;
|
|
case 1:
|
|
killerMessage = "You bring " + victimName + " to a fiery end!";
|
|
victimMessage = killerName + " brings you to a fiery end!";
|
|
otherMessage = killerName + " brings " + victimName + " to a fiery end!";
|
|
break;
|
|
case 2:
|
|
killerMessage = "You reduced " + victimName + " to cinders!";
|
|
victimMessage = "You are reduced to cinders by " + killerName + "!";
|
|
otherMessage = killerName + " reduced " + victimName + " to cinders!";
|
|
break;
|
|
case 3:
|
|
killerMessage = victimName + " is incinerated by your assault!";
|
|
victimMessage = "You are incinerated by " + killerName + "'s assault!";
|
|
otherMessage = victimName + " is incinerated by " + killerName + "'s assault!";
|
|
break;
|
|
}
|
|
break;
|
|
case PIERCE_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 3))
|
|
{
|
|
case 0:
|
|
killerMessage = victimName + "'s perforated corpse falls before you!";
|
|
victimMessage = "Your perforated corpse falls before " + killerName + "!";
|
|
otherMessage = victimName + "'s perforated corpse falls before " + killerName + "!";
|
|
break;
|
|
case 1:
|
|
killerMessage = victimName + "'s death is preceded by a sharp, stabbing pain!";
|
|
victimMessage = "Your death is preceded by a sharp, stabbing pain!";
|
|
otherMessage = victimName + "'s death is preceded by a sharp, stabbing pain courtesy of " + killerName + "!";
|
|
break;
|
|
case 2:
|
|
killerMessage = victimName + " is fatally punctured!";
|
|
victimMessage = "You were fatally punctured!";
|
|
otherMessage = victimName + " is fatally punctured by " + killerName + "!";
|
|
break;
|
|
case 3:
|
|
killerMessage = "You run " + victimName + " through!";
|
|
victimMessage = killerName + " runs you through!";
|
|
otherMessage = killerName + " runs " + victimName + " through!";
|
|
break;
|
|
}
|
|
break;
|
|
case SLASH_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 2))
|
|
{
|
|
case 0:
|
|
killerMessage = "You split " + victimName + " apart!";
|
|
victimMessage = killerName + " splits you apart!";
|
|
otherMessage = killerName + " splits " + victimName + " apart!";
|
|
break;
|
|
case 1:
|
|
killerMessage = "You cleave " + victimName + " in twain!";
|
|
victimMessage = killerName + " cleaves you in twain!";
|
|
otherMessage = killerName + " cleaves " + victimName + " in twain!";
|
|
break;
|
|
case 2:
|
|
killerMessage = "You slay " + victimName + " viciously enough to impart death several times over!";
|
|
victimMessage = killerName + " slays you viciously enough to impart death several times over!";
|
|
otherMessage = killerName + " slays " + victimName + " viciously enough to impart death several times over!";
|
|
break;
|
|
}
|
|
break;
|
|
case NETHER_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 2))
|
|
{
|
|
case 0:
|
|
killerMessage = "You reduce " + victimName + " to a drained, twisted corpse!";
|
|
victimMessage = killerName + " reduces you to a drained, twisted corpse!";
|
|
otherMessage = killerName + " reduces " + victimName + " to a drained, twisted corpse!";
|
|
break;
|
|
case 1:
|
|
killerMessage = victimName + " is dessicated by your attack!";
|
|
victimMessage = "You are dessicated by " + killerName + "'s attack!";
|
|
otherMessage = victimName + " is dessicated by " + killerName + "'s attack!";
|
|
break;
|
|
case 2:
|
|
killerMessage = victimName + "'s last strength withers before you!";
|
|
victimMessage = "Your last strength withers before " + killerName + "!";
|
|
otherMessage = victimName + "'s last strength withers before " + killerName + "!";
|
|
break;
|
|
}
|
|
break;
|
|
case ACID_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 2))
|
|
{
|
|
case 0:
|
|
killerMessage = "You reduce " + victimName + " to a sizzling, oozing mass!";
|
|
victimMessage = killerName + " reduces you to a sizzling, oozing mass!";
|
|
otherMessage = killerName + " reduces " + victimName + " to a sizzling, oozing mass!";
|
|
break;
|
|
case 1:
|
|
killerMessage = victimName + "'s last strength dissolves before you!";
|
|
victimMessage = "Your last strength dissolves before " + killerName + "!";
|
|
otherMessage = victimName + "'s last strength dissolves before " + killerName + "!";
|
|
break;
|
|
case 2:
|
|
killerMessage = victimName + " is liquified by your attack!";
|
|
victimMessage = "You are liquified by " + killerName + "'s attack!";
|
|
otherMessage = victimName + " is liquified by " + killerName + "'s attack!";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case COLD_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 2))
|
|
{
|
|
case 0:
|
|
killerMessage = "Your attack stops " + victimName + " cold!";
|
|
victimMessage = killerName + "'s attack stops you cold!";
|
|
otherMessage = killerName + " stops " + victimName + " cold!";
|
|
break;
|
|
case 1:
|
|
killerMessage = "Your assault sends " + victimName + " to an icy death!";
|
|
victimMessage = killerName + "'s assault sends you to an icy death!";
|
|
otherMessage = killerName + "'s assault sends " + victimName + " to an icy death!";
|
|
break;
|
|
case 2:
|
|
killerMessage = victimName + " suffers a frozen fate!";
|
|
victimMessage = "You suffer a frozen fate at the hands of " + killerName + "!";
|
|
otherMessage = victimName + " suffers a frozen fate at the hands of " + killerName + "!";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ELECTRIC_DAMAGE_TYPE:
|
|
switch (Random::GenInt(0, 1))
|
|
{
|
|
case 0:
|
|
killerMessage = "Your lightning coruscates over " + victimName + "'s mortal remains!";
|
|
victimMessage = killerName + "'s lightning coruscates over your mortal remains!";
|
|
otherMessage = killerName + "'s lightning coruscates over " + victimName + "'s mortal remains!";
|
|
break;
|
|
/*
|
|
case 1:
|
|
killerMessage = "Blistered by lightning, " + victimName + " falls!";
|
|
break;
|
|
*/
|
|
case 1:
|
|
killerMessage = "Electricity tears " + victimName + " apart!";
|
|
victimMessage = killerName + "'s electricity tears you apart!";
|
|
otherMessage = killerName + "'s electricity tears " + victimName + " apart!";
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
killerMessage = "You killed " + victimName + "!";
|
|
victimMessage = "You were killed by " + killerName + "!";
|
|
otherMessage = killerName + " slayed " + victimName + "!";
|
|
}
|
|
}
|
|
}
|
|
|
|
float CWeenieObject::GetArmorModForDamageType(DAMAGE_TYPE dt)
|
|
{
|
|
switch (dt)
|
|
{
|
|
case SLASH_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_SLASH_FLOAT, 1.0f, FALSE);
|
|
case PIERCE_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_PIERCE_FLOAT, 1.0f, FALSE);
|
|
case BLUDGEON_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_BLUDGEON_FLOAT, 1.0f, FALSE);
|
|
case FIRE_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_FIRE_FLOAT, 1.0f, FALSE);
|
|
case COLD_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_COLD_FLOAT, 1.0f, FALSE);
|
|
case ELECTRIC_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_ELECTRIC_FLOAT, 1.0f, FALSE);
|
|
case ACID_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_ACID_FLOAT, 1.0f, FALSE);
|
|
case NETHER_DAMAGE_TYPE: return InqFloatQuality(ARMOR_MOD_VS_NETHER_FLOAT, 1.0f, FALSE);
|
|
}
|
|
|
|
return 1.0f;
|
|
}
|
|
|
|
float CWeenieObject::GetEffectiveArmorLevel(DamageEventData &damageData, bool bIgnoreMagicArmor)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pTarget = damageData.target.lock();
|
|
if (!pTarget)
|
|
{
|
|
return 0;
|
|
}
|
|
std::shared_ptr<CWeenieObject> pSource = damageData.source.lock();
|
|
|
|
|
|
float armorLevel = 0.0f;
|
|
|
|
bool isShield = InqIntQuality(COMBAT_USE_INT, 0, TRUE) == COMBAT_USE::COMBAT_USE_SHIELD &&
|
|
InqIntQuality(SHIELD_VALUE_INT, 0, false) > 0;
|
|
bool isShieldSpeced = false;
|
|
SKILL_ADVANCEMENT_CLASS sac = SKILL_ADVANCEMENT_CLASS::UNTRAINED_SKILL_ADVANCEMENT_CLASS;
|
|
if (pTarget->m_Qualities.InqSkillAdvancementClass(SHIELD_SKILL, sac))
|
|
isShieldSpeced = sac == SPECIALIZED_SKILL_ADVANCEMENT_CLASS;
|
|
|
|
if (isShield && (damageData.hit_quadrant & DQ_FRONT) == 0)
|
|
return 0.0f; //shield only works if the attack was from the front.
|
|
|
|
EnchantedQualityDetails buffDetails;
|
|
if (!isShield)
|
|
GetIntEnchantmentDetails(ARMOR_LEVEL_INT, 0, &buffDetails);
|
|
else
|
|
GetIntEnchantmentDetails(SHIELD_VALUE_INT, 0, &buffDetails);
|
|
|
|
if (bIgnoreMagicArmor)
|
|
armorLevel = buffDetails.rawValue; //take the Raw armor value for Hollows. Debuffs should not count
|
|
else
|
|
armorLevel = buffDetails.enchantedValue;
|
|
|
|
if (isShield)
|
|
{
|
|
unsigned long shieldSkill;
|
|
pTarget->m_Qualities.InqSkill(SHIELD_SKILL, shieldSkill, false);
|
|
|
|
if (!isShieldSpeced)
|
|
{
|
|
if (armorLevel >= shieldSkill / 2)
|
|
{
|
|
armorLevel = shieldSkill / 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
armorLevel = min(shieldSkill, armorLevel);
|
|
}
|
|
}
|
|
|
|
switch (damageData.damage_type)
|
|
{
|
|
case DAMAGE_TYPE::SLASH_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_SLASH_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::PIERCE_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_PIERCE_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::BLUDGEON_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_BLUDGEON_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::FIRE_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_FIRE_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::COLD_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_COLD_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::ACID_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_ACID_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::ELECTRIC_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_ELECTRIC_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
case DAMAGE_TYPE::NETHER_DAMAGE_TYPE: armorLevel *= max(0.0, min(InqFloatQuality(ARMOR_MOD_VS_NETHER_FLOAT, 1.0f, bIgnoreMagicArmor), 2.0f)); break;
|
|
}
|
|
|
|
if (isShield)
|
|
{
|
|
double ignoreShieldMod = 0.0f;
|
|
if (pSource)
|
|
pSource->m_Qualities.InqFloat(IGNORE_SHIELD_FLOAT, ignoreShieldMod, FALSE);
|
|
|
|
armorLevel *= (1.0 - ignoreShieldMod);
|
|
}
|
|
|
|
return armorLevel;
|
|
}
|
|
|
|
void CWeenieObject::TakeDamage(DamageEventData &damageData)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pTarget = damageData.target.lock();
|
|
if (!pTarget)
|
|
{
|
|
return;
|
|
}
|
|
std::shared_ptr<CWeenieObject> pSource = damageData.source.lock();
|
|
|
|
EnchantedQualityDetails buffDetails;
|
|
bool isEnchanted = false;
|
|
|
|
switch (damageData.damage_type)
|
|
{
|
|
case SLASH_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_SLASH_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case PIERCE_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_PIERCE_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case BLUDGEON_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_BLUDGEON_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case FIRE_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_FIRE_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case COLD_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_COLD_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case ACID_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_ACID_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case ELECTRIC_DAMAGE_TYPE:
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_ELECTRIC_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case HEALTH_DAMAGE_TYPE:
|
|
if (damageData.damageAfterMitigation > 0)
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_HEALTH_DRAIN_FLOAT, 1.0, &buffDetails);
|
|
else
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_HEALTH_BOOST_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case STAMINA_DAMAGE_TYPE:
|
|
if (damageData.damageAfterMitigation > 0)
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_STAMINA_DRAIN_FLOAT, 1.0, &buffDetails);
|
|
else
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_STAMINA_BOOST_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
case MANA_DAMAGE_TYPE:
|
|
if (damageData.damageAfterMitigation > 0)
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_MANA_DRAIN_FLOAT, 1.0, &buffDetails);
|
|
else
|
|
isEnchanted = GetFloatEnchantmentDetails(RESIST_MANA_BOOST_FLOAT, 1.0, &buffDetails);
|
|
break;
|
|
}
|
|
|
|
if (damageData.isElementalRending && damageData.rendingMultiplier > buffDetails.valueIncreasingMultiplier)
|
|
{
|
|
//our elemental rending is better than the debuffs applied, replace debuffs with rending.
|
|
//we don't need to check if the elemental rending is of the correct type here as the check is already done on the attacker's side
|
|
//and the flag is only true if the damage type matches the rending element.
|
|
buffDetails.valueIncreasingMultiplier = damageData.rendingMultiplier;
|
|
buffDetails.CalculateEnchantedValue();
|
|
}
|
|
|
|
float resistanceRegular;
|
|
if (damageData.ignoreMagicResist)
|
|
resistanceRegular = buffDetails.rawValue; //take the Raw resistance value for Hollows. Debuffs should not count
|
|
else
|
|
resistanceRegular = buffDetails.enchantedValue;
|
|
|
|
if (damageData.damageAfterMitigation > 0 || damageData.damage_type == HEALTH_DAMAGE_TYPE || damageData.damage_type == STAMINA_DAMAGE_TYPE || damageData.damage_type == MANA_DAMAGE_TYPE)
|
|
{
|
|
if (!AsPlayer() || damageData.ignoreMagicResist)
|
|
damageData.damageAfterMitigation *= resistanceRegular;
|
|
else //only players have natural resistances.
|
|
{
|
|
//Some combination of strength and endurance allows one to have a level of "natural resistances" to the 7 damage types.This caps out at a 50 % resistance(the equivalent to level 5 life prots) to these damage types.This resistance is not additive to life protections : higher level life protections will overwrite these natural resistances, although life vulns will take these natural resistances into account, if the player does not have a higher level life protection cast upon them.
|
|
//For example, a player will not get a free protective bonus from natural resistances if they have both Prot 7 and Vuln 7 cast upon them.The Prot and Vuln will cancel each other out, and since the Prot has overwritten the natural resistances, there will be no resistance bonus.
|
|
//The abilities that Endurance or Endurance/Strength conveys are not increased by Strength or Endurance buffs.It is the raw Strength and/or Endurance scores that determine the various bonuses.
|
|
//drain resistances(same formula as natural resistances) allows one to partially resist drain health/stamina/mana and harm attacks, up to a maximum of roughly 50%.
|
|
|
|
//todo: natural resistances only change when base strength or endurance changes so we could potentially pre-calculate GetPointer<CWeenieObject>() somewhere else.
|
|
DWORD strength = 0;
|
|
DWORD endurance = 0;
|
|
m_Qualities.InqAttribute(STRENGTH_ATTRIBUTE, strength, true);
|
|
m_Qualities.InqAttribute(ENDURANCE_ATTRIBUTE, endurance, true);
|
|
float strAndEnd = (float)(strength + endurance);
|
|
float resistanceNatural;
|
|
if (strAndEnd <= 200) //formula deduced from values present in the client pdb.
|
|
resistanceNatural = 1.0f - ((0.05 * strAndEnd) / 100.f);
|
|
else
|
|
resistanceNatural = 1.0f - (((0.1666667 * strAndEnd) - 23.33333) / 100.f);
|
|
|
|
resistanceNatural = max(resistanceNatural, 0.5);
|
|
|
|
//natural resistances only work if they beat the buffed value before any debuffs are applied.
|
|
if (resistanceNatural < buffDetails.enchantedValue_DecreasingOnly)
|
|
{
|
|
//replace our buffed value with natural resistances and recalculate.
|
|
buffDetails.valueDecreasingMultiplier = resistanceNatural;
|
|
buffDetails.CalculateEnchantedValue();
|
|
damageData.damageAfterMitigation *= buffDetails.enchantedValue;
|
|
}
|
|
else
|
|
damageData.damageAfterMitigation *= resistanceRegular;
|
|
}
|
|
}
|
|
|
|
if (!damageData.ignoreArmorEntirely && (damageData.damage_form & DF_PHYSICAL))
|
|
{
|
|
// calculate armor level
|
|
double armorLevel = GetEffectiveArmorLevel(damageData, damageData.ignoreMagicArmor);
|
|
|
|
float damageFactor;
|
|
if (armorLevel >= 0)
|
|
damageFactor = 1.0 / (1.0 + (armorLevel / (190.0 / 3.0)));
|
|
else
|
|
damageFactor = (1.0 + (-armorLevel / (190.0 / 3.0)));
|
|
|
|
damageData.damageAfterMitigation *= damageFactor;
|
|
}
|
|
|
|
if (damageData.damage_form & DF_MAGIC && damageData.isProjectileSpell)
|
|
{
|
|
// check for magic absorption
|
|
std::shared_ptr<CWeenieObject> missile_weapon = GetWieldedCombat(COMBAT_USE::COMBAT_USE_MISSILE);
|
|
|
|
if (missile_weapon && missile_weapon->GetImbueEffects() & ImbuedEffectType::IgnoreSomeMagicProjectileDamage_ImbuedEffectType)
|
|
{
|
|
double reduction = (0.25 * GetMagicDefense() * 0.003) - (0.25 * 0.3);
|
|
|
|
if (reduction > 0.0)
|
|
{
|
|
if (reduction > 0.25)
|
|
reduction = 0.25;
|
|
|
|
damageData.damageAfterMitigation *= 1.0 - reduction;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> shield = GetWieldedCombat(COMBAT_USE::COMBAT_USE_SHIELD);
|
|
|
|
if (shield)
|
|
{
|
|
SKILL_ADVANCEMENT_CLASS sac = SKILL_ADVANCEMENT_CLASS::UNTRAINED_SKILL_ADVANCEMENT_CLASS;
|
|
pTarget->m_Qualities.InqSkillAdvancementClass(SHIELD_SKILL, sac);
|
|
float cap = shield->InqFloatQuality(ABSORB_MAGIC_DAMAGE_FLOAT, 0.0);
|
|
float st = sac != SPECIALIZED_SKILL_ADVANCEMENT_CLASS ? .8 : 1;
|
|
|
|
unsigned long shieldSkill;
|
|
pTarget->m_Qualities.InqSkill(SHIELD_SKILL, shieldSkill, false);
|
|
|
|
float capPercent = (shieldSkill / 433) * cap * st;
|
|
float reduction = 0;
|
|
|
|
if (cap > 0.0 && shieldSkill >= 100)
|
|
{
|
|
// Needed? boils down to the same formula as capPercent above
|
|
//reduction = 1 * (float(capPercent * st * shieldSkill * 0.0030) - (capPercent * st * .3));
|
|
reduction = capPercent;
|
|
}
|
|
|
|
damageData.damageAfterMitigation *= 1.0 - reduction;
|
|
}
|
|
}
|
|
|
|
if (damageData.damageAfterMitigation < 0)
|
|
{
|
|
//todo: can't all damage types heal? Like hitting a fire elemental with fire should heal it instead of damage it?
|
|
if (damageData.damage_type & (HEALTH_DAMAGE_TYPE | STAMINA_DAMAGE_TYPE | MANA_DAMAGE_TYPE))
|
|
{
|
|
// these types can heal
|
|
damageData.outputDamageFinal = (int)ceil(damageData.damageAfterMitigation - F_EPSILON);
|
|
}
|
|
else
|
|
{
|
|
damageData.damageAfterMitigation = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
damageData.outputDamageFinal = (int)floor(damageData.damageAfterMitigation + F_EPSILON);
|
|
}
|
|
|
|
STypeAttribute2nd vitalAffected;
|
|
switch (damageData.damage_type)
|
|
{
|
|
default: vitalAffected = HEALTH_ATTRIBUTE_2ND; break;
|
|
case STAMINA_DAMAGE_TYPE: vitalAffected = STAMINA_ATTRIBUTE_2ND; break;
|
|
case MANA_DAMAGE_TYPE: vitalAffected = MANA_ATTRIBUTE_2ND; break;
|
|
}
|
|
|
|
DWORD vitalStartValue = 0;
|
|
DWORD vitalMinValue = 0;
|
|
DWORD vitalMaxValue = 0;
|
|
m_Qualities.InqAttribute2nd(vitalAffected, vitalStartValue, FALSE);
|
|
m_Qualities.InqAttribute2nd((STypeAttribute2nd)((int)vitalAffected - 1), vitalMaxValue, FALSE);
|
|
|
|
int vitalNewValue = vitalStartValue - damageData.outputDamageFinal;
|
|
vitalNewValue = max((int)vitalMinValue, min((int)vitalMaxValue, vitalNewValue));
|
|
|
|
damageData.outputDamageFinal = (int)vitalStartValue - (int)vitalNewValue;
|
|
damageData.outputDamageFinalPercent = damageData.outputDamageFinal / (double)vitalMaxValue;
|
|
|
|
if (AsMonster())
|
|
{
|
|
// add to list of attackers
|
|
AsMonster()->UpdateDamageList(damageData);
|
|
}
|
|
|
|
if (vitalNewValue != vitalStartValue)
|
|
{
|
|
m_Qualities.SetAttribute2nd(vitalAffected, vitalNewValue);
|
|
NotifyAttribute2ndStatUpdated(vitalAffected);
|
|
|
|
if (vitalAffected == HEALTH_ATTRIBUTE_2ND)
|
|
{
|
|
if (vitalNewValue <= 0)
|
|
{
|
|
damageData.killingBlow = true;
|
|
OnDeath(pSource ? pSource->GetID() : 0);
|
|
}
|
|
else
|
|
{
|
|
if (damageData.damage_form & DF_IMPACT)
|
|
{
|
|
if (damageData.outputDamageFinalPercent >= 0.1f)
|
|
{
|
|
EmitSound(13, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
EmitSound(14, 1.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (damageData.outputDamageFinalPercent >= 0.1f)
|
|
{
|
|
if (damageData.damage_form & DF_PHYSICAL)
|
|
{
|
|
switch (damageData.damage_type)
|
|
{
|
|
case DAMAGE_TYPE::BLUDGEON_DAMAGE_TYPE:
|
|
EmitSound(Random::GenInt(13, 14), 1.0f);
|
|
break;
|
|
|
|
default:
|
|
EmitSound(12, 1.0f);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (damageData.killingBlow && (pSource && pSource->IsCreature()))
|
|
{
|
|
std::string kmsg, vmsg, omsg;
|
|
GetDeathMessage(
|
|
damageData.damage_type,
|
|
damageData.GetSourceName(),
|
|
damageData.GetTargetName(),
|
|
damageData.killer_msg,
|
|
damageData.victim_msg,
|
|
damageData.other_msg);
|
|
}
|
|
else
|
|
{
|
|
damageData.killer_msg = "You killed " + damageData.GetTargetName() + "!";
|
|
damageData.victim_msg = "You died!";
|
|
damageData.other_msg = damageData.GetTargetName() + " died!";
|
|
}
|
|
|
|
// notify the victim they took damage
|
|
OnTookDamage(damageData);
|
|
|
|
// notify the atttacker they did damage
|
|
if (pSource)
|
|
{
|
|
pSource->OnDealtDamage(damageData);
|
|
|
|
if (damageData.killingBlow)
|
|
{
|
|
pSource->GivePerksForKill(GetPointer<CWeenieObject>());
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *damageFormString(DAMAGE_FORM df)
|
|
{
|
|
switch (df)
|
|
{
|
|
case DF_MELEE: return "melee";
|
|
case DF_MISSILE: return "missile";
|
|
case DF_MAGIC: return "magic";
|
|
}
|
|
|
|
return "unknown form";
|
|
}
|
|
|
|
const char *damageQuadrantString(DAMAGE_QUADRANT dq)
|
|
{
|
|
if (dq & DQ_LOW)
|
|
return "low";
|
|
if (dq & DQ_MEDIUM)
|
|
return "medium";
|
|
if (dq & DQ_HIGH)
|
|
return "high";
|
|
|
|
return "*";
|
|
}
|
|
|
|
const char *damageTypeString(DAMAGE_TYPE dt)
|
|
{
|
|
switch (dt)
|
|
{
|
|
case BLUDGEON_DAMAGE_TYPE: return "bludgeon";
|
|
case PIERCE_DAMAGE_TYPE: return "pierce";
|
|
case SLASH_DAMAGE_TYPE: return "slash";
|
|
case FIRE_DAMAGE_TYPE: return "fire";
|
|
case COLD_DAMAGE_TYPE: return "cold";
|
|
case ELECTRIC_DAMAGE_TYPE: return "electric";
|
|
case ACID_DAMAGE_TYPE: return "acid";
|
|
}
|
|
|
|
return "unknown type";
|
|
}
|
|
|
|
std::string DamageEventData::GetSourceName()
|
|
{
|
|
std::shared_ptr<CWeenieObject> pSource = source.lock();
|
|
|
|
return pSource ? pSource->GetName() : "A mysterious source";
|
|
}
|
|
|
|
std::string DamageEventData::GetTargetName()
|
|
{
|
|
std::shared_ptr<CWeenieObject> pTarget = target.lock();
|
|
|
|
return pTarget ? pTarget->GetName() : "A mysterious victim";
|
|
}
|
|
|
|
void CWeenieObject::NotifyDeathMessage(DWORD killer_id, const char *message)
|
|
{
|
|
BinaryWriter deathMsg;
|
|
deathMsg.Write<DWORD>(0x19E);
|
|
deathMsg.WriteString(message);
|
|
deathMsg.Write<DWORD>(GetID());
|
|
deathMsg.Write<DWORD>(killer_id);
|
|
|
|
if (_IsPlayer() && (g_pConfig->ShowDeathMessagesGlobally() || (g_pConfig->ShowPlayerDeathMessagesGlobally() && g_pWorld->FindPlayer(killer_id) != NULL)))
|
|
{
|
|
g_pWorld->BroadcastGlobal(&deathMsg, PRIVATE_MSG, 0, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
g_pWorld->BroadcastPVS(GetLandcell(), deathMsg.GetData(), deathMsg.GetSize(), PRIVATE_MSG);
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::OnTookDamage(DamageEventData &data)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pSource = data.source.lock();
|
|
|
|
if (data.damage_form & DF_PHYSICAL && data.outputDamageFinal >= 0)
|
|
{
|
|
if (data.hit_quadrant & DQ_LOW)
|
|
EmitEffect(Random::GenInt(91, 94), 1.0f);
|
|
|
|
if (!(data.hit_quadrant & DQ_HEIGHT_MASK) || data.hit_quadrant & DQ_MEDIUM)
|
|
EmitEffect(Random::GenInt(95, 98), 1.0f);
|
|
|
|
if (data.hit_quadrant & DQ_HIGH)
|
|
EmitEffect(Random::GenInt(99, 102), 1.0f);
|
|
}
|
|
|
|
if (data.killingBlow)
|
|
{
|
|
if (pSource)
|
|
{
|
|
NotifyVictimEvent(data.victim_msg.c_str());
|
|
|
|
if (_IsPlayer())
|
|
NotifyDeathMessage(pSource->GetID(), data.other_msg.c_str());
|
|
}
|
|
else
|
|
{
|
|
NotifyVictimEvent("You died!");
|
|
|
|
if (_IsPlayer())
|
|
NotifyDeathMessage(0, csprintf("%s died!", data.GetTargetName().c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (data.damage_form & DF_PHYSICAL)
|
|
{
|
|
NotifyDefenderEvent(
|
|
data.GetSourceName().c_str(),
|
|
data.damage_type,
|
|
data.outputDamageFinalPercent,
|
|
data.outputDamageFinal,
|
|
data.hitPart,
|
|
data.wasCrit,
|
|
data.attackConditions);
|
|
}
|
|
else if (data.damage_form & DF_MAGIC)
|
|
{
|
|
std::string single_adj, plural_adj;
|
|
if (CombatSystem::InqCombatHitAdjectives(data.damage_type, data.outputDamageFinalPercent, single_adj, plural_adj))
|
|
{
|
|
// Killer Phyntos Soldier sears you for 46 points with Incantation of Acid Stream.
|
|
// You chill Killer Phyntos Swarm for 212 points with Halo of Frost II.
|
|
switch (data.damage_type)
|
|
{
|
|
default:
|
|
SendText(csprintf("%s %s you for %d points with %s.", data.GetSourceName().c_str(), plural_adj.c_str(), max(0, data.outputDamageFinal), data.spell_name.c_str()), LTT_MAGIC);
|
|
break;
|
|
case DAMAGE_TYPE::HEALTH_DAMAGE_TYPE:
|
|
case DAMAGE_TYPE::STAMINA_DAMAGE_TYPE:
|
|
case DAMAGE_TYPE::MANA_DAMAGE_TYPE:
|
|
std::string vitalName;
|
|
switch (data.damage_type)
|
|
{
|
|
case HEALTH_DAMAGE_TYPE:
|
|
vitalName = "health";
|
|
break;
|
|
case STAMINA_DAMAGE_TYPE:
|
|
vitalName = "stamina";
|
|
break;
|
|
case MANA_DAMAGE_TYPE:
|
|
vitalName = "mana";
|
|
break;
|
|
}
|
|
bool isRestore = (data.outputDamageFinal < 0);
|
|
SendText(csprintf("%s casts %s and %s %d points of your %s.", data.GetSourceName().c_str(), data.spell_name.c_str(), isRestore ? "restores" : "drains", abs(data.outputDamageFinal), vitalName.c_str()), LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (data.damage_form & DF_IMPACT)
|
|
{
|
|
std::string single_adj;
|
|
if (data.outputDamageFinalPercent > 0.5)
|
|
single_adj = "massive";
|
|
else if (data.outputDamageFinalPercent > 0.25)
|
|
single_adj = "crushing";
|
|
else if (data.outputDamageFinalPercent > 0.1)
|
|
single_adj = "heavy";
|
|
else
|
|
single_adj = "minor";
|
|
|
|
SendText(csprintf("You suffer %d points of %s impact damage.", data.outputDamageFinal, single_adj.c_str()), LTT_COMBAT);
|
|
}
|
|
else if (data.damage_form & DF_HOTSPOT)
|
|
{
|
|
if (pSource)
|
|
{
|
|
std::string activationTalkString;
|
|
if (pSource->m_Qualities.InqString(ACTIVATION_TALK_STRING, activationTalkString))
|
|
{
|
|
char damageNumText[32];
|
|
_itoa(abs(data.outputDamageFinal), damageNumText, 10);
|
|
ReplaceString(activationTalkString, "%i", damageNumText);
|
|
SendText(activationTalkString.c_str(), LTT_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::OnDealtDamage(DamageEventData &data)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pTarget = data.target.lock();
|
|
if (!pTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (data.killingBlow)
|
|
{
|
|
if (pTarget)
|
|
{
|
|
NotifyKillerEvent(data.killer_msg.c_str());
|
|
}
|
|
else
|
|
{
|
|
// NotifyKillerEvent(csprintf("You killed %s!", data.GetTargetName().c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (data.damage_form & DF_PHYSICAL)
|
|
{
|
|
NotifyAttackerEvent(
|
|
data.GetTargetName().c_str(),
|
|
data.damage_type,
|
|
data.outputDamageFinalPercent,
|
|
data.outputDamageFinal,
|
|
data.wasCrit,
|
|
data.attackConditions);
|
|
}
|
|
else if (data.damage_form & DF_MAGIC)
|
|
{
|
|
std::string single_adj, plural_adj;
|
|
if (CombatSystem::InqCombatHitAdjectives(data.damage_type, data.outputDamageFinalPercent, single_adj, plural_adj))
|
|
{
|
|
// Killer Phyntos Soldier sears you for 46 points with Incantation of Acid Stream.
|
|
// You chill Killer Phyntos Swarm for 212 points with Halo of Frost II.
|
|
switch (data.damage_type)
|
|
{
|
|
default:
|
|
SendText(csprintf("%sYou %s %s for %d points with %s.", data.wasCrit ? "Critical hit! " : "", single_adj.c_str(), data.GetTargetName().c_str(), data.outputDamageFinal, data.spell_name.c_str()), LTT_MAGIC);
|
|
break;
|
|
case DAMAGE_TYPE::HEALTH_DAMAGE_TYPE:
|
|
case DAMAGE_TYPE::STAMINA_DAMAGE_TYPE:
|
|
case DAMAGE_TYPE::MANA_DAMAGE_TYPE:
|
|
std::string vitalName;
|
|
switch (data.damage_type)
|
|
{
|
|
case HEALTH_DAMAGE_TYPE:
|
|
vitalName = "health";
|
|
break;
|
|
case STAMINA_DAMAGE_TYPE:
|
|
vitalName = "stamina";
|
|
break;
|
|
case MANA_DAMAGE_TYPE:
|
|
vitalName = "mana";
|
|
break;
|
|
}
|
|
bool isRestore = (data.outputDamageFinal < 0);
|
|
if (pTarget == data.source.lock())
|
|
SendText(csprintf("%sYou cast %s and %s %d points of your %s.", data.wasCrit ? "Critical hit! " : "", data.spell_name.c_str(), isRestore ? "restore" : "drain", abs(data.outputDamageFinal), vitalName.c_str()), LTT_MAGIC);
|
|
else
|
|
SendText(csprintf("%sWith %s you %s %d points of %s %s %s.", data.wasCrit ? "Critical hit! " : "", data.spell_name.c_str(), isRestore ? "restore" : "drain", abs(data.outputDamageFinal), vitalName.c_str(), isRestore ? "to" : "from", data.GetTargetName().c_str()), LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (data.damage_form & DF_IMPACT)
|
|
{
|
|
}
|
|
else if (data.damage_form & DF_HOTSPOT)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::OnRegen(STypeAttribute2nd currentAttrib, int newAmount)
|
|
{
|
|
|
|
m_Qualities.SetAttribute2nd(currentAttrib, newAmount);
|
|
}
|
|
|
|
bool CWeenieObject::IsContainedWithinViewable(DWORD object_id)
|
|
{
|
|
// local container
|
|
if (FindContained(object_id))
|
|
return true;
|
|
|
|
// remote container
|
|
if (std::shared_ptr<CWeenieObject> externalObject = g_pWorld->FindObject(object_id))
|
|
{
|
|
if (std::shared_ptr<CContainerWeenie> externalContainer = externalObject->GetWorldTopLevelContainer())
|
|
{
|
|
if (externalContainer->_openedById == GetID())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::ShouldSave()
|
|
{
|
|
if (_IsPlayer())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::Save()
|
|
{
|
|
CStopWatch watch;
|
|
|
|
if (m_Position.objcell_id)
|
|
{
|
|
m_Qualities.SetPosition(LOCATION_POSITION, m_Position);
|
|
}
|
|
|
|
CWeenieSave save;
|
|
SaveEx(save);
|
|
|
|
BinaryWriter writer;
|
|
save.Pack(&writer);
|
|
bool result = g_pDBIO->CreateOrUpdateWeenie(GetID(), GetTopLevelID(), m_Position.objcell_id >> 16, writer.GetData(), writer.GetSize());
|
|
if (!result)
|
|
SERVER_ERROR << "Failed to save Weenie:" << GetID() << " Owner:" << GetTopLevelID() << " At:" << (m_Position.objcell_id >> 16);
|
|
|
|
double elapsed = watch.GetElapsed();
|
|
if (elapsed >= 0.1)
|
|
{
|
|
SERVER_WARN << csprintf("Took %f seconds to save %s\n", elapsed, GetName().c_str());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CWeenieObject::SaveEx(CWeenieSave &save)
|
|
{
|
|
save.m_SaveInstanceTS = _instance_timestamp;
|
|
save.m_Qualities.CopyFrom(&m_Qualities);
|
|
save.m_ObjDesc = m_ObjDescOverride;
|
|
save.m_WornObjDesc = m_WornObjDesc;
|
|
}
|
|
|
|
void CWeenieObject::LoadEx(CWeenieSave &save)
|
|
{
|
|
m_Qualities.CopyFrom(&save.m_Qualities);
|
|
m_ObjDescOverride = save.m_ObjDesc;
|
|
m_WornObjDesc = save.m_WornObjDesc;
|
|
|
|
// fix for old wield reqs with old skills
|
|
int weaponSkill;
|
|
if (m_Qualities.InqInt(WEAPON_SKILL_INT, weaponSkill, TRUE, FALSE))
|
|
m_Qualities.SetInt(WEAPON_SKILL_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill));
|
|
|
|
int wieldReq;
|
|
|
|
wieldReq = m_Qualities.GetInt(WIELD_REQUIREMENTS_INT, 0);
|
|
if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8)
|
|
{
|
|
if (m_Qualities.InqInt(WIELD_SKILLTYPE_INT, weaponSkill, TRUE, FALSE))
|
|
m_Qualities.SetInt(WIELD_SKILLTYPE_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill));
|
|
}
|
|
|
|
wieldReq = m_Qualities.GetInt(WIELD_REQUIREMENTS_2_INT, 0);
|
|
if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8)
|
|
{
|
|
if (m_Qualities.InqInt(WIELD_SKILLTYPE_2_INT, weaponSkill, TRUE, FALSE))
|
|
m_Qualities.SetInt(WIELD_SKILLTYPE_2_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill));
|
|
}
|
|
|
|
wieldReq = m_Qualities.GetInt(WIELD_REQUIREMENTS_3_INT, 0);
|
|
if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8)
|
|
{
|
|
if (m_Qualities.InqInt(WIELD_SKILLTYPE_3_INT, weaponSkill, TRUE, FALSE))
|
|
m_Qualities.SetInt(WIELD_SKILLTYPE_3_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill));
|
|
}
|
|
|
|
wieldReq = m_Qualities.GetInt(WIELD_REQUIREMENTS_4_INT, 0);
|
|
if (wieldReq == 1 || wieldReq == 2 || wieldReq == 8)
|
|
{
|
|
if (m_Qualities.InqInt(WIELD_SKILLTYPE_4_INT, weaponSkill, TRUE, FALSE))
|
|
m_Qualities.SetInt(WIELD_SKILLTYPE_4_INT, SkillTable::OldToNewSkill((STypeSkill)weaponSkill));
|
|
}
|
|
}
|
|
|
|
bool CWeenieObject::Load()
|
|
{
|
|
#ifndef PUBLIC_BUILD
|
|
CStopWatch watch;
|
|
#endif
|
|
|
|
unsigned int top_level_object_id = 0;
|
|
unsigned int block_id = 0;
|
|
void *data = NULL;
|
|
unsigned long data_length = 0;
|
|
|
|
if (g_pDBIO->GetWeenie(GetID(), &top_level_object_id, &block_id, &data, &data_length))
|
|
{
|
|
BinaryReader reader(data, data_length);
|
|
|
|
CWeenieSave save;
|
|
if (save.UnPack(&reader) && !reader.GetLastError())
|
|
{
|
|
LoadEx(save);
|
|
|
|
#ifndef PUBLIC_BUILD
|
|
double elapsed = watch.GetElapsed();
|
|
if (elapsed >= 0.1)
|
|
{
|
|
LOG_PRIVATE(Temp, Warning, "Took %f seconds to load %s\n", elapsed, GetName().c_str());
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
LOG_PRIVATE(Temp, Error, "Failed to unpack weenie 0x%08X!\n", GetID());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_PRIVATE(Temp, Error, "Failed to load weenie 0x%08X!\n", GetID());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CWeenieObject::Load(DWORD weenie_id)
|
|
{
|
|
#ifndef PUBLIC_BUILD
|
|
CStopWatch watch;
|
|
#endif
|
|
|
|
unsigned int top_level_object_id = 0;
|
|
unsigned int block_id = 0;
|
|
void *data = NULL;
|
|
unsigned long data_length = 0;
|
|
|
|
if (g_pDBIO->GetWeenie(weenie_id, &top_level_object_id, &block_id, &data, &data_length))
|
|
{
|
|
BinaryReader reader(data, data_length);
|
|
|
|
CWeenieSave save;
|
|
if (save.UnPack(&reader) && !reader.GetLastError())
|
|
{
|
|
std::shared_ptr<CWeenieObject> weenie = g_pWeenieFactory->CreateBaseWeenieByType(save.m_Qualities.m_WeenieType, save.m_Qualities.id);
|
|
|
|
if (weenie)
|
|
{
|
|
weenie->SetID(weenie_id);
|
|
weenie->LoadEx(save);
|
|
|
|
#ifndef PUBLIC_BUILD
|
|
double elapsed = watch.GetElapsed();
|
|
if (elapsed >= 0.1)
|
|
{
|
|
LOG_PRIVATE(Temp, Warning, "Took %f seconds to load %s\n", elapsed, weenie->GetName().c_str());
|
|
}
|
|
#endif
|
|
|
|
return weenie;
|
|
}
|
|
else
|
|
{
|
|
LOG_PRIVATE(Temp, Error, "Failed to load abstract weenie 0x%08X!\n", weenie_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_PRIVATE(Temp, Error, "Failed to unpack abstract weenie 0x%08X!\n", weenie_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_PRIVATE(Temp, Error, "Failed to load abstract weenie 0x%08X!\n", weenie_id);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
DWORD CWeenieObject::GetTopLevelID()
|
|
{
|
|
DWORD container_id = InqIIDQuality(CONTAINER_IID, 0);
|
|
if (container_id)
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> pContainer = g_pWorld->FindObject(container_id))
|
|
{
|
|
return pContainer->GetTopLevelID();
|
|
}
|
|
else
|
|
{
|
|
SERVER_ERROR << "Could not find parent container weenie" << container_id << "for" << GetID() << "in GetTopLevelID()";
|
|
}
|
|
}
|
|
|
|
DWORD wielder_id = InqIIDQuality(WIELDER_IID, 0);
|
|
if (wielder_id)
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> pWielder = g_pWorld->FindObject(wielder_id))
|
|
{
|
|
return pWielder->GetTopLevelID();
|
|
}
|
|
else
|
|
{
|
|
SERVER_ERROR << "Could not find parent wielder weenie" << wielder_id << "for" << GetID() << "in GetTopLevelID()"; ;
|
|
}
|
|
}
|
|
|
|
return GetID();
|
|
}
|
|
|
|
int CWeenieObject::UseChecked(std::shared_ptr<CPlayerWeenie> other)
|
|
{
|
|
if (IsBusyOrInAction() || IsDead())
|
|
return WERROR_ACTIONS_LOCKED;
|
|
|
|
return Use(other);
|
|
}
|
|
|
|
bool CWeenieObject::IsLocked()
|
|
{
|
|
return m_Qualities.GetBool(LOCKED_BOOL, FALSE) ? true : false;
|
|
}
|
|
|
|
void CWeenieObject::SetLocked(BOOL locked)
|
|
{
|
|
if (g_pConfig->EverythingUnlocked())
|
|
{
|
|
locked = FALSE;
|
|
}
|
|
|
|
if (InqBoolQuality(LOCKED_BOOL, FALSE) == locked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_Qualities.SetBool(LOCKED_BOOL, locked);
|
|
NotifyBoolStatUpdated(LOCKED_BOOL, false);
|
|
}
|
|
|
|
int CWeenieObject::Activate(DWORD activator_id)
|
|
{
|
|
if (m_Qualities._generator_registry)
|
|
{
|
|
GeneratorRegistryNode *node = m_Qualities._generator_registry->_registry.lookup(activator_id);
|
|
|
|
if (node)
|
|
{
|
|
m_Qualities._generator_registry->_registry.remove(activator_id);
|
|
|
|
// find GetPointer<CWeenieObject>() profile slot
|
|
double delay = -1.0;
|
|
if (m_Qualities._generator_table)
|
|
{
|
|
for (auto &profile : m_Qualities._generator_table->_profile_list)
|
|
{
|
|
if (profile.slot == node->slot)
|
|
{
|
|
delay = profile.delay * g_pConfig->RespawnTimeMultiplier();;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delay >= 0)
|
|
{
|
|
if (!m_Qualities._generator_queue)
|
|
m_Qualities._generator_queue = new GeneratorQueue();
|
|
|
|
GeneratorQueueNode queueNode;
|
|
queueNode.slot = node->slot;
|
|
queueNode.when = Timer::cur_time + delay;
|
|
m_Qualities._generator_queue->_queue.push_back(queueNode);
|
|
|
|
if (_nextRegen < 0.0)
|
|
_nextRegen = Timer::cur_time + (InqFloatQuality(REGENERATION_INTERVAL_FLOAT, 0.0, TRUE) * g_pConfig->RespawnTimeMultiplier());
|
|
}
|
|
}
|
|
}
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
int CWeenieObject::DoUseWithResponse(std::shared_ptr<CWeenieObject> player, std::shared_ptr<CWeenieObject> with)
|
|
{
|
|
if (std::shared_ptr<CPlayerWeenie> player_weenie = player->AsPlayer())
|
|
{
|
|
return player_weenie->UseEx(GetPointer<CWeenieObject>(), with);
|
|
}
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
bool CWeenieObject::IsExecutingEmote()
|
|
{
|
|
if (m_EmoteManager && m_EmoteManager->IsExecutingAlready())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CWeenieObject::ChanceExecuteEmoteSet(DWORD other_id, EmoteCategory category)
|
|
{
|
|
//if (IsExecutingEmote())
|
|
//{
|
|
// return;
|
|
//}
|
|
|
|
if (m_Qualities._emote_table)
|
|
{
|
|
PackableList<EmoteSet> *emoteSetList = m_Qualities._emote_table->_emote_table.lookup(category);
|
|
|
|
if (emoteSetList)
|
|
{
|
|
double dice = Random::GenFloat(0.0, 1.0);
|
|
|
|
for (auto &emoteSet : *emoteSetList)
|
|
{
|
|
if (dice < emoteSet.probability)
|
|
{
|
|
MakeEmoteManager()->ExecuteEmoteSet(emoteSet, other_id);
|
|
|
|
break;
|
|
}
|
|
|
|
// dice -= emoteSet.probability;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::DoUseEmote(std::shared_ptr<CWeenieObject> other)
|
|
{
|
|
if (IsExecutingEmote())
|
|
{
|
|
if (other)
|
|
{
|
|
other->SendText(csprintf("%s is busy.", GetName().c_str()), LTT_DEFAULT);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ChanceExecuteEmoteSet(other->GetID(), Use_EmoteCategory);
|
|
}
|
|
|
|
void CWeenieObject::DoActivationEmote(DWORD activator_id)
|
|
{
|
|
ChanceExecuteEmoteSet(activator_id, Activation_EmoteCategory);
|
|
}
|
|
|
|
int CWeenieObject::Use(std::shared_ptr<CPlayerWeenie> player)
|
|
{
|
|
CGenericUseEvent *useEvent = new CGenericUseEvent();
|
|
useEvent->_target_id = GetID();
|
|
player->ExecuteUseEvent(useEvent);
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
int CWeenieObject::UseWith(std::shared_ptr<CPlayerWeenie> player, std::shared_ptr<CWeenieObject> with)
|
|
{
|
|
if (GetPointer<CWeenieObject>() == with)
|
|
{
|
|
// don't use self
|
|
player->NotifyInventoryFailedEvent(GetID(), WERROR_NONE);
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
CGenericUseEvent *useEvent = new CGenericUseEvent();
|
|
useEvent->_tool_id = GetID();
|
|
useEvent->_target_id = with->GetID();
|
|
useEvent->_do_use_animation = Motion_ClapHands;
|
|
|
|
player->ExecuteUseEvent(useEvent);
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
void CWeenieObject::HandleMoveToDone(DWORD error)
|
|
{
|
|
if (m_UseManager)
|
|
{
|
|
m_UseManager->HandleMoveToDone(error);
|
|
}
|
|
if (m_AttackManager)
|
|
{
|
|
m_AttackManager->HandleMoveToDone(error);
|
|
}
|
|
}
|
|
|
|
BOOL CWeenieObject::IsPK()
|
|
{
|
|
return (InqIntQuality(PLAYER_KILLER_STATUS_INT, 0) & PK_PKStatus) ? true : false;
|
|
}
|
|
|
|
BOOL CWeenieObject::IsPKLite()
|
|
{
|
|
return (InqIntQuality(PLAYER_KILLER_STATUS_INT, 0) & PKLite_PKStatus) ? true : false;
|
|
}
|
|
|
|
BOOL CWeenieObject::IsImpenetrable()
|
|
{
|
|
return (InqIntQuality(PLAYER_KILLER_STATUS_INT, 0) & Free_PKStatus) ? true : false;
|
|
}
|
|
|
|
bool CWeenieObject::HasFellowship()
|
|
{
|
|
std::string dummy;
|
|
if (m_Qualities.InqString(FELLOWSHIP_STRING, dummy))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CWeenieObject::LeaveFellowship()
|
|
{
|
|
std::string fellowshipName;
|
|
if (m_Qualities.InqString(FELLOWSHIP_STRING, fellowshipName))
|
|
{
|
|
g_pFellowshipManager->Quit(fellowshipName, GetID());
|
|
m_Qualities.RemoveString(FELLOWSHIP_STRING);
|
|
}
|
|
}
|
|
|
|
Fellowship *CWeenieObject::GetFellowship()
|
|
{
|
|
std::string fellowshipName;
|
|
if (m_Qualities.InqString(FELLOWSHIP_STRING, fellowshipName))
|
|
{
|
|
return g_pFellowshipManager->GetFellowship(fellowshipName);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
COMBAT_MODE CWeenieObject::GetEquippedCombatMode()
|
|
{
|
|
switch (InqIntQuality(COMBAT_USE_INT, COMBAT_USE::COMBAT_USE_NONE))
|
|
{
|
|
case COMBAT_USE_SHIELD:
|
|
case COMBAT_USE_TWO_HANDED:
|
|
case COMBAT_USE_MELEE:
|
|
return COMBAT_MODE::MELEE_COMBAT_MODE;
|
|
case COMBAT_USE_MISSILE:
|
|
return COMBAT_MODE::MISSILE_COMBAT_MODE;
|
|
default:
|
|
if (InqIntQuality(DEFAULT_COMBAT_STYLE_INT, CombatStyle::Undef_CombatStyle) & Magic_CombatStyle)
|
|
return COMBAT_MODE::MAGIC_COMBAT_MODE;
|
|
return COMBAT_MODE::UNDEF_COMBAT_MODE;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CWeenieObject::GetWorldContainer()
|
|
{
|
|
return g_pWorld->FindObject(GetContainerID());
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CWeenieObject::GetWorldWielder()
|
|
{
|
|
return g_pWorld->FindObject(GetWielderID());
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CWeenieObject::GetWorldOwner()
|
|
{
|
|
if (IsContained())
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> owner = GetWorldContainer())
|
|
return owner;
|
|
}
|
|
|
|
if (IsWielded())
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> owner = GetWorldWielder())
|
|
return owner;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CWeenieObject::GetWorldTopLevelOwner()
|
|
{
|
|
return g_pWorld->FindObject(GetTopLevelID());
|
|
}
|
|
|
|
std::shared_ptr<CContainerWeenie> CWeenieObject::GetWorldTopLevelContainer()
|
|
{
|
|
DWORD top_level_id = GetTopLevelID();
|
|
|
|
if (top_level_id != GetID())
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> containerObj = g_pWorld->FindObject(top_level_id))
|
|
{
|
|
if (std::shared_ptr<CContainerWeenie> container = containerObj->AsContainer())
|
|
{
|
|
return container;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CWeenieObject::ReleaseFromAnyWeenieParent(bool bBroadcastContainerChange, bool bBroadcastEquipmentChange)
|
|
{
|
|
if (IsContained())
|
|
{
|
|
if (m_bWorldIsAware && bBroadcastContainerChange)
|
|
{
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(IID_StatType, CONTAINER_IID);
|
|
statNotify.Write<DWORD>(0x2DA);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(CONTAINER_IID);
|
|
statNotify.Write<DWORD>(0);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
if (std::shared_ptr<CWeenieObject> containerObj = GetWorldContainer())
|
|
{
|
|
assert(containerObj->AsContainer());
|
|
|
|
if (std::shared_ptr<CContainerWeenie> container = containerObj->AsContainer())
|
|
{
|
|
container->ReleaseContainedItemRecursive(GetPointer<CWeenieObject>());
|
|
}
|
|
}
|
|
|
|
m_Qualities.SetInstanceID(CONTAINER_IID, 0);
|
|
|
|
/*
|
|
if (bBroadcastContainerChange)
|
|
{
|
|
NotifyIIDStatUpdated(CONTAINER_IID, false);
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (IsWielded())
|
|
{
|
|
if (m_bWorldIsAware && bBroadcastEquipmentChange)
|
|
{
|
|
BinaryWriter statNotify;
|
|
BYTE statTS = GetNextStatTimestamp(IID_StatType, WIELDER_IID);
|
|
statNotify.Write<DWORD>(0x2DA);
|
|
statNotify.Write<BYTE>(statTS);
|
|
statNotify.Write<DWORD>(GetID());
|
|
statNotify.Write<DWORD>(WIELDER_IID);
|
|
statNotify.Write<DWORD>(0);
|
|
g_pWorld->BroadcastPVS(GetPointer<CWeenieObject>(), statNotify.GetData(), statNotify.GetSize(), PRIVATE_MSG, FALSE, FALSE);
|
|
}
|
|
|
|
if (std::shared_ptr<CWeenieObject> wielderObj = GetWorldWielder())
|
|
{
|
|
assert(wielderObj->AsContainer());
|
|
|
|
OnUnwield(wielderObj);
|
|
|
|
if (std::shared_ptr<CContainerWeenie> wielder = wielderObj->AsContainer())
|
|
{
|
|
wielder->ReleaseContainedItemRecursive(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
m_Qualities.SetInstanceID(WIELDER_IID, 0);
|
|
|
|
// remove any enchantments
|
|
if (wielderObj->m_Qualities._enchantment_reg)
|
|
{
|
|
PackableListWithJson<DWORD> spells_to_remove;
|
|
|
|
if (wielderObj->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (const auto &entry : *wielderObj->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wielderObj->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (const auto &entry : *wielderObj->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wielderObj->m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
for (const auto &entry : *wielderObj->m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!spells_to_remove.empty())
|
|
{
|
|
wielderObj->m_Qualities._enchantment_reg->RemoveEnchantments(&spells_to_remove);
|
|
|
|
if (wielderObj->AsPlayer())
|
|
{
|
|
BinaryWriter silentRemoveMessage;
|
|
silentRemoveMessage.Write<DWORD>(0x2C8);
|
|
spells_to_remove.Pack(&silentRemoveMessage);
|
|
|
|
wielderObj->SendNetMessage(&silentRemoveMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
wielderObj->CheckVitalRanges();
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
{
|
|
PackableListWithJson<DWORD> spells_to_remove;
|
|
|
|
if (m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
for (const auto &entry : *m_Qualities._enchantment_reg->_cooldown_list)
|
|
{
|
|
if (entry._caster == GetID())
|
|
{
|
|
spells_to_remove.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!spells_to_remove.empty())
|
|
{
|
|
m_Qualities._enchantment_reg->RemoveEnchantments(&spells_to_remove);
|
|
|
|
CheckVitalRanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (bBroadcastEquipmentChange)
|
|
{
|
|
NotifyIIDStatUpdated(WIELDER_IID, false);
|
|
}
|
|
*/
|
|
}
|
|
|
|
_cachedHasOwner = false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CWeenieObject::FindContained(DWORD object_id)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void CWeenieObject::SetWeenieContainer(DWORD container_id)
|
|
{
|
|
m_Qualities.SetInstanceID(CONTAINER_IID, container_id);
|
|
NotifyIIDStatUpdated(CONTAINER_IID, false);
|
|
|
|
RecacheHasOwner();
|
|
}
|
|
|
|
void CWeenieObject::SetWielderID(DWORD wielder_id)
|
|
{
|
|
m_Qualities.SetInstanceID(WIELDER_IID, wielder_id);
|
|
NotifyIIDStatUpdated(WIELDER_IID, false);
|
|
|
|
RecacheHasOwner();
|
|
}
|
|
|
|
bool CWeenieObject::HasOwner()
|
|
{
|
|
RecacheHasOwner();
|
|
return _cachedHasOwner;
|
|
}
|
|
|
|
bool CWeenieObject::CachedHasOwner()
|
|
{
|
|
return _cachedHasOwner;
|
|
}
|
|
|
|
void CWeenieObject::RecacheHasOwner()
|
|
{
|
|
_cachedHasOwner = (IsContained() || IsWielded()) ? true : false;
|
|
}
|
|
|
|
void CWeenieObject::SetWieldedLocation(DWORD inv_loc)
|
|
{
|
|
m_Qualities.SetInt(CURRENT_WIELDED_LOCATION_INT, inv_loc);
|
|
NotifyIntStatUpdated(CURRENT_WIELDED_LOCATION_INT, false);
|
|
}
|
|
|
|
bool CWeenieObject::IsValidWieldLocation(DWORD location)
|
|
{
|
|
if (InqIntQuality(LOCATIONS_INT, 0, TRUE) & location)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CWeenieObject::CanEquipWith(std::shared_ptr<CWeenieObject> other, DWORD otherLocation)
|
|
{
|
|
if (InqIntQuality(CURRENT_WIELDED_LOCATION_INT, 0, TRUE) & otherLocation)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int CWeenieObject::SimulateGiveObject(std::shared_ptr<CContainerWeenie> target_container, std::shared_ptr<CWeenieObject> object_weenie)
|
|
{
|
|
if (!target_container->Container_CanStore(object_weenie))
|
|
return WERROR_GIVE_NOT_ALLOWED;
|
|
|
|
ReleaseContainedItemRecursive(object_weenie);
|
|
|
|
object_weenie->m_Qualities.SetInstanceID(CONTAINER_IID, target_container->GetID());
|
|
object_weenie->_cachedHasOwner = true;
|
|
|
|
SendNetMessage(InventoryMove(object_weenie->GetID(), target_container->GetID(), 0, object_weenie->RequiresPackSlot() ? 1 : 0), PRIVATE_MSG, TRUE);
|
|
|
|
target_container->MakeAware(object_weenie, true);
|
|
target_container->OnReceiveInventoryItem(GetPointer<CWeenieObject>(), object_weenie, 0);
|
|
|
|
if (!InqBoolQuality(NPC_INTERACTS_SILENTLY_BOOL, FALSE) && !target_container->InqBoolQuality(NPC_INTERACTS_SILENTLY_BOOL, FALSE))
|
|
{
|
|
target_container->EmitSound(Sound_ReceiveItem, 1.0f);
|
|
|
|
if (IsCreature())
|
|
{
|
|
int amount = object_weenie->InqIntQuality(STACK_SIZE_INT, 1);
|
|
if (amount > 1)
|
|
{
|
|
SendText(csprintf("You give %s %s %s.", target_container->GetName().c_str(), FormatNumberString(amount).c_str(), object_weenie->GetPluralName().c_str()), LTT_DEFAULT);
|
|
target_container->SendText(csprintf("%s gives you %s %s.", GetName().c_str(), FormatNumberString(amount).c_str(), object_weenie->GetPluralName().c_str()), LTT_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
SendText(csprintf("You give %s %s.", target_container->GetName().c_str(), object_weenie->GetName().c_str()), LTT_DEFAULT);
|
|
target_container->SendText(csprintf("%s gives you %s.", GetName().c_str(), object_weenie->GetName().c_str()), LTT_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
|
|
target_container->DebugValidate();
|
|
DebugValidate();
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
void CWeenieObject::SimulateGiveObject(class std::shared_ptr<CContainerWeenie> target_container, DWORD wcid, int amount, int ptid, float shade, int bondedType)
|
|
{
|
|
std::shared_ptr<CWeenieObject> object_weenie = g_pWeenieFactory->CreateWeenieByClassID(wcid, NULL, false);
|
|
|
|
if (!object_weenie)
|
|
return;
|
|
|
|
if (ptid)
|
|
{
|
|
object_weenie->m_Qualities.SetInt(PALETTE_TEMPLATE_INT, ptid);
|
|
|
|
if (shade > 0.0)
|
|
object_weenie->m_Qualities.SetFloat(SHADE_FLOAT, shade);
|
|
}
|
|
|
|
if (bondedType != 0)
|
|
object_weenie->m_Qualities.SetInt(BONDED_INT, bondedType);
|
|
|
|
if (amount > 1)
|
|
{
|
|
int maxStackSize = object_weenie->m_Qualities.GetInt(MAX_STACK_SIZE_INT, 1);
|
|
if (amount <= maxStackSize)
|
|
object_weenie->SetStackSize(amount);
|
|
else
|
|
{
|
|
int amountOfStacks = amount / maxStackSize;
|
|
int restStackSize = amount % maxStackSize;
|
|
for (int i = 0; i < amountOfStacks; i++)
|
|
SimulateGiveObject(target_container, wcid, maxStackSize, ptid, shade, bondedType);
|
|
if (restStackSize > 0)
|
|
object_weenie->SetStackSize(restStackSize);
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
object_weenie->SetID(g_pWorld->GenerateGUID(eDynamicGUID));
|
|
|
|
if (!g_pWorld->CreateEntity(object_weenie, false))
|
|
return;
|
|
|
|
if (!target_container->Container_CanStore(object_weenie))
|
|
{
|
|
g_pWorld->RemoveEntity(object_weenie);
|
|
return;
|
|
}
|
|
|
|
ReleaseContainedItemRecursive(object_weenie);
|
|
|
|
object_weenie->m_Qualities.SetInstanceID(CONTAINER_IID, target_container->GetID());
|
|
object_weenie->_cachedHasOwner = true;
|
|
|
|
SendNetMessage(InventoryMove(object_weenie->GetID(), target_container->GetID(), 0, object_weenie->RequiresPackSlot() ? 1 : 0), PRIVATE_MSG, TRUE);
|
|
|
|
target_container->MakeAware(object_weenie, true);
|
|
target_container->OnReceiveInventoryItem(GetPointer<CWeenieObject>(), object_weenie, 0);
|
|
|
|
if (!InqBoolQuality(NPC_INTERACTS_SILENTLY_BOOL, FALSE) && !target_container->InqBoolQuality(NPC_INTERACTS_SILENTLY_BOOL, FALSE))
|
|
{
|
|
target_container->EmitSound(Sound_ReceiveItem, 1.0f);
|
|
|
|
if (IsCreature())
|
|
{
|
|
int amount = object_weenie->InqIntQuality(STACK_SIZE_INT, 1);
|
|
if (amount > 1)
|
|
{
|
|
SendText(csprintf("You give %s %s %s.", target_container->GetName().c_str(), FormatNumberString(amount).c_str(), object_weenie->GetPluralName().c_str()), LTT_DEFAULT);
|
|
target_container->SendText(csprintf("%s gives you %s %s.", GetName().c_str(), FormatNumberString(amount).c_str(), object_weenie->GetPluralName().c_str()), LTT_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
SendText(csprintf("You give %s %s.", target_container->GetName().c_str(), object_weenie->GetName().c_str()), LTT_DEFAULT);
|
|
target_container->SendText(csprintf("%s gives you %s.", GetName().c_str(), object_weenie->GetName().c_str()), LTT_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
|
|
target_container->DebugValidate();
|
|
DebugValidate();
|
|
}
|
|
|
|
void CWeenieObject::SendNetMessage(void *_data, DWORD _len, WORD _group, BOOL _event)
|
|
{
|
|
}
|
|
|
|
void CWeenieObject::SendNetMessage(BinaryWriter *_food, WORD _group, BOOL _event, BOOL should_delete)
|
|
{
|
|
if (should_delete)
|
|
{
|
|
delete _food;
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SendNetMessageToTopMost(void *_data, DWORD _len, WORD _group, BOOL _event)
|
|
{
|
|
if (AsPlayer())
|
|
{
|
|
// obviously we are the top most
|
|
SendNetMessage(_data, _len, _group, _event);
|
|
return;
|
|
}
|
|
|
|
if (std::shared_ptr<CWeenieObject> topMost = GetWorldTopLevelOwner())
|
|
{
|
|
// we are wielded or contained, send to topmost owner
|
|
topMost->SendNetMessage(_data, _len, _group, _event);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SendNetMessageToTopMost(BinaryWriter *_food, WORD _group, BOOL _event, BOOL should_delete)
|
|
{
|
|
if (AsPlayer())
|
|
{
|
|
// obviously we are the top most
|
|
SendNetMessage(_food, _group, _event, should_delete);
|
|
return;
|
|
}
|
|
|
|
if (std::shared_ptr<CWeenieObject> topMost = GetWorldTopLevelOwner())
|
|
{
|
|
// we are wielded or contained, send to topmost owner
|
|
topMost->SendNetMessage(_food, _group, _event, should_delete);
|
|
return;
|
|
}
|
|
|
|
if (should_delete)
|
|
{
|
|
delete _food;
|
|
}
|
|
}
|
|
|
|
int CWeenieObject::CraftObject(std::shared_ptr<CContainerWeenie> target_container, std::shared_ptr<CWeenieObject> object_weenie)
|
|
{
|
|
if (!target_container->Container_CanStore(object_weenie))
|
|
return WERROR_GIVE_NOT_ALLOWED;
|
|
|
|
object_weenie->m_Qualities.SetInstanceID(CONTAINER_IID, target_container->GetID());
|
|
object_weenie->_cachedHasOwner = true;
|
|
|
|
SendNetMessage(InventoryMove(object_weenie->GetID(), target_container->GetID(), 0, object_weenie->RequiresPackSlot() ? 1 : 0), PRIVATE_MSG, TRUE);
|
|
|
|
target_container->MakeAware(object_weenie, true);
|
|
target_container->OnReceiveInventoryItem(GetPointer<CWeenieObject>(), object_weenie, 0);
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
void CWeenieObject::CheckDeath(std::shared_ptr<CWeenieObject> source, DAMAGE_TYPE dt)
|
|
{
|
|
if (IsDead())
|
|
{
|
|
if (source == GetPointer<CWeenieObject>())
|
|
{
|
|
NotifyVictimEvent("You died!");
|
|
|
|
if (_IsPlayer())
|
|
{
|
|
NotifyDeathMessage(source->GetID(), csprintf("%s died!", GetName().c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NotifyVictimEvent(csprintf("You were killed by %s!", source->GetName().c_str()));
|
|
source->NotifyKillerEvent(csprintf("You killed %s!", GetName().c_str()));
|
|
|
|
if (_IsPlayer())
|
|
{
|
|
NotifyDeathMessage(source->GetID(), csprintf("%s killed %s!", source->GetName().c_str(), GetName().c_str()));
|
|
}
|
|
|
|
// notify the atttacker they did damage
|
|
source->GivePerksForKill(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
OnDeath(source->GetID());
|
|
}
|
|
}
|
|
|
|
DWORD CWeenieObject::GetMagicDefense()
|
|
{
|
|
DWORD defenseSkill = 0;
|
|
InqSkill(MAGIC_DEFENSE_SKILL, defenseSkill, FALSE);
|
|
return defenseSkill;
|
|
}
|
|
|
|
bool CWeenieObject::TryMagicResist(DWORD magicSkill)
|
|
{
|
|
DWORD defenseSkill = GetMagicDefense();
|
|
|
|
double defenseMod = GetMagicDefenseModUsingWielded();
|
|
if (AsContainer())
|
|
{
|
|
for (auto item : AsContainer()->m_Wielded)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pItem = item.lock();
|
|
|
|
if (!pItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pItem->GetImbueEffects() & MagicDefense_ImbuedEffectType)
|
|
defenseSkill += 1;
|
|
}
|
|
}
|
|
defenseSkill = (int)round((double)defenseSkill * defenseMod);
|
|
|
|
return ::TryMagicResist(magicSkill, defenseSkill);
|
|
}
|
|
|
|
double CWeenieObject::GetMeleeDefenseModUsingWielded()
|
|
{
|
|
return GetMeleeDefenseMod();
|
|
}
|
|
|
|
double CWeenieObject::GetMeleeDefenseMod()
|
|
{
|
|
double mod = InqFloatQuality(WEAPON_DEFENSE_FLOAT, 1.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(WEAPON_DEFENSE_FLOAT, &mod);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
mod *= wielder->GetMeleeDefenseMod();
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
double CWeenieObject::GetMissileDefenseModUsingWielded()
|
|
{
|
|
return GetMissileDefenseMod();
|
|
}
|
|
|
|
double CWeenieObject::GetMissileDefenseMod()
|
|
{
|
|
double mod = InqFloatQuality(WEAPON_MISSILE_DEFENSE_FLOAT, 1.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(WEAPON_MISSILE_DEFENSE_FLOAT, &mod);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
mod *= wielder->GetMissileDefenseMod();
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
double CWeenieObject::GetMagicDefenseModUsingWielded()
|
|
{
|
|
return GetMagicDefenseMod();
|
|
}
|
|
|
|
double CWeenieObject::GetMagicDefenseMod()
|
|
{
|
|
double mod = InqFloatQuality(WEAPON_MAGIC_DEFENSE_FLOAT, 1.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(WEAPON_MAGIC_DEFENSE_FLOAT, &mod);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
mod *= wielder->GetMagicDefenseMod();
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
double CWeenieObject::GetOffenseMod()
|
|
{
|
|
double mod = InqFloatQuality(WEAPON_OFFENSE_FLOAT, 1.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(WEAPON_OFFENSE_FLOAT, &mod);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
mod *= wielder->GetOffenseMod();
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
double CWeenieObject::GetCrushingBlowMultiplier()
|
|
{
|
|
double mod = InqFloatQuality(CRITICAL_MULTIPLIER_FLOAT, 0.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(CRITICAL_MULTIPLIER_FLOAT, &mod);
|
|
|
|
return mod;
|
|
}
|
|
|
|
double CWeenieObject::GetBitingStrikeFrequency()
|
|
{
|
|
double mod = InqFloatQuality(CRITICAL_FREQUENCY_FLOAT, 0.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(CRITICAL_FREQUENCY_FLOAT, &mod);
|
|
|
|
return mod;
|
|
}
|
|
|
|
void CWeenieObject::AddImbueEffect(ImbuedEffectType effect)
|
|
{
|
|
DWORD imbueEffects = GetImbueEffects();
|
|
|
|
if (imbueEffects & effect)
|
|
return; //it's already present.
|
|
|
|
imbueEffects |= effect;
|
|
m_Qualities.SetInt(IMBUED_EFFECT_INT, (int)effect);
|
|
|
|
//imbue effects 2 to 5 are not used by our dataset.
|
|
//if (!InqIntQuality(IMBUED_EFFECT_INT, 0, FALSE))
|
|
// m_Qualities.SetInt(IMBUED_EFFECT_INT, (int)effect);
|
|
//else if (!InqIntQuality(IMBUED_EFFECT_2_INT, 0, FALSE))
|
|
// m_Qualities.SetInt(IMBUED_EFFECT_2_INT, (int)effect);
|
|
//else if (!InqIntQuality(IMBUED_EFFECT_3_INT, 0, FALSE))
|
|
// m_Qualities.SetInt(IMBUED_EFFECT_3_INT, (int)effect);
|
|
//else if (!InqIntQuality(IMBUED_EFFECT_4_INT, 0, FALSE))
|
|
// m_Qualities.SetInt(IMBUED_EFFECT_4_INT, (int)effect);
|
|
//else if (!InqIntQuality(IMBUED_EFFECT_5_INT, 0, FALSE))
|
|
// m_Qualities.SetInt(IMBUED_EFFECT_5_INT, (int)effect);
|
|
//else
|
|
// return false;
|
|
//return true;
|
|
}
|
|
|
|
DWORD CWeenieObject::GetImbueEffects()
|
|
{
|
|
return InqIntQuality(IMBUED_EFFECT_INT, 0, FALSE);
|
|
|
|
//imbue effects 2 to 5 are not used by our dataset.
|
|
//DWORD imbuedEffect = 0;
|
|
|
|
//imbuedEffect |= (DWORD) InqIntQuality(IMBUED_EFFECT_INT, 0, FALSE);
|
|
//imbuedEffect |= (DWORD) InqIntQuality(IMBUED_EFFECT_2_INT, 0, FALSE);
|
|
//imbuedEffect |= (DWORD) InqIntQuality(IMBUED_EFFECT_2_INT, 0, FALSE);
|
|
//imbuedEffect |= (DWORD) InqIntQuality(IMBUED_EFFECT_3_INT, 0, FALSE);
|
|
//imbuedEffect |= (DWORD) InqIntQuality(IMBUED_EFFECT_4_INT, 0, FALSE);
|
|
//imbuedEffect |= (DWORD) InqIntQuality(IMBUED_EFFECT_5_INT, 0, FALSE);
|
|
|
|
//return imbuedEffect;
|
|
}
|
|
|
|
double CWeenieObject::GetManaConversionMod()
|
|
{
|
|
double mod = InqFloatQuality(MANA_CONVERSION_MOD_FLOAT, 0.0, FALSE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantFloat(MANA_CONVERSION_MOD_FLOAT, &mod);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
mod *= (1.0 + wielder->GetManaConversionMod());
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
DWORD CWeenieObject::GetEffectiveManaConversionSkill()
|
|
{
|
|
SKILL_ADVANCEMENT_CLASS sac;
|
|
DWORD manaConvSkill = 0;
|
|
|
|
//mana conversion is an unuseable untrained skill, which means it's always 0 if you don't have it at least trained.
|
|
if (!m_Qualities.InqSkillAdvancementClass(MANA_CONVERSION_SKILL, sac) || sac >= TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
InqSkill(MANA_CONVERSION_SKILL, manaConvSkill, FALSE);
|
|
|
|
if (std::shared_ptr<CWeenieObject> wand = GetWieldedCaster())
|
|
{
|
|
double manaMod = wand->GetManaConversionMod();
|
|
|
|
if (manaMod > 0.0)
|
|
{
|
|
manaConvSkill = (DWORD)max(0, ((int)manaConvSkill * (1.0 + manaMod)));
|
|
}
|
|
}
|
|
|
|
return manaConvSkill;
|
|
}
|
|
|
|
int CWeenieObject::GetAttackTime()
|
|
{
|
|
int speed = 0;
|
|
m_Qualities.InqInt(WEAPON_TIME_INT, speed, TRUE, TRUE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantInt(WEAPON_TIME_INT, &speed, TRUE);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
speed += wielder->GetAttackTime();
|
|
}
|
|
|
|
/*
|
|
if (speed < 0)
|
|
speed = 0;
|
|
if (speed > 100)
|
|
speed = 100;
|
|
*/
|
|
|
|
// 0 = 2.0
|
|
// 100 = 1.0
|
|
// 1.0 / (1.0 + ((100 - speed) * (0.0075)))
|
|
return speed;
|
|
}
|
|
|
|
int CWeenieObject::GetAttackTimeUsingWielded()
|
|
{
|
|
return GetAttackTime();
|
|
}
|
|
|
|
int CWeenieObject::GetAttackDamage()
|
|
{
|
|
int damage = InqIntQuality(DAMAGE_INT, 0, TRUE);
|
|
|
|
if (m_Qualities._enchantment_reg)
|
|
m_Qualities._enchantment_reg->EnchantInt(DAMAGE_INT, &damage, FALSE);
|
|
|
|
std::shared_ptr<CWeenieObject> wielder = GetWorldWielder();
|
|
if (wielder && InqIntQuality(RESIST_MAGIC_INT, 0, FALSE) < 9999)
|
|
{
|
|
damage += wielder->GetAttackDamage();
|
|
}
|
|
|
|
return damage;
|
|
}
|
|
|
|
bool CWeenieObject::IsInPeaceMode()
|
|
{
|
|
return get_minterp()->interpreted_state.current_style == Motion_NonCombat;
|
|
}
|
|
|
|
bool CWeenieObject::TryMeleeEvade(DWORD attackSkill)
|
|
{
|
|
if (GetStamina() < 1) // when we're out of stamina our defense skill is lowered to 0.
|
|
return false;
|
|
|
|
DWORD defenseSkill = 0;
|
|
InqSkill(MELEE_DEFENSE_SKILL, defenseSkill, FALSE);
|
|
|
|
double defenseMod = GetMeleeDefenseModUsingWielded();
|
|
|
|
bool inCombatMode = true;
|
|
if (get_minterp()->interpreted_state.current_style == Motion_NonCombat)
|
|
{
|
|
inCombatMode = false;
|
|
switch (get_minterp()->interpreted_state.forward_command)
|
|
{
|
|
default:
|
|
defenseMod *= 0.8;
|
|
break;
|
|
case Motion_Crouch:
|
|
defenseMod *= 0.5;
|
|
break;
|
|
case Motion_Sitting:
|
|
defenseMod *= 0.25;
|
|
break;
|
|
case Motion_Sleeping:
|
|
defenseMod *= 0.1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
defenseSkill = (int)round((double)defenseSkill * defenseMod);
|
|
|
|
bool success = ::TryMeleeEvade(attackSkill, defenseSkill);
|
|
|
|
if (inCombatMode)
|
|
{
|
|
if (success)
|
|
{
|
|
if (AsPlayer())
|
|
{
|
|
//the higher a player's Endurance, the more likely they are not to use a point of stamina to successfully evade a missile or melee attack.
|
|
//A player is required to have Melee Defense for melee attacks or Missile Defense for missile attacks trained or specialized in order
|
|
//for GetPointer<CWeenieObject>() specific ability to work. This benefit is tied to Endurance only, and it caps out at around a 75% chance to avoid losing
|
|
//a point of stamina per successful evasion.
|
|
SKILL_ADVANCEMENT_CLASS defenseSkillSAC = SKILL_ADVANCEMENT_CLASS::UNTRAINED_SKILL_ADVANCEMENT_CLASS;
|
|
m_Qualities.InqSkillAdvancementClass(STypeSkill::MELEE_DEFENSE_SKILL, defenseSkillSAC);
|
|
|
|
if (defenseSkillSAC >= SKILL_ADVANCEMENT_CLASS::TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
{
|
|
DWORD endurance = 0;
|
|
m_Qualities.InqAttribute(ENDURANCE_ATTRIBUTE, endurance, true);
|
|
float noStaminaUseChance = ((float)endurance - 100.0) / 400.0; //made up formula: 75% reduction at 400 endurance.
|
|
noStaminaUseChance = min(max(noStaminaUseChance, 0.0), 0.75);
|
|
if (Random::RollDice(0.0, 1.0) > noStaminaUseChance)
|
|
AdjustStamina(-1); // failed the roll, use stamina.
|
|
}
|
|
else
|
|
AdjustStamina(-1); // defense skill not trained/specialized, use stamina.
|
|
}
|
|
else
|
|
AdjustStamina(-1);
|
|
}
|
|
else
|
|
AdjustStamina(-1); // while in combat all evasion failures consume 1 stamina.
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool CWeenieObject::TryMissileEvade(DWORD attackSkill)
|
|
{
|
|
if (GetStamina() < 1) // when we're out of stamine our defense skill is lowered to 0.
|
|
return false;
|
|
|
|
DWORD defenseSkill = 0;
|
|
InqSkill(MISSILE_DEFENSE_SKILL, defenseSkill, FALSE);
|
|
|
|
double defenseMod = GetMissileDefenseModUsingWielded();
|
|
if (AsContainer())
|
|
{
|
|
for (auto item : AsContainer()->m_Wielded)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pItem = item.lock();
|
|
|
|
if (!pItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pItem->GetImbueEffects() & MissileDefense_ImbuedEffectType)
|
|
defenseMod += 0.01;
|
|
}
|
|
}
|
|
|
|
bool inCombatMode = true;
|
|
if (get_minterp()->interpreted_state.current_style == Motion_NonCombat)
|
|
{
|
|
inCombatMode = false;
|
|
switch (get_minterp()->interpreted_state.forward_command)
|
|
{
|
|
default:
|
|
defenseMod *= 0.8;
|
|
break;
|
|
case Motion_Crouch:
|
|
defenseMod *= 0.5;
|
|
break;
|
|
case Motion_Sitting:
|
|
defenseMod *= 0.25;
|
|
break;
|
|
case Motion_Sleeping:
|
|
defenseMod *= 0.1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
defenseSkill = (int)round((double)defenseSkill * defenseMod);
|
|
|
|
bool success = ::TryMissileEvade(attackSkill, defenseSkill);
|
|
|
|
if (inCombatMode)
|
|
{
|
|
if (success)
|
|
{
|
|
//the higher a player's Endurance, the more likely they are not to use a point of stamina to successfully evade a missile or melee attack.
|
|
//A player is required to have Melee Defense for melee attacks or Missile Defense for missile attacks trained or specialized in order
|
|
//for GetPointer<CWeenieObject>() specific ability to work. This benefit is tied to Endurance only, and it caps out at around a 75% chance to avoid losing
|
|
//a point of stamina per successful evasion.
|
|
SKILL_ADVANCEMENT_CLASS defenseSkillSAC = SKILL_ADVANCEMENT_CLASS::UNTRAINED_SKILL_ADVANCEMENT_CLASS;
|
|
m_Qualities.InqSkillAdvancementClass(STypeSkill::MISSILE_DEFENSE_SKILL, defenseSkillSAC);
|
|
|
|
if (defenseSkillSAC >= SKILL_ADVANCEMENT_CLASS::TRAINED_SKILL_ADVANCEMENT_CLASS)
|
|
{
|
|
DWORD endurance = 0;
|
|
m_Qualities.InqAttribute(ENDURANCE_ATTRIBUTE, endurance, true);
|
|
float noStaminaUseChance = ((float)endurance - 100.0) / 400.0; //made up formula: 75% reduction at 400 endurance.
|
|
noStaminaUseChance = min(max(noStaminaUseChance, 0.0), 0.75);
|
|
if (Random::RollDice(0.0, 1.0) > noStaminaUseChance)
|
|
AdjustStamina(-1); // failed the roll, use stamina.
|
|
}
|
|
else
|
|
AdjustStamina(-1); // defense skill not trained/specialized, use stamina.
|
|
}
|
|
else
|
|
AdjustStamina(-1); // while in combat all evasion failures consume 1 stamina.
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void CWeenieObject::HandleAggro(std::shared_ptr<CWeenieObject> attacker)
|
|
{
|
|
}
|
|
|
|
void CWeenieObject::OnWield(std::shared_ptr<CWeenieObject> wielder)
|
|
{
|
|
if (wielder)
|
|
ChanceExecuteEmoteSet(wielder->GetID(), Wield_EmoteCategory);
|
|
}
|
|
|
|
void CWeenieObject::OnUnwield(std::shared_ptr<CWeenieObject> wielder)
|
|
{
|
|
if (wielder)
|
|
ChanceExecuteEmoteSet(wielder->GetID(), UnWield_EmoteCategory);
|
|
}
|
|
|
|
void CWeenieObject::OnPickedUp(std::shared_ptr<CWeenieObject> pickedUpBy)
|
|
{
|
|
if (pickedUpBy)
|
|
ChanceExecuteEmoteSet(pickedUpBy->GetID(), PickUp_EmoteCategory);
|
|
}
|
|
|
|
void CWeenieObject::OnDropped(std::shared_ptr<CWeenieObject> droppedBy)
|
|
{
|
|
if (droppedBy)
|
|
ChanceExecuteEmoteSet(droppedBy->GetID(), Drop_EmoteCategory);
|
|
}
|
|
|
|
void CWeenieObject::OnTeleported()
|
|
{
|
|
if (m_UseManager)
|
|
m_UseManager->Cancel();
|
|
|
|
if (m_SpellcastingManager)
|
|
m_SpellcastingManager->Cancel();
|
|
|
|
if (m_EmoteManager)
|
|
m_EmoteManager->Cancel();
|
|
|
|
if (m_AttackManager)
|
|
m_AttackManager->Cancel();
|
|
}
|
|
|
|
void CWeenieObject::Movement_Teleport(const Position &position, bool bWasDeath)
|
|
{
|
|
assert(position.objcell_id);
|
|
assert(!parent.lock());
|
|
|
|
OnTeleported();
|
|
|
|
last_move_was_autonomous = false;
|
|
|
|
DWORD dwOldLastCell = GetLandcell();
|
|
|
|
std::shared_ptr<CWeenieObject> pWeenie = weenie_obj.lock();
|
|
|
|
if (pWeenie && pWeenie->_IsPlayer())
|
|
EnterPortal(dwOldLastCell);
|
|
|
|
SetPositionStruct sps;
|
|
sps.pos = position;
|
|
sps.SetFlags(SEND_POSITION_EVENT_SPF | SLIDE_SPF | PLACEMENT_SPF | TELEPORT_SPF);
|
|
SetPosition(sps);
|
|
|
|
_teleport_timestamp += 2;
|
|
|
|
if (bWasDeath)
|
|
{
|
|
// Send position and movement -- only seen GetPointer<CWeenieObject>() happen on death so far
|
|
BinaryWriter positionAndMovement;
|
|
positionAndMovement.Write<DWORD>(0xF619);
|
|
positionAndMovement.Write<DWORD>(GetID());
|
|
|
|
PositionPack position;
|
|
position.position = m_Position;
|
|
position.has_contact = (transient_state & CONTACT_TS && transient_state & ON_WALKABLE_TS) ? TRUE : FALSE;
|
|
position.placement_id = GetActivePlacementFrameID();
|
|
position.instance_timestamp = _instance_timestamp;
|
|
position.position_timestamp = ++_position_timestamp;
|
|
position.teleport_timestamp = _teleport_timestamp;
|
|
// position.velocity = pEntity->m_velocityVector;
|
|
position.force_position_timestamp = _force_position_timestamp;
|
|
position.Pack(&positionAndMovement);
|
|
|
|
positionAndMovement.Write<WORD>(++_movement_timestamp);
|
|
positionAndMovement.Write<WORD>(_server_control_timestamp);
|
|
positionAndMovement.Write<BYTE>(last_move_was_autonomous);
|
|
positionAndMovement.Align();
|
|
|
|
// last_move_was_autonomous = true;
|
|
|
|
BinaryWriter *AnimInfo = Animation_GetAnimationInfo();
|
|
positionAndMovement.Write(AnimInfo);
|
|
delete AnimInfo;
|
|
|
|
g_pWorld->BroadcastPVS(dwOldLastCell, positionAndMovement.GetData(), positionAndMovement.GetSize());
|
|
}
|
|
|
|
Movement_UpdatePos();
|
|
}
|
|
|
|
bool CWeenieObject::IsAttacking()
|
|
{
|
|
return (m_AttackManager && m_AttackManager->IsAttacking());
|
|
}
|
|
|
|
void CWeenieObject::TryMeleeAttack(DWORD target_id, ATTACK_HEIGHT height, float power, DWORD motion)
|
|
{
|
|
}
|
|
|
|
void CWeenieObject::TryMissileAttack(DWORD target_id, ATTACK_HEIGHT height, float power, DWORD motion)
|
|
{
|
|
}
|
|
|
|
int CWeenieObject::GetStructureNum()
|
|
{
|
|
int numUses = 1;
|
|
m_Qualities.InqInt(STRUCTURE_INT, numUses, TRUE);
|
|
return numUses;
|
|
}
|
|
|
|
int CWeenieObject::GetStackOrStructureNum()
|
|
{
|
|
int dummy;
|
|
if (m_Qualities.InqInt(STRUCTURE_INT, dummy, TRUE))
|
|
return dummy;
|
|
else
|
|
return InqIntQuality(STACK_SIZE_INT, 1);
|
|
}
|
|
|
|
void CWeenieObject::DecrementStackOrStructureNum(int amount, bool bDestroyOnZero)
|
|
{
|
|
int dummy;
|
|
if (m_Qualities.InqInt(STRUCTURE_INT, dummy, TRUE))
|
|
DecrementStructureNum(amount, bDestroyOnZero);
|
|
else
|
|
DecrementStackNum(amount, bDestroyOnZero);
|
|
}
|
|
|
|
void CWeenieObject::DecrementStackNum(int amount, bool bDestroyOnZero)
|
|
{
|
|
int usesLeft = InqIntQuality(STACK_SIZE_INT, 1);
|
|
|
|
usesLeft -= amount;
|
|
|
|
if (usesLeft <= 0)
|
|
{
|
|
if (bDestroyOnZero)
|
|
Remove();
|
|
else
|
|
SetStackSize(0);
|
|
}
|
|
else
|
|
SetStackSize(usesLeft);
|
|
}
|
|
|
|
void CWeenieObject::DecrementStructureNum(int amount, bool bDestroyOnZero)
|
|
{
|
|
int usesLeft = InqIntQuality(STRUCTURE_INT, 1);
|
|
|
|
usesLeft -= amount;
|
|
|
|
if (usesLeft <= 0)
|
|
{
|
|
m_Qualities.SetInt(STRUCTURE_INT, 0);
|
|
if (bDestroyOnZero)
|
|
Remove();
|
|
else
|
|
NotifyIntStatUpdated(STRUCTURE_INT, false);
|
|
}
|
|
else
|
|
{
|
|
m_Qualities.SetInt(STRUCTURE_INT, usesLeft);
|
|
NotifyIntStatUpdated(STRUCTURE_INT, false);
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::SetStackSize(DWORD stackSize)
|
|
{
|
|
if (!m_Qualities.GetInt(MAX_STACK_SIZE_INT, 0))
|
|
{
|
|
// not a stackable... ??
|
|
return;
|
|
}
|
|
CWeenieDefaults *weenieDefs = g_pWeenieFactory->GetWeenieDefaults(m_Qualities.GetID());
|
|
|
|
m_Qualities.SetInt(STACK_SIZE_INT, stackSize);
|
|
//if (m_bWorldIsAware)
|
|
// NotifyIntStatUpdated(STACK_SIZE_INT, false);
|
|
|
|
m_Qualities.SetInt(ENCUMB_VAL_INT, stackSize * weenieDefs->m_Qualities.GetInt(STACK_UNIT_ENCUMB_INT, 0));
|
|
//if (m_bWorldIsAware)
|
|
// NotifyIntStatUpdated(ENCUMB_VAL_INT, false);
|
|
|
|
m_Qualities.SetInt(VALUE_INT, stackSize * weenieDefs->m_Qualities.GetInt(STACK_UNIT_VALUE_INT, 0));
|
|
//if (m_bWorldIsAware)
|
|
// NotifyIntStatUpdated(VALUE_INT, false);
|
|
|
|
if (m_bWorldIsAware)
|
|
NotifyStackSizeUpdated(false);
|
|
|
|
if (std::shared_ptr<CWeenieObject> owner = GetWorldTopLevelOwner())
|
|
{
|
|
owner->RecalculateEncumbrance();
|
|
if (owner->AsPlayer() && m_Qualities.id == W_COINSTACK_CLASS)
|
|
owner->RecalculateCoinAmount();
|
|
|
|
}
|
|
}
|
|
|
|
bool CWeenieObject::IsAvatarJumpsuit()
|
|
{
|
|
return m_Qualities.id >= 7000000 && m_Qualities.id <= 7010000;
|
|
}
|
|
|
|
bool CWeenieObject::LearnSpell(DWORD spell_id, bool showTextAndEffect)
|
|
{
|
|
if (spell_id)
|
|
{
|
|
if (const CSpellBase *spell = MagicSystem::GetSpellTable()->GetSpellBase(spell_id))
|
|
{
|
|
if (m_Qualities._spell_book && m_Qualities._spell_book->Exists(spell_id))
|
|
{
|
|
if (showTextAndEffect)
|
|
{
|
|
SendText("You already know that spell!", LTT_DEFAULT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Qualities.AddSpell(spell_id);
|
|
|
|
BinaryWriter addSpellToSpellbook;
|
|
addSpellToSpellbook.Write<DWORD>(0x2C1);
|
|
addSpellToSpellbook.Write<DWORD>(spell_id);
|
|
SendNetMessage(&addSpellToSpellbook, PRIVATE_MSG, TRUE, FALSE);
|
|
|
|
if (showTextAndEffect)
|
|
{
|
|
SendText(csprintf("You learn the %s spell.", spell->_name.c_str()), LTT_DEFAULT);
|
|
EmitEffect(PS_SkillUpPurple, 1.0f);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CWeenieObject::MarkForDestroy()
|
|
{
|
|
m_dDestroyTime = Timer::cur_time;
|
|
}
|
|
|
|
bool CWeenieObject::ShouldDestroy()
|
|
{
|
|
return m_dDestroyTime > 0 && m_dDestroyTime < Timer::cur_time;
|
|
}
|
|
|
|
void CWeenieObject::Remove()
|
|
{
|
|
if (IsContained())
|
|
{
|
|
std::shared_ptr<CWeenieObject> owner = GetWorldTopLevelOwner();
|
|
if (owner)
|
|
{
|
|
owner->ReleaseContainedItemRecursive(GetPointer<CWeenieObject>());
|
|
owner->NotifyContainedItemRemoved(GetID(), false);
|
|
}
|
|
}
|
|
else
|
|
NotifyObjectRemoved();
|
|
|
|
|
|
//if (!sourceItem->HasOwner())
|
|
//{
|
|
if (std::shared_ptr<CWeenieObject> generator = g_pWorld->FindObject(InqIIDQuality(GENERATOR_IID, 0)))
|
|
generator->NotifyGeneratedPickedUp(GetPointer<CWeenieObject>());
|
|
//}
|
|
|
|
//not sure which ones of the following are necessary but for now GetPointer<CWeenieObject>() works.
|
|
ReleaseFromAnyWeenieParent(false, true);
|
|
SetWielderID(0);
|
|
SetWieldedLocation(INVENTORY_LOC::NONE_LOC);
|
|
m_Qualities.SetInt(PARENT_LOCATION_INT, 0);
|
|
unset_parent();
|
|
|
|
g_pWorld->RemoveEntity(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
void CWeenieObject::DebugValidate()
|
|
{
|
|
#ifdef _DEBUG
|
|
assert(GetID());
|
|
assert(g_pWorld->FindObject(GetID()));
|
|
|
|
assert(!GetContainerID() || !GetWielderID());
|
|
|
|
if (GetContainerID())
|
|
{
|
|
assert(g_pWorld->FindObject(GetContainerID()));
|
|
assert(!GetWielderID());
|
|
assert(!m_pBlock);
|
|
}
|
|
|
|
if (GetWielderID())
|
|
{
|
|
assert(g_pWorld->FindObject(GetWielderID()));
|
|
assert(!GetContainerID());
|
|
assert(!m_pBlock);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
if (cell)
|
|
{
|
|
assert(m_pBlock);
|
|
assert(m_pBlock->GetHeader() == (cell->id >> 16));
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (!GetContainerID() && !GetWielderID())
|
|
{
|
|
// assert(m_pBlock);
|
|
}
|
|
|
|
int parentInt = InqIntQuality(PARENT_LOCATION_INT, 0);
|
|
|
|
if (parentInt)
|
|
{
|
|
// assert(parent);
|
|
assert(GetWielderID());
|
|
}
|
|
else
|
|
{
|
|
assert(!parent.lock());
|
|
}
|
|
|
|
int placement = InqIntQuality(PLACEMENT_POSITION_INT, 0);
|
|
|
|
if (!placement)
|
|
{
|
|
assert(!parent.lock());
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
BOOL CWeenieObject::InqSkill(STypeSkill key, DWORD &value, BOOL raw)
|
|
{
|
|
key = SkillTable::OldToNewSkill(key);
|
|
BOOL bResult = m_Qualities.InqSkill(key, value, raw);
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void CWeenieObject::RecalculateEncumbrance()
|
|
{
|
|
}
|
|
|
|
bool CWeenieObject::IsAttunedOrContainsAttuned()
|
|
{
|
|
return InqIntQuality(ATTUNED_INT, 0) ? true : false;
|
|
}
|
|
|
|
bool CWeenieObject::IsBonded()
|
|
{
|
|
return InqIntQuality(BONDED_INT, 0) == Bonded_BondedStatus ? true : false;
|
|
}
|
|
|
|
bool CWeenieObject::IsDroppedOnDeath()
|
|
{
|
|
return InqIntQuality(BONDED_INT, 0) == Slippery_BondedStatus ? true : false;
|
|
}
|
|
|
|
bool CWeenieObject::IsDestroyedOnDeath()
|
|
{
|
|
return InqIntQuality(BONDED_INT, 0) == Destroy_BondedStatus ? true : false;
|
|
}
|
|
|
|
void CWeenieObject::CheckVitalRanges()
|
|
{
|
|
if (GetMaxHealth() < GetHealth())
|
|
{
|
|
SetHealth(GetMaxHealth());
|
|
}
|
|
|
|
if (GetMaxStamina() < GetStamina())
|
|
{
|
|
SetStamina(GetMaxStamina());
|
|
}
|
|
|
|
if (GetMaxMana() < GetMana())
|
|
{
|
|
SetMana(GetMaxMana());
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::CheckEventState()
|
|
{
|
|
if (!m_Qualities._generator_table)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::string eventName;
|
|
if (m_Qualities.InqString(GENERATOR_EVENT_STRING, eventName))
|
|
{
|
|
if (g_pGameEventManager->IsEventStarted(eventName.c_str()))
|
|
{
|
|
HandleEventActive();
|
|
}
|
|
else
|
|
{
|
|
HandleEventInactive();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeenieObject::HandleEventActive()
|
|
{
|
|
// if we have any spawns, we shouldn't be needing activation
|
|
if (m_Qualities._generator_registry && !m_Qualities._generator_registry->_registry.empty())
|
|
return;
|
|
if (m_Qualities._generator_queue && !m_Qualities._generator_queue->_queue.empty())
|
|
return;
|
|
|
|
g_pWeenieFactory->AddFromGeneratorTable(GetPointer<CWeenieObject>(), false);
|
|
}
|
|
|
|
void CWeenieObject::HandleEventInactive()
|
|
{
|
|
if (m_Qualities._generator_registry)
|
|
{
|
|
while (!m_Qualities._generator_registry->_registry.empty())
|
|
{
|
|
DWORD weenie_id = m_Qualities._generator_registry->_registry.begin()->first;
|
|
|
|
if (std::shared_ptr<CWeenieObject> spawned_weenie = g_pWorld->FindObject(weenie_id))
|
|
{
|
|
g_pWorld->RemoveEntity(spawned_weenie);
|
|
}
|
|
|
|
// make sure it's gone (it should be already.)
|
|
m_Qualities._generator_registry->_registry.erase(weenie_id);
|
|
}
|
|
}
|
|
|
|
if (m_Qualities._generator_queue)
|
|
{
|
|
m_Qualities._generator_queue->_queue.clear();
|
|
}
|
|
}
|