mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
2439 lines
No EOL
68 KiB
C++
2439 lines
No EOL
68 KiB
C++
|
|
#include "StdAfx.h"
|
|
#include "WeenieObject.h"
|
|
#include "Monster.h"
|
|
#include "World.h"
|
|
#include "GameMode.h"
|
|
#include "Lifestone.h"
|
|
#include "ChatMsgs.h"
|
|
#include "SpellProjectile.h"
|
|
#include "MonsterAI.h"
|
|
#include "WeenieFactory.h"
|
|
#include "InferredPortalData.h"
|
|
#include "ObjectMsgs.h"
|
|
#include "EmoteManager.h"
|
|
#include "Corpse.h"
|
|
#include "AttackManager.h"
|
|
#include "WClassID.h"
|
|
#include "InferredPortalData.h"
|
|
#include "Config.h"
|
|
#include "DatabaseIO.h"
|
|
#include "InferredPortalData.h"
|
|
#include "SpellTableExtendedData.h"
|
|
#include "Door.h"
|
|
#include "Player.h"
|
|
#include "House.h"
|
|
|
|
CMonsterWeenie::CMonsterWeenie()
|
|
{
|
|
SetItemType(TYPE_CREATURE);
|
|
|
|
m_Qualities.SetInt(LEVEL_INT, 1);
|
|
m_Qualities.SetAttribute(STRENGTH_ATTRIBUTE, 100);
|
|
m_Qualities.SetAttribute(ENDURANCE_ATTRIBUTE, 100);
|
|
m_Qualities.SetAttribute(COORDINATION_ATTRIBUTE, 100);
|
|
m_Qualities.SetAttribute(QUICKNESS_ATTRIBUTE, 100);
|
|
m_Qualities.SetAttribute(FOCUS_ATTRIBUTE, 100);
|
|
m_Qualities.SetAttribute(SELF_ATTRIBUTE, 100);
|
|
m_Qualities.SetAttribute2nd(MAX_HEALTH_ATTRIBUTE_2ND, 0);
|
|
m_Qualities.SetAttribute2nd(HEALTH_ATTRIBUTE_2ND, 50);
|
|
m_Qualities.SetAttribute2nd(MAX_STAMINA_ATTRIBUTE_2ND, 0);
|
|
m_Qualities.SetAttribute2nd(STAMINA_ATTRIBUTE_2ND, 100);
|
|
m_Qualities.SetAttribute2nd(MAX_MANA_ATTRIBUTE_2ND, 0);
|
|
m_Qualities.SetAttribute2nd(MANA_ATTRIBUTE_2ND, 100);
|
|
|
|
m_Qualities.SetInt(SHOWABLE_ON_RADAR_INT, ShowAlways_RadarEnum);
|
|
|
|
m_Qualities.SetBool(STUCK_BOOL, TRUE);
|
|
m_Qualities.SetBool(ATTACKABLE_BOOL, TRUE);
|
|
m_Qualities.SetInt(ITEM_USEABLE_INT, USEABLE_NO);
|
|
}
|
|
|
|
CMonsterWeenie::~CMonsterWeenie()
|
|
{
|
|
SafeDelete(m_MonsterAI);
|
|
}
|
|
|
|
void CMonsterWeenie::ApplyQualityOverrides()
|
|
{
|
|
}
|
|
|
|
void CMonsterWeenie::PreSpawnCreate()
|
|
{
|
|
if (m_Qualities._create_list)
|
|
g_pWeenieFactory->AddFromCreateList(GetPointer<CWeenieObject>(), m_Qualities._create_list, (DestinationType)(Wield_DestinationType | WieldTreasure_DestinationType));
|
|
|
|
if (DWORD wieldedTreasureType = InqDIDQuality(WIELDED_TREASURE_TYPE_DID, 0))
|
|
g_pWeenieFactory->GenerateFromTypeOrWcid(GetPointer<CWeenieObject>(), DestinationType::WieldTreasure_DestinationType, wieldedTreasureType);
|
|
}
|
|
|
|
void CMonsterWeenie::PostSpawn()
|
|
{
|
|
CWeenieObject::PostSpawn();
|
|
|
|
// check if we need to create a monster AI
|
|
if (IsCreature() && !AsPlayer() && !IsVendor() && m_Qualities.GetInt(PLAYER_KILLER_STATUS_INT, 0) != RubberGlue_PKStatus)
|
|
{
|
|
m_MonsterAI = new MonsterAIManager(AsMonster(), m_Position);
|
|
}
|
|
|
|
if (!_IsPlayer())
|
|
{
|
|
EmitEffect(PS_Create, 1.0f);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CMonsterWeenie::SpawnWielded(DWORD index, SmartArray<Style_CG> possibleStyles, DWORD color, SmartArray<DWORD> validColors, long double shade)
|
|
{
|
|
index = max(min(index, possibleStyles.num_used - 1), 0);
|
|
shade = max(min(shade, 1), 0);
|
|
|
|
DWORD wcid = possibleStyles.array_data[index].weenieDefault;
|
|
DWORD clothingTable = possibleStyles.array_data[index].clothingTable;
|
|
|
|
bool validColor = false;
|
|
for (int i = 0; i < validColors.num_used; i++)
|
|
{
|
|
if (color == validColors.array_data[i])
|
|
{
|
|
validColor = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!validColor)
|
|
color = validColors.array_data[0];
|
|
|
|
std::shared_ptr<CWeenieObject> weenie = SpawnWielded(wcid, color, shade);
|
|
weenie->m_Qualities.SetDataID(CLOTHINGBASE_DID, clothingTable);
|
|
|
|
return weenie;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CMonsterWeenie::SpawnWielded(DWORD wcid, int ptid, float shade)
|
|
{
|
|
std::shared_ptr<CWeenieObject> item = g_pWeenieFactory->CreateWeenieByClassID(wcid, NULL, false);
|
|
|
|
if (ptid)
|
|
item->m_Qualities.SetInt(PALETTE_TEMPLATE_INT, ptid);
|
|
|
|
if (shade > 0.0)
|
|
item->m_Qualities.SetFloat(SHADE_FLOAT, shade);
|
|
|
|
return SpawnWielded(item);
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CMonsterWeenie::SpawnWielded(std::shared_ptr<CWeenieObject> item, bool deleteItemOnFailure)
|
|
{
|
|
if (!item)
|
|
return NULL;
|
|
|
|
if (!item->IsEquippable())
|
|
{
|
|
LOG_PRIVATE(Temp, Warning, "Trying to SpawnWielded an unwieldable item %u\n", item->m_Qualities.id);
|
|
return NULL;
|
|
}
|
|
|
|
item->SetID(g_pWorld->GenerateGUID(eDynamicGUID));
|
|
|
|
if (!g_pWorld->CreateEntity(item, false))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (!FinishMoveItemToWield(item, item->m_Qualities.GetInt(LOCATIONS_INT, 0)))
|
|
{
|
|
g_pWorld->RemoveEntity(item);
|
|
item = NULL;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> CMonsterWeenie::FindValidNearbyItem(DWORD itemId, float maxDistance)
|
|
{
|
|
//do we have it?
|
|
std::shared_ptr<CWeenieObject> item = FindContainedItem(itemId);
|
|
|
|
if (!item)
|
|
{
|
|
// maybe it's in an external container
|
|
if (_lastOpenedRemoteContainerId != 0)
|
|
{
|
|
std::shared_ptr<CContainerWeenie> externalContainer = FindContainer(_lastOpenedRemoteContainerId);
|
|
if (externalContainer && externalContainer->_openedById == GetID())
|
|
{
|
|
item = externalContainer->FindContainedItem(itemId);
|
|
}
|
|
}
|
|
|
|
if (!item)
|
|
{
|
|
// maybe it's on the ground
|
|
item = g_pWorld->FindObject(itemId);
|
|
|
|
if (!item || item->HasOwner() || !item->InValidCell())
|
|
{
|
|
NotifyInventoryFailedEvent(itemId, WERROR_OBJECT_GONE);
|
|
return NULL;
|
|
}
|
|
|
|
if (DistanceTo(item, true) > maxDistance)
|
|
{
|
|
NotifyInventoryFailedEvent(itemId, WERROR_TOO_FAR);
|
|
return NULL;;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!item->CanPickup())
|
|
{
|
|
NotifyInventoryFailedEvent(itemId, WERROR_NONE);
|
|
return NULL;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
std::shared_ptr<CContainerWeenie> CMonsterWeenie::FindValidNearbyContainer(DWORD containerId, float maxDistance)
|
|
{
|
|
//do we have it?
|
|
std::shared_ptr<CContainerWeenie> container = FindContainer(containerId);
|
|
|
|
if (!container)
|
|
{
|
|
// maybe it's in the external container
|
|
if (_lastOpenedRemoteContainerId != 0)
|
|
{
|
|
std::shared_ptr<CContainerWeenie> externalContainer = FindContainer(_lastOpenedRemoteContainerId);
|
|
if (externalContainer && externalContainer->_openedById == GetID())
|
|
container = externalContainer->FindContainer(containerId);
|
|
}
|
|
|
|
if (!container)
|
|
{
|
|
// maybe it's on the ground
|
|
std::shared_ptr<CWeenieObject> object = g_pWorld->FindObject(containerId);
|
|
container = object->AsContainer();
|
|
|
|
if (!container || container->HasOwner() || !container->InValidCell())
|
|
{
|
|
NotifyInventoryFailedEvent(containerId, WERROR_OBJECT_GONE);
|
|
return NULL;
|
|
}
|
|
|
|
if (DistanceTo(container, true) > maxDistance)
|
|
{
|
|
NotifyInventoryFailedEvent(containerId, WERROR_TOO_FAR);
|
|
return NULL;;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (container->GetID() != GetID() && container->IsCreature())
|
|
{
|
|
NotifyInventoryFailedEvent(containerId, WERROR_NONE);
|
|
return NULL;
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
bool CMonsterWeenie::GetEquipPlacementAndHoldLocation(std::shared_ptr<CWeenieObject> item, DWORD location, DWORD *pPlacementFrame, DWORD *pHoldLocation)
|
|
{
|
|
if (location & (MELEE_WEAPON_LOC | TWO_HANDED_LOC))
|
|
{
|
|
*pPlacementFrame = Placement::RightHandCombat;
|
|
*pHoldLocation = PARENT_RIGHT_HAND;
|
|
}
|
|
else if (location & MISSILE_WEAPON_LOC)
|
|
{
|
|
int combatStyle = item->InqIntQuality(DEFAULT_COMBAT_STYLE_INT, 0);
|
|
if (combatStyle == ThrownWeapon_CombatStyle || combatStyle == Atlatl_CombatStyle)
|
|
{
|
|
*pPlacementFrame = Placement::RightHandCombat;
|
|
*pHoldLocation = PARENT_RIGHT_HAND;
|
|
}
|
|
else
|
|
{
|
|
*pPlacementFrame = Placement::LeftHand;
|
|
*pHoldLocation = PARENT_LEFT_HAND;
|
|
}
|
|
}
|
|
else if (location & SHIELD_LOC)
|
|
{
|
|
*pPlacementFrame = Placement::Shield;
|
|
*pHoldLocation = PARENT_SHIELD;
|
|
}
|
|
else if (location & HELD_LOC)
|
|
{
|
|
*pPlacementFrame = Placement::RightHandCombat;
|
|
*pHoldLocation = PARENT_RIGHT_HAND;
|
|
}
|
|
else if (location & MISSILE_AMMO_LOC)
|
|
{
|
|
//*pPlacementFrame = Placement::RightHandCombat;
|
|
//*pHoldLocation = PARENT_RIGHT_HAND;
|
|
|
|
*pPlacementFrame = 0;
|
|
*pHoldLocation = 0;
|
|
}
|
|
else if (location & (ARMOR_LOC | CLOTHING_LOC))
|
|
{
|
|
*pPlacementFrame = 0;
|
|
*pHoldLocation = 0;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// LOG(Temp, Normal, "Trying to place %s but don't know how with coverage 0x%08X\n", item->GetName().c_str(), location);
|
|
*pPlacementFrame = 0;
|
|
*pHoldLocation = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BYTE CMonsterWeenie::GetEnchantmentSerialByteForMask(int priority)
|
|
{
|
|
if (!priority)
|
|
return 0;
|
|
|
|
for (DWORD i = 0; i < 32; i++)
|
|
{
|
|
if (priority & 1)
|
|
return i + 1;
|
|
|
|
priority >>= 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CMonsterWeenie::CheckWieldRequirements(std::shared_ptr<CWeenieObject> item, std::shared_ptr<CWeenieObject> wielder, STypeInt requirementStat, STypeInt skillStat, STypeInt difficultyStat)
|
|
{
|
|
int requirementType = item->InqIntQuality(requirementStat, 0, TRUE);
|
|
|
|
if (!requirementType)
|
|
return WERROR_NONE;
|
|
|
|
int skillType = item->InqIntQuality(skillStat, 0, TRUE);
|
|
int wieldDifficulty = item->InqIntQuality(difficultyStat, 0, TRUE);
|
|
|
|
switch (requirementType)
|
|
{
|
|
case 1: // skill
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!wielder->m_Qualities.InqSkill((STypeSkill)skillType, skillLevel, FALSE) || (int)skillLevel < wieldDifficulty)
|
|
return WERROR_SKILL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 2: // base skill
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!wielder->m_Qualities.InqSkill((STypeSkill)skillType, skillLevel, TRUE) || (int)skillLevel < wieldDifficulty)
|
|
return WERROR_SKILL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 3: // attribute
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!wielder->m_Qualities.InqAttribute((STypeAttribute)skillType, skillLevel, FALSE) || (int)skillLevel < wieldDifficulty)
|
|
return WERROR_ACTIVATION_SKILL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 4: // base attribute
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!wielder->m_Qualities.InqAttribute((STypeAttribute)skillType, skillLevel, TRUE) || (int)skillLevel < wieldDifficulty)
|
|
return WERROR_ACTIVATION_SKILL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 5: // attribute 2nd
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!wielder->m_Qualities.InqAttribute2nd((STypeAttribute2nd)skillType, skillLevel, FALSE) || (int)skillLevel < wieldDifficulty)
|
|
return WERROR_ACTIVATION_SKILL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 6: // attribute 2nd base
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!wielder->m_Qualities.InqAttribute2nd((STypeAttribute2nd)skillType, skillLevel, TRUE) || (int)skillLevel < wieldDifficulty)
|
|
return WERROR_ACTIVATION_SKILL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 7: // level
|
|
{
|
|
if (wielder->InqIntQuality(LEVEL_INT, 1, TRUE) < wieldDifficulty)
|
|
return WERROR_LEVEL_TOO_LOW;
|
|
break;
|
|
}
|
|
case 8: // skill advancement class
|
|
{
|
|
SKILL_ADVANCEMENT_CLASS sac = SKILL_ADVANCEMENT_CLASS::UNDEF_SKILL_ADVANCEMENT_CLASS;
|
|
if (!wielder->m_Qualities.InqSkillAdvancementClass((STypeSkill)skillType, sac) || (int)sac < wieldDifficulty)
|
|
return WERROR_ACTIVATION_NOT_SPECIALIZED;
|
|
break;
|
|
}
|
|
case 9: // faction
|
|
{
|
|
return WERROR_NONE; //todo
|
|
}
|
|
case 10: // unknown
|
|
{
|
|
return WERROR_NONE; //todo
|
|
}
|
|
case 11: // type
|
|
{
|
|
if (wielder->InqIntQuality(CREATURE_TYPE_INT, 0, TRUE) != wieldDifficulty)
|
|
return WERROR_ACTIVATION_WRONG_RACE;
|
|
break;
|
|
}
|
|
case 12: // race
|
|
{
|
|
if (wielder->InqIntQuality(HERITAGE_GROUP_INT, 0, TRUE) != wieldDifficulty)
|
|
return WERROR_ACTIVATION_WRONG_RACE;
|
|
break;
|
|
}
|
|
}
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
bool CMonsterWeenie::MoveItemToContainer(DWORD sourceItemId, DWORD targetContainerId, DWORD targetSlot, bool animationDone)
|
|
{
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CContainerWeenie> targetContainer = FindValidNearbyContainer(targetContainerId, USEDISTANCE_FAR);
|
|
if (!targetContainer || !targetContainer->Container_CanStore(sourceItem))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (sourceItem->IsAttunedOrContainsAttuned() && targetContainer->GetTopLevelID() != GetID())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->id, WERROR_ATTUNED_ITEM);
|
|
return false;
|
|
}
|
|
|
|
bool isExternalTargetContainer = (targetContainer->GetTopLevelID() != GetID());
|
|
|
|
if (!animationDone && (!FindContainedItem(sourceItem->GetID()) || isExternalTargetContainer))
|
|
{
|
|
//from ground or external container or to external container.
|
|
CPickupInventoryUseEvent *pickupEvent = new CPickupInventoryUseEvent();
|
|
pickupEvent->_target_id = sourceItemId;
|
|
pickupEvent->_source_item_id = sourceItemId;
|
|
pickupEvent->_target_container_id = targetContainerId;
|
|
pickupEvent->_target_slot = targetSlot;
|
|
ExecuteUseEvent(pickupEvent);
|
|
}
|
|
else
|
|
{
|
|
//from our inventory or have already done the animation.
|
|
FinishMoveItemToContainer(sourceItem, targetContainer, targetSlot);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMonsterWeenie::FinishMoveItemToContainer(std::shared_ptr<CWeenieObject> sourceItem, std::shared_ptr<CContainerWeenie> targetContainer, DWORD targetSlot, bool bSendEvent, bool silent)
|
|
{
|
|
// Scenarios to consider:
|
|
// 1. Item being stored is equipped.
|
|
// 2. Item being stored is on the GROUND!
|
|
// 3. Item being stored is already in the inventory.
|
|
// 4. Item being stored is in an external container (chest) or being moved to an external container (chest)
|
|
|
|
bool wasWielded = sourceItem->IsWielded();
|
|
bool isPickup = sourceItem->GetWorldTopLevelOwner() != GetPointer<CWeenieObject>();
|
|
|
|
DWORD dwCell = GetLandcell();
|
|
|
|
//if (!sourceItem->HasOwner())
|
|
//{
|
|
if (std::shared_ptr<CWeenieObject> generator = g_pWorld->FindObject(sourceItem->InqIIDQuality(GENERATOR_IID, 0)))
|
|
generator->NotifyGeneratedPickedUp(sourceItem);
|
|
//}
|
|
|
|
// Take it out of whatever slot it's in.
|
|
sourceItem->ReleaseFromAnyWeenieParent(false, true);
|
|
sourceItem->SetWieldedLocation(INVENTORY_LOC::NONE_LOC);
|
|
sourceItem->SetWeenieContainer(targetContainer->GetID());
|
|
sourceItem->ReleaseFromBlock();
|
|
|
|
// The container will auto-correct this slot into a valid range.
|
|
targetSlot = targetContainer->Container_InsertInventoryItem(dwCell, sourceItem, targetSlot);
|
|
|
|
if (!silent)
|
|
{
|
|
if (wasWielded)
|
|
EmitSound(Sound_UnwieldObject, 1.0f);
|
|
else
|
|
EmitSound(Sound_PickUpItem, 1.0f);
|
|
}
|
|
|
|
if (isPickup)
|
|
sourceItem->OnPickedUp(GetPointer<CWeenieObject>());
|
|
|
|
if (bSendEvent)
|
|
{
|
|
if (sourceItem->AsContainer())
|
|
sourceItem->AsContainer()->MakeAwareViewContent(GetPointer<CWeenieObject>());
|
|
|
|
SendNetMessage(InventoryMove(sourceItem->GetID(), targetContainer->GetID(), targetSlot, sourceItem->RequiresPackSlot() ? 1 : 0), PRIVATE_MSG, TRUE);
|
|
}
|
|
|
|
if (targetContainer->GetWorldTopLevelOwner() != GetPointer<CWeenieObject>())
|
|
RecalculateEncumbrance();
|
|
|
|
if (sourceItem->GetItemType() & (TYPE_ARMOR | TYPE_CLOTHING))
|
|
UpdateModel();
|
|
|
|
if (wasWielded && get_minterp()->InqStyle() != Motion_NonCombat)
|
|
AdjustToNewCombatMode();
|
|
|
|
if (AsPlayer() && sourceItem->m_Qualities.id == W_COINSTACK_CLASS)
|
|
RecalculateCoinAmount();
|
|
|
|
sourceItem->m_Qualities.RemoveFloat(TIME_TO_ROT_FLOAT);
|
|
sourceItem->_timeToRot = -1.0;
|
|
sourceItem->_beganRot = false;
|
|
}
|
|
|
|
bool CMonsterWeenie::MoveItemTo3D(DWORD sourceItemId, bool animationDone)
|
|
{
|
|
// Scenarios to consider:
|
|
// 1. Item being stored is equipped.
|
|
// 3. Item being stored is in the inventory.
|
|
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (sourceItem->IsAttunedOrContainsAttuned())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ATTUNED_ITEM);
|
|
return false;
|
|
}
|
|
|
|
if (!animationDone)
|
|
{
|
|
CDropInventoryUseEvent *dropEvent = new CDropInventoryUseEvent();
|
|
dropEvent->_target_id = sourceItemId;
|
|
ExecuteUseEvent(dropEvent);
|
|
}
|
|
else
|
|
{
|
|
FinishMoveItemTo3D(sourceItem);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMonsterWeenie::FinishMoveItemTo3D(std::shared_ptr<CWeenieObject> sourceItem)
|
|
{
|
|
// Scenarios to consider:
|
|
// 1. Item being dropped is equipped.
|
|
// 3. Item being dropped is in the inventory.
|
|
|
|
BOOL bWasWielded = sourceItem->IsWielded();
|
|
|
|
// Take it out of whatever slot it's in.
|
|
sourceItem->ReleaseFromAnyWeenieParent(false, true);
|
|
sourceItem->SetWeenieContainer(0);
|
|
sourceItem->SetWielderID(0);
|
|
sourceItem->SetWieldedLocation(INVENTORY_LOC::NONE_LOC);
|
|
|
|
EmitSound(Sound_DropItem, 1.0f);
|
|
SendNetMessage(InventoryDrop(sourceItem->GetID()), PRIVATE_MSG, TRUE);
|
|
|
|
sourceItem->m_Qualities.SetInt(PARENT_LOCATION_INT, 0);
|
|
sourceItem->unset_parent();
|
|
sourceItem->enter_world(&m_Position);
|
|
|
|
sourceItem->SetPlacementFrame(0x65, FALSE);
|
|
|
|
//if (!pItem->HasAnims())
|
|
// pItem->SetPlacementFrame(pItem->CanPickup() ? 0x65 : 0, 1);
|
|
|
|
if (sourceItem->AsClothing())
|
|
UpdateModel();
|
|
|
|
g_pWorld->InsertEntity(sourceItem);
|
|
sourceItem->Movement_Teleport(GetPosition());
|
|
|
|
sourceItem->_timeToRot = Timer::cur_time + 300.0;
|
|
sourceItem->_beganRot = false;
|
|
sourceItem->m_Qualities.SetFloat(TIME_TO_ROT_FLOAT, sourceItem->_timeToRot);
|
|
|
|
RecalculateEncumbrance();
|
|
|
|
if (AsPlayer() && sourceItem->m_Qualities.id == W_COINSTACK_CLASS)
|
|
RecalculateCoinAmount();
|
|
|
|
if (bWasWielded && get_minterp()->InqStyle() != Motion_NonCombat)
|
|
AdjustToNewCombatMode();
|
|
|
|
if(bWasWielded)
|
|
sourceItem->OnUnwield(GetPointer<CWeenieObject>());
|
|
sourceItem->OnDropped(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
bool CMonsterWeenie::MoveItemToWield(DWORD sourceItemId, DWORD targetLoc, bool animationDone)
|
|
{
|
|
// Scenarios to consider:
|
|
// 1. Item being equipped from the GROUND!
|
|
// 2. Item being equipped from different equip slot.
|
|
// 3. Item being equipped from the player's inventory.
|
|
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (!Container_CanEquip(sourceItem, targetLoc))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_BE_WIELDED_FAILURE);
|
|
return false;
|
|
}
|
|
|
|
if (!animationDone && !FindContainedItem(sourceItem->GetID()))
|
|
{
|
|
//from ground or external container.
|
|
CMoveToWieldInventoryUseEvent *pickupEvent = new CMoveToWieldInventoryUseEvent();
|
|
pickupEvent->_target_id = sourceItemId;
|
|
pickupEvent->_sourceItemId = sourceItemId;
|
|
pickupEvent->_targetLoc = targetLoc;
|
|
ExecuteUseEvent(pickupEvent);
|
|
}
|
|
else
|
|
{
|
|
//from our inventory or have already done the animation.
|
|
return FinishMoveItemToWield(sourceItem, targetLoc);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CMonsterWeenie::FinishMoveItemToWield(std::shared_ptr<CWeenieObject> sourceItem, DWORD targetLoc)
|
|
{
|
|
// Scenarios to consider:
|
|
// 1. Item being equipped from the GROUND!
|
|
// 2. Item being equipped from different equip slot.
|
|
// 3. Item being equipped from the player's inventory.
|
|
|
|
bool isPickup = sourceItem->GetWorldTopLevelOwner() != GetPointer<CWeenieObject>();
|
|
|
|
int error = CheckWieldRequirements(sourceItem, GetPointer<CWeenieObject>(), WIELD_REQUIREMENTS_INT, WIELD_SKILLTYPE_INT, WIELD_DIFFICULTY_INT);
|
|
if (error != WERROR_NONE)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), error);
|
|
return false;
|
|
}
|
|
|
|
error = CheckWieldRequirements(sourceItem, GetPointer<CWeenieObject>(), WIELD_REQUIREMENTS_2_INT, WIELD_SKILLTYPE_2_INT, WIELD_DIFFICULTY_2_INT);
|
|
if (error != WERROR_NONE)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), error);
|
|
return false;
|
|
}
|
|
|
|
error = CheckWieldRequirements(sourceItem, GetPointer<CWeenieObject>(), WIELD_REQUIREMENTS_3_INT, WIELD_SKILLTYPE_3_INT, WIELD_DIFFICULTY_3_INT);
|
|
if (error != WERROR_NONE)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), error);
|
|
return false;
|
|
}
|
|
|
|
error = CheckWieldRequirements(sourceItem, GetPointer<CWeenieObject>(), WIELD_REQUIREMENTS_4_INT, WIELD_SKILLTYPE_4_INT, WIELD_DIFFICULTY_4_INT);
|
|
if (error != WERROR_NONE)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), error);
|
|
return false;
|
|
}
|
|
|
|
if (sourceItem->InqIntQuality(ITEM_TYPE_INT, 0) == TYPE_ARMOR && sourceItem->InqIntQuality(LOCATIONS_INT, 0) == SHIELD_LOC)
|
|
{
|
|
sourceItem->m_Qualities.SetInt(SHIELD_VALUE_INT, sourceItem->InqIntQuality(ARMOR_LEVEL_INT, 0));
|
|
}
|
|
|
|
// Weeping wand nerf
|
|
if (sourceItem->m_Qualities.m_WeenieType == 35 && sourceItem->InqStringQuality(NAME_STRING, "") == "Weeping Wand"
|
|
&& (sourceItem->InqDIDQuality(SPELL_DID, 0) > 0 || sourceItem->InqFloatQuality(SLAYER_DAMAGE_BONUS_FLOAT, 0) != 1.4))
|
|
{
|
|
sourceItem->m_Qualities.RemoveDataID(SPELL_DID);
|
|
sourceItem->m_Qualities.SetFloat(SLAYER_DAMAGE_BONUS_FLOAT, 1.4);
|
|
}
|
|
|
|
//if (!sourceItem->HasOwner())
|
|
//{
|
|
if (std::shared_ptr<CWeenieObject> generator = g_pWorld->FindObject(sourceItem->InqIIDQuality(GENERATOR_IID, 0)))
|
|
generator->NotifyGeneratedPickedUp(sourceItem);
|
|
//}
|
|
|
|
DWORD cell_id = m_Position.objcell_id;
|
|
|
|
// Take it out of whatever slot it may be in
|
|
sourceItem->ReleaseFromAnyWeenieParent(false, false);
|
|
|
|
DWORD placement_id, child_location_id;
|
|
bool bShouldPlace = GetEquipPlacementAndHoldLocation(sourceItem, targetLoc, &placement_id, &child_location_id);
|
|
|
|
sourceItem->SetWielderID(GetID());
|
|
sourceItem->SetWieldedLocation(targetLoc);
|
|
sourceItem->ReleaseFromBlock();
|
|
|
|
// The container will auto-correct this slot into a valid range.
|
|
Container_EquipItem(cell_id, sourceItem, targetLoc, child_location_id, placement_id);
|
|
|
|
if (_IsPlayer())
|
|
{
|
|
SendNetMessage(InventoryEquip(sourceItem->GetID(), targetLoc), PRIVATE_MSG, TRUE);
|
|
EmitSound(Sound_WieldObject, 1.0f);
|
|
}
|
|
|
|
if (m_bWorldIsAware)
|
|
{
|
|
sourceItem->NotifyIIDStatUpdated(CONTAINER_IID, false);
|
|
sourceItem->NotifyIIDStatUpdated(WIELDER_IID, false);
|
|
sourceItem->NotifyIntStatUpdated(CURRENT_WIELDED_LOCATION_INT, false);
|
|
}
|
|
|
|
if (sourceItem->AsClothing() && m_bWorldIsAware)
|
|
UpdateModel();
|
|
|
|
sourceItem->m_Qualities.RemoveFloat(TIME_TO_ROT_FLOAT);
|
|
sourceItem->_timeToRot = -1.0;
|
|
sourceItem->_beganRot = false;
|
|
|
|
if (get_minterp()->InqStyle() != Motion_NonCombat)
|
|
AdjustToNewCombatMode();
|
|
|
|
// apply enchantments
|
|
if (sourceItem->m_Qualities._spell_book)
|
|
{
|
|
bool bShouldCast = true;
|
|
|
|
std::string name;
|
|
if (sourceItem->m_Qualities.InqString(CRAFTSMAN_NAME_STRING, name))
|
|
{
|
|
if (!name.empty() && name != InqStringQuality(NAME_STRING, ""))
|
|
{
|
|
bShouldCast = false;
|
|
|
|
NotifyWeenieErrorWithString(WERROR_ACTIVATION_NOT_CRAFTSMAN, name.c_str());
|
|
}
|
|
}
|
|
|
|
int difficulty;
|
|
difficulty = 0;
|
|
if (sourceItem->m_Qualities.InqInt(ITEM_DIFFICULTY_INT, difficulty, TRUE, FALSE))
|
|
{
|
|
DWORD skillLevel = 0;
|
|
if (!m_Qualities.InqSkill(ARCANE_LORE_SKILL, skillLevel, FALSE) || (int)skillLevel < difficulty)
|
|
{
|
|
bShouldCast = false;
|
|
|
|
NotifyWeenieError(WERROR_ACTIVATION_ARCANE_LORE_TOO_LOW);
|
|
}
|
|
}
|
|
|
|
if (bShouldCast)
|
|
{
|
|
difficulty = 0;
|
|
DWORD skillActivationTypeDID = 0;
|
|
if (sourceItem->m_Qualities.InqInt(ITEM_SKILL_LEVEL_LIMIT_INT, difficulty, TRUE, FALSE) && sourceItem->m_Qualities.InqDataID(ITEM_SKILL_LIMIT_DID, skillActivationTypeDID))
|
|
{
|
|
STypeSkill skillActivationType = SkillTable::OldToNewSkill((STypeSkill)skillActivationTypeDID);
|
|
|
|
DWORD skillLevel = 0;
|
|
if (!m_Qualities.InqSkill(skillActivationType, skillLevel, FALSE) || (int)skillLevel < difficulty)
|
|
{
|
|
bShouldCast = false;
|
|
|
|
NotifyWeenieErrorWithString(WERROR_ACTIVATION_SKILL_TOO_LOW, CachedSkillTable->GetSkillName(skillActivationType).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bShouldCast && sourceItem->InqIntQuality(ITEM_ALLEGIANCE_RANK_LIMIT_INT, 0) > InqIntQuality(ALLEGIANCE_RANK_INT, 0))
|
|
{
|
|
bShouldCast = false;
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_ACTIVATION_RANK_TOO_LOW);
|
|
}
|
|
|
|
if (bShouldCast)
|
|
{
|
|
int heritageRequirement = sourceItem->InqIntQuality(HERITAGE_GROUP_INT, -1);
|
|
if (heritageRequirement != -1 && heritageRequirement != InqIntQuality(HERITAGE_GROUP_INT, 0))
|
|
{
|
|
bShouldCast = false;
|
|
std::string heritageString = sourceItem->InqStringQuality(ITEM_HERITAGE_GROUP_RESTRICTION_STRING, "of the correct heritage");
|
|
NotifyWeenieErrorWithString(WERROR_ACTIVATION_WRONG_RACE, heritageString.c_str());
|
|
}
|
|
}
|
|
|
|
int currentMana = 0;
|
|
if (bShouldCast && sourceItem->m_Qualities.InqInt(ITEM_CUR_MANA_INT, currentMana, TRUE, FALSE))
|
|
{
|
|
if (currentMana == 0)
|
|
{
|
|
bShouldCast = false;
|
|
NotifyWeenieError(WERROR_ACTIVATION_NOT_ENOUGH_MANA);
|
|
}
|
|
else
|
|
sourceItem->_nextManaUse = Timer::cur_time;
|
|
}
|
|
|
|
if (bShouldCast)
|
|
{
|
|
DWORD serial = 0;
|
|
|
|
serial |= ((DWORD)GetEnchantmentSerialByteForMask(sourceItem->InqIntQuality(LOCATIONS_INT, 0, TRUE)) << (DWORD)0);
|
|
serial |= ((DWORD)GetEnchantmentSerialByteForMask(sourceItem->InqIntQuality(CLOTHING_PRIORITY_INT, 0, TRUE)) << (DWORD)8);
|
|
|
|
for (auto &spellPage : sourceItem->m_Qualities._spell_book->_spellbook)
|
|
{
|
|
sourceItem->MakeSpellcastingManager()->CastSpellEquipped(GetID(), spellPage.first, (WORD)serial);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(isPickup)
|
|
sourceItem->OnPickedUp(GetPointer<CWeenieObject>());
|
|
sourceItem->OnWield(GetPointer<CWeenieObject>());
|
|
return true;
|
|
}
|
|
|
|
bool CMonsterWeenie::MergeItem(DWORD sourceItemId, DWORD targetItemId, DWORD amountToTransfer, bool animationDone)
|
|
{
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> targetItem = FindValidNearbyItem(targetItemId, USEDISTANCE_FAR);
|
|
if (!targetItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (!animationDone && (!FindContainedItem(sourceItemId) || !FindContainedItem(targetItemId)))
|
|
{
|
|
//from ground or other container or to ground or other container.
|
|
CStackMergeInventoryUseEvent *mergeEvent = new CStackMergeInventoryUseEvent();
|
|
if(sourceItem->IsContained() || sourceItem->IsWielded())
|
|
mergeEvent->_target_id = targetItemId;
|
|
else
|
|
mergeEvent->_target_id = sourceItemId;
|
|
mergeEvent->_sourceItemId = sourceItemId;
|
|
mergeEvent->_targetItemId = targetItemId;
|
|
mergeEvent->_amountToTransfer = amountToTransfer;
|
|
ExecuteUseEvent(mergeEvent);
|
|
}
|
|
else
|
|
{
|
|
//check if the items is still in range.
|
|
if (FindValidNearbyItem(sourceItemId, 5.0) == NULL || FindValidNearbyItem(targetItemId, 5.0) == NULL)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_TOO_FAR);
|
|
return false;
|
|
}
|
|
|
|
if (sourceItem->m_Qualities.id != targetItem->m_Qualities.id)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_NONE);
|
|
return false;
|
|
}
|
|
|
|
DWORD sourceCurrentAmount = sourceItem->InqIntQuality(STACK_SIZE_INT, 1);
|
|
if (amountToTransfer > sourceCurrentAmount)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_NONE);
|
|
return false;
|
|
}
|
|
|
|
DWORD targetCurrentAmount = targetItem->InqIntQuality(STACK_SIZE_INT, 1);
|
|
DWORD targetMaxStackSize = targetItem->InqIntQuality(MAX_STACK_SIZE_INT, 1);
|
|
|
|
int maxTransferAmount = targetMaxStackSize - targetCurrentAmount;
|
|
if (maxTransferAmount <= 0)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_NONE);
|
|
return false;
|
|
}
|
|
|
|
if (maxTransferAmount < amountToTransfer)
|
|
amountToTransfer = maxTransferAmount;
|
|
targetItem->SetStackSize(targetCurrentAmount + amountToTransfer);
|
|
|
|
bool wasSourceItemWielded = sourceItem->IsWielded();
|
|
bool wasTargetItemWielded = targetItem->IsWielded();
|
|
|
|
DWORD sourceItemNewStackSize = sourceCurrentAmount - amountToTransfer;
|
|
if (sourceItemNewStackSize > 0) //partial stack movement
|
|
{
|
|
//the client seems to forget about the source item when merging using the "place selected object in inventory" keybind
|
|
//to split from a stack so we have to remind it. I don't know how we can determine when the client uses the keybind or dragging
|
|
//so just do it regarless.
|
|
MakeAware(sourceItem, true);
|
|
sourceItem->SetStackSize(sourceItemNewStackSize);
|
|
}
|
|
else //full stack movement
|
|
sourceItem->Remove();
|
|
|
|
if (wasSourceItemWielded)
|
|
EmitSound(Sound_UnwieldObject, 1.0f);
|
|
else if (wasTargetItemWielded)
|
|
EmitSound(Sound_WieldObject, 1.0f);
|
|
else if (!FindContainedItem(targetItemId)) //Todo: does placing items in external containers make the drop or pickup sound?
|
|
EmitSound(Sound_DropItem, 1.0f);
|
|
else
|
|
EmitSound(Sound_PickUpItem, 1.0f);
|
|
|
|
RecalculateEncumbrance();
|
|
|
|
if (AsPlayer() && sourceItem->m_Qualities.id == W_COINSTACK_CLASS)
|
|
RecalculateCoinAmount();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CMonsterWeenie::SplitItemToContainer(DWORD sourceItemId, DWORD targetContainerId, DWORD targetSlot, DWORD amountToTransfer, bool animationDone)
|
|
{
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CContainerWeenie> targetContainer = FindValidNearbyContainer(targetContainerId, USEDISTANCE_FAR);
|
|
if (!targetContainer || !targetContainer->Container_CanStore(sourceItem))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
bool isExternalTargetContainer = (targetContainer->GetTopLevelID() != GetID());
|
|
|
|
if (isExternalTargetContainer && sourceItem->IsAttunedOrContainsAttuned())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ATTUNED_ITEM);
|
|
return false;
|
|
}
|
|
|
|
if (!animationDone && (!FindContainedItem(sourceItemId) || isExternalTargetContainer))
|
|
{
|
|
//from ground or external container or to external container
|
|
CStackSplitToContainerInventoryUseEvent *mergeEvent = new CStackSplitToContainerInventoryUseEvent();
|
|
if (sourceItem->IsContained() || sourceItem->IsWielded())
|
|
mergeEvent->_target_id = targetContainerId;
|
|
else
|
|
mergeEvent->_target_id = sourceItemId;
|
|
mergeEvent->_sourceItemId = sourceItemId;
|
|
mergeEvent->_targetContainerId = targetContainerId;
|
|
mergeEvent->_targetSlot = targetSlot;
|
|
mergeEvent->_amountToTransfer = amountToTransfer;
|
|
ExecuteUseEvent(mergeEvent);
|
|
}
|
|
else
|
|
{
|
|
//check if items are still in range.
|
|
if (FindValidNearbyItem(sourceItemId, 5.0) == NULL || FindValidNearbyContainer(targetContainerId, 5.0) == NULL)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_TOO_FAR);
|
|
return false;
|
|
}
|
|
|
|
int totalAmount = sourceItem->InqIntQuality(STACK_SIZE_INT, 1, TRUE);
|
|
|
|
if (amountToTransfer >= totalAmount)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> newStackItem = g_pWeenieFactory->CloneWeenie(sourceItem);
|
|
if (!newStackItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
newStackItem->SetStackSize(amountToTransfer);
|
|
newStackItem->SetID(g_pWorld->GenerateGUID(eDynamicGUID));
|
|
|
|
if (!g_pWorld->CreateEntity(newStackItem))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FinishMoveItemToContainer(newStackItem, targetContainer, targetSlot);
|
|
MakeAware(newStackItem);
|
|
|
|
//and set original item amount to what remains.
|
|
sourceItem->SetStackSize(totalAmount - amountToTransfer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CMonsterWeenie::SplitItemto3D(DWORD sourceItemId, DWORD amountToTransfer, bool animationDone)
|
|
{
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (sourceItem->IsAttunedOrContainsAttuned())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ATTUNED_ITEM);
|
|
return false;
|
|
}
|
|
|
|
if (!animationDone && !FindContainedItem(sourceItem->GetID()))
|
|
{
|
|
//from ground or other container...
|
|
CStackSplitTo3DInventoryUseEvent *splitEvent = new CStackSplitTo3DInventoryUseEvent();
|
|
splitEvent->_target_id = sourceItemId;
|
|
splitEvent->_sourceItemId = sourceItemId;
|
|
splitEvent->_amountToTransfer = amountToTransfer;
|
|
ExecuteUseEvent(splitEvent);
|
|
}
|
|
else
|
|
{
|
|
//We do this by first splitting the item to our main pack and then dropping it. We could change this to do it directly if this causes any issues.
|
|
int totalAmount = sourceItem->InqIntQuality(STACK_SIZE_INT, 1, TRUE);
|
|
|
|
if (amountToTransfer >= totalAmount)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> newStackItem = g_pWeenieFactory->CloneWeenie(sourceItem);
|
|
if (!newStackItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
newStackItem->SetStackSize(amountToTransfer);
|
|
newStackItem->SetID(g_pWorld->GenerateGUID(eDynamicGUID));
|
|
|
|
if (!g_pWorld->CreateEntity(newStackItem))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// move the new stack to our main pack in preparation for dropping it.
|
|
FinishMoveItemToContainer(newStackItem, AsContainer(), 0);
|
|
MakeAware(newStackItem);
|
|
|
|
// set original item amount to what remains.
|
|
sourceItem->SetStackSize(totalAmount - amountToTransfer);
|
|
|
|
// and now move on to dropping it
|
|
CDropInventoryUseEvent *dropEvent = new CDropInventoryUseEvent();
|
|
dropEvent->_target_id = newStackItem->GetID();
|
|
ExecuteUseEvent(dropEvent);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CMonsterWeenie::SplitItemToWield(DWORD sourceItemId, DWORD targetLoc, DWORD amountToTransfer, bool animationDone)
|
|
{
|
|
if (!animationDone && IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR);
|
|
if (!sourceItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (!Container_CanEquip(sourceItem, targetLoc))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_BE_WIELDED_FAILURE);
|
|
return false;
|
|
}
|
|
|
|
if (!animationDone && !FindContainedItem(sourceItem->GetID()))
|
|
{
|
|
//from ground or other container...
|
|
CStackSplitToWieldInventoryUseEvent *stackSplitEvent = new CStackSplitToWieldInventoryUseEvent();
|
|
stackSplitEvent->_target_id = sourceItemId;
|
|
stackSplitEvent->_sourceItemId = sourceItemId;
|
|
stackSplitEvent->_targetLoc = targetLoc;
|
|
stackSplitEvent->_amountToTransfer = amountToTransfer;
|
|
ExecuteUseEvent(stackSplitEvent);
|
|
}
|
|
else
|
|
{
|
|
int totalAmount = sourceItem->InqIntQuality(STACK_SIZE_INT, 1, TRUE);
|
|
|
|
if (amountToTransfer >= totalAmount)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> newStackItem = g_pWeenieFactory->CloneWeenie(sourceItem);
|
|
if (!newStackItem)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
newStackItem->SetStackSize(amountToTransfer);
|
|
|
|
newStackItem->SetID(g_pWorld->GenerateGUID(eDynamicGUID));
|
|
if (!g_pWorld->CreateEntity(newStackItem))
|
|
{
|
|
NotifyInventoryFailedEvent(newStackItem->GetID(), WERROR_OBJECT_GONE);
|
|
return false;
|
|
}
|
|
|
|
if (!FinishMoveItemToWield(newStackItem, targetLoc))
|
|
{
|
|
g_pWorld->RemoveEntity(newStackItem);
|
|
return false; //no need for notifications here as FinishMoveItemToWield takes care of that.
|
|
}
|
|
MakeAware(newStackItem);
|
|
|
|
//and set original item amount to what remains.
|
|
sourceItem->SetStackSize(totalAmount - amountToTransfer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMonsterWeenie::GiveItem(DWORD targetContainerId, DWORD sourceItemId, DWORD transferAmount)
|
|
{
|
|
if (IsBusyOrInAction())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindObject(targetContainerId);
|
|
std::shared_ptr<CWeenieObject> sourceItem = FindContainedItem(sourceItemId);
|
|
|
|
if (!sourceItem || !target)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return;
|
|
}
|
|
|
|
if (target->IsContained())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE);
|
|
return;
|
|
}
|
|
|
|
if (sourceItem == target || !target->AsContainer())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
if (sourceItem->IsAttunedOrContainsAttuned() && target->AsPlayer())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_ATTUNED_ITEM);
|
|
return;
|
|
}
|
|
|
|
if (DistanceTo(target, true) > 1.0)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItemId, WERROR_TOO_FAR);
|
|
return;
|
|
}
|
|
|
|
FinishGiveItem(target->AsContainer(), sourceItem, transferAmount);
|
|
}
|
|
|
|
void CMonsterWeenie::FinishGiveItem(std::shared_ptr<CContainerWeenie> targetContainer, std::shared_ptr<CWeenieObject> sourceItem, DWORD amountToTransfer)
|
|
{
|
|
if (amountToTransfer <= 0 || amountToTransfer >= 100000)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
// for now we won't support giving items that are currently equipped
|
|
if (sourceItem->IsEquipped() || sourceItem->parent.lock())
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
if (!targetContainer->Container_CanStore(sourceItem))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> targetTopLevel = targetContainer->GetWorldTopLevelOwner();
|
|
|
|
if (targetTopLevel != sourceItem->GetWorldTopLevelOwner() && targetTopLevel && targetTopLevel->IsExecutingEmote())
|
|
{
|
|
SendText(csprintf("%s is busy.", targetTopLevel->GetName().c_str()), LTT_DEFAULT);
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
if (std::shared_ptr<CPlayerWeenie> player = targetContainer->AsPlayer())
|
|
{
|
|
if (!(player->GetCharacterOptions() & AllowGive_CharacterOption))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int currentStackSize = sourceItem->InqIntQuality(STACK_SIZE_INT, 1, TRUE);
|
|
if (amountToTransfer > currentStackSize)
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> newStackItem;
|
|
if (amountToTransfer < currentStackSize)
|
|
{
|
|
// partial stack, make a new object
|
|
newStackItem = g_pWeenieFactory->CloneWeenie(sourceItem);
|
|
newStackItem->SetID(g_pWorld->GenerateGUID(eDynamicGUID));
|
|
|
|
if (!g_pWorld->CreateEntity(newStackItem, false))
|
|
{
|
|
NotifyInventoryFailedEvent(sourceItem->GetID(), WERROR_GIVE_NOT_ALLOWED);
|
|
return;
|
|
}
|
|
|
|
sourceItem->SetStackSize(currentStackSize - amountToTransfer);
|
|
newStackItem->SetStackSize(amountToTransfer);
|
|
}
|
|
else
|
|
{
|
|
newStackItem = sourceItem;
|
|
sourceItem->ReleaseFromAnyWeenieParent();
|
|
}
|
|
|
|
newStackItem->m_Qualities.SetInstanceID(CONTAINER_IID, targetContainer->GetID());
|
|
newStackItem->_cachedHasOwner = true;
|
|
|
|
std::shared_ptr<CWeenieObject> topLevelOwnerObj = newStackItem->GetWorldTopLevelOwner();
|
|
assert(topLevelOwnerObj);
|
|
assert(topLevelOwnerObj->AsContainer());
|
|
|
|
if (topLevelOwnerObj)
|
|
{
|
|
if (std::shared_ptr<CContainerWeenie> topLevelOwner = topLevelOwnerObj->AsContainer())
|
|
{
|
|
if (newStackItem == sourceItem)
|
|
{
|
|
SendNetMessage(InventoryMove(sourceItem->GetID(), targetContainer->GetID(), 0, sourceItem->RequiresPackSlot() ? 1 : 0), PRIVATE_MSG, TRUE);
|
|
}
|
|
|
|
if (!InqBoolQuality(NPC_INTERACTS_SILENTLY_BOOL, FALSE) && !topLevelOwner->InqBoolQuality(NPC_INTERACTS_SILENTLY_BOOL, FALSE))
|
|
{
|
|
targetContainer->EmitSound(Sound_ReceiveItem, 1.0f);
|
|
|
|
if (amountToTransfer > 1)
|
|
{
|
|
SendText(csprintf("You give %s %s %s.", topLevelOwner->GetName().c_str(), FormatNumberString(amountToTransfer).c_str(), newStackItem->GetPluralName().c_str()), LTT_DEFAULT);
|
|
topLevelOwner->SendText(csprintf("%s gives you %s %s.", GetName().c_str(), FormatNumberString(amountToTransfer).c_str(), newStackItem->GetPluralName().c_str()), LTT_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
SendText(csprintf("You give %s %s.", topLevelOwner->GetName().c_str(), newStackItem->GetName().c_str()), LTT_DEFAULT);
|
|
topLevelOwner->SendText(csprintf("%s gives you %s.", GetName().c_str(), newStackItem->GetName().c_str()), LTT_DEFAULT);
|
|
}
|
|
}
|
|
|
|
topLevelOwner->MakeAware(newStackItem, true);
|
|
|
|
if (newStackItem->AsContainer())
|
|
{
|
|
newStackItem->AsContainer()->MakeAwareViewContent(topLevelOwner);
|
|
}
|
|
|
|
if (AsPlayer() && newStackItem->m_Qualities.id == W_COINSTACK_CLASS)
|
|
RecalculateCoinAmount();
|
|
|
|
topLevelOwner->OnReceiveInventoryItem(GetPointer<CWeenieObject>(), newStackItem, 0);
|
|
topLevelOwner->DebugValidate();
|
|
}
|
|
}
|
|
|
|
DebugValidate();
|
|
|
|
RecalculateEncumbrance();
|
|
}
|
|
|
|
void CMonsterWeenie::Tick()
|
|
{
|
|
CContainerWeenie::Tick();
|
|
|
|
Movement_Think();
|
|
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->Update();
|
|
}
|
|
|
|
void CMonsterWeenie::OnDealtDamage(DamageEventData &damageData)
|
|
{
|
|
CWeenieObject::OnDealtDamage(damageData);
|
|
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->OnDealtDamage(damageData);
|
|
}
|
|
|
|
void CMonsterWeenie::OnTookDamage(DamageEventData &damageData)
|
|
{
|
|
CWeenieObject::OnTookDamage(damageData);
|
|
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->OnTookDamage(damageData);
|
|
}
|
|
|
|
void CMonsterWeenie::UpdateDamageList(DamageEventData &damageData)
|
|
{
|
|
std::shared_ptr<CPhysicsObj> pSource = damageData.source.lock();
|
|
|
|
if (pSource && damageData.outputDamageFinal > 0)
|
|
{
|
|
DWORD source =pSource->GetID();
|
|
|
|
if (m_aDamageSources.find(source) == m_aDamageSources.end())
|
|
{
|
|
m_aDamageSources[source] = 0;
|
|
}
|
|
|
|
m_aDamageSources[source] += damageData.outputDamageFinal;
|
|
}
|
|
}
|
|
|
|
void CMonsterWeenie::OnRegen(STypeAttribute2nd currentAttrib, int newAmount)
|
|
{
|
|
CWeenieObject::OnRegen(currentAttrib, newAmount);
|
|
|
|
if (currentAttrib == HEALTH_ATTRIBUTE_2ND)
|
|
{
|
|
DWORD maxHealth = 0;
|
|
m_Qualities.InqAttribute2nd(MAX_HEALTH_ATTRIBUTE_2ND, maxHealth, FALSE);
|
|
|
|
if (maxHealth == newAmount)
|
|
{
|
|
// reset damage sources
|
|
m_aDamageSources.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CMonsterWeenie::GivePerksForKill(std::shared_ptr<CWeenieObject> pKilled)
|
|
{
|
|
// Prevent CWeenieObject::GivePerksForKill from running
|
|
}
|
|
|
|
|
|
void CMonsterWeenie::OnIdentifyAttempted(std::shared_ptr<CWeenieObject> other)
|
|
{
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->OnIdentifyAttempted(other);
|
|
}
|
|
|
|
void CMonsterWeenie::OnResistSpell(std::shared_ptr<CWeenieObject> attacker)
|
|
{
|
|
HandleAggro(attacker);
|
|
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->OnResistSpell(attacker);
|
|
}
|
|
|
|
void CMonsterWeenie::OnEvadeAttack(std::shared_ptr<CWeenieObject> attacker)
|
|
{
|
|
HandleAggro(attacker);
|
|
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->OnEvadeAttack(attacker);
|
|
}
|
|
|
|
void CMonsterWeenie::HandleAggro(std::shared_ptr<CWeenieObject> attacker)
|
|
{
|
|
if (m_MonsterAI)
|
|
m_MonsterAI->HandleAggro(attacker);
|
|
}
|
|
|
|
/*
|
|
CBaelZharon::CBaelZharon()
|
|
{
|
|
SetName("Bael'Zharon");
|
|
SetSetupID(0x0200099E);
|
|
SetScale(1.8f);
|
|
|
|
m_ObjDesc.paletteID = 0x04001071;
|
|
m_ObjDesc.AddSubpalette(new Subpalette(0x04001072, 0, 0));
|
|
|
|
m_fTickFrequency = -1.0f;
|
|
}
|
|
*/
|
|
|
|
#if 0
|
|
CTargetDrudge::CTargetDrudge()
|
|
{
|
|
SetSetupID(0x02000034);
|
|
SetScale(0.95f);
|
|
SetName("Oak Target Drudge");
|
|
SetMotionTableID(0x0900008A);
|
|
SetPETableID(0x3400006B);
|
|
SetSoundTableID(0x20000051);
|
|
|
|
m_miBaseModel.SetBasePalette(0x01B9);
|
|
m_miBaseModel.ReplacePalette(0x08B4, 0x00, 0x00);
|
|
m_miBaseModel.ReplaceTexture(0x00, 0x0036, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x01, 0x0031, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x02, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x03, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x04, 0x0D33, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x05, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x06, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x07, 0x0D33, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x08, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x09, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x0A, 0x0035, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x0B, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x0C, 0x0030, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x0D, 0x0035, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x0E, 0x0D33, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x0E, 0x0EE8, 0x0EE8);
|
|
m_miBaseModel.ReplaceTexture(0x0F, 0x0035, 0x0D33);
|
|
m_miBaseModel.ReplaceTexture(0x10, 0x0035, 0x0D33);
|
|
m_miBaseModel.ReplaceModel(0x00, 0x005D);
|
|
m_miBaseModel.ReplaceModel(0x01, 0x005E);
|
|
m_miBaseModel.ReplaceModel(0x02, 0x006E);
|
|
m_miBaseModel.ReplaceModel(0x03, 0x0064);
|
|
m_miBaseModel.ReplaceModel(0x04, 0x18D9);
|
|
m_miBaseModel.ReplaceModel(0x05, 0x006F);
|
|
m_miBaseModel.ReplaceModel(0x06, 0x0316);
|
|
m_miBaseModel.ReplaceModel(0x07, 0x18D9);
|
|
m_miBaseModel.ReplaceModel(0x08, 0x006D);
|
|
m_miBaseModel.ReplaceModel(0x09, 0x006B);
|
|
m_miBaseModel.ReplaceModel(0x0A, 0x005F);
|
|
m_miBaseModel.ReplaceModel(0x0B, 0x006C);
|
|
m_miBaseModel.ReplaceModel(0x0C, 0x0068);
|
|
m_miBaseModel.ReplaceModel(0x0D, 0x0060);
|
|
m_miBaseModel.ReplaceModel(0x0E, 0x18D7);
|
|
m_miBaseModel.ReplaceModel(0x0F, 0x0067);
|
|
m_miBaseModel.ReplaceModel(0x10, 0x0060);
|
|
}
|
|
#endif
|
|
|
|
bool CMonsterWeenie::IsAttackMotion(DWORD motion)
|
|
{
|
|
switch ((WORD)motion)
|
|
{
|
|
case 0x62:
|
|
case 0x63:
|
|
case 0x64:
|
|
case 0x65:
|
|
case 0x66:
|
|
case 0x67:
|
|
case 0x68:
|
|
case 0x69:
|
|
case 0x6A:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CMonsterWeenie::OnMotionDone(DWORD motion, BOOL success)
|
|
{
|
|
CWeenieObject::OnMotionDone(motion, success);
|
|
|
|
if (motion == 0x40000011)
|
|
{
|
|
OnDeathAnimComplete();
|
|
}
|
|
|
|
if (m_MotionUseData.m_MotionUseType != MUT_UNDEF)
|
|
{
|
|
bool bUseSuccess = success && !IsDead();
|
|
|
|
if (motion == m_MotionUseData.m_MotionUseMotionID)
|
|
{
|
|
switch (m_MotionUseData.m_MotionUseType)
|
|
{
|
|
case MUT_CONSUME_FOOD:
|
|
{
|
|
bool bConsumed = false;
|
|
MotionUseData useData = m_MotionUseData;
|
|
|
|
if (bUseSuccess)
|
|
{
|
|
m_MotionUseData.Reset(); // necessary so this doesn't become infinitely recursive
|
|
|
|
DoForcedStopCompletely();
|
|
|
|
std::shared_ptr<CWeenieObject> pItem = FindContainedItem(useData.m_MotionUseTarget);
|
|
|
|
if (pItem)
|
|
{
|
|
ReleaseContainedItemRecursive(pItem);
|
|
|
|
bConsumed = true;
|
|
|
|
if (DWORD use_sound_did = pItem->InqDIDQuality(USE_SOUND_DID, 0))
|
|
EmitSound(use_sound_did, 1.0f);
|
|
|
|
DWORD boost_stat = pItem->InqIntQuality(BOOSTER_ENUM_INT, 0);
|
|
DWORD boost_value = pItem->InqIntQuality(BOOST_VALUE_INT, 0);
|
|
|
|
switch (boost_stat)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
case MANA_ATTRIBUTE_2ND:
|
|
{
|
|
STypeAttribute2nd maxStatType = (STypeAttribute2nd)(boost_stat - 1);
|
|
STypeAttribute2nd statType = (STypeAttribute2nd)boost_stat;
|
|
|
|
DWORD statValue = 0, maxStatValue = 0;
|
|
m_Qualities.InqAttribute2nd(statType, statValue, FALSE);
|
|
m_Qualities.InqAttribute2nd(maxStatType, maxStatValue, FALSE);
|
|
|
|
DWORD newStatValue = min(statValue + boost_value, maxStatValue);
|
|
|
|
int statChange = newStatValue - statValue;
|
|
if (statChange)
|
|
{
|
|
if (statType == HEALTH_ATTRIBUTE_2ND)
|
|
{
|
|
AdjustHealth(statChange);
|
|
NotifyAttribute2ndStatUpdated(statType);
|
|
}
|
|
else
|
|
{
|
|
m_Qualities.SetAttribute2nd(statType, newStatValue);
|
|
NotifyAttribute2ndStatUpdated(statType);
|
|
}
|
|
}
|
|
|
|
const char *vitalName = "";
|
|
switch (boost_stat)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND: vitalName = "health"; break;
|
|
case STAMINA_ATTRIBUTE_2ND: vitalName = "stamina"; break;
|
|
case MANA_ATTRIBUTE_2ND: vitalName = "mana"; break;
|
|
}
|
|
|
|
SendText(csprintf("The %s restores %d points of your %s.", pItem->GetName().c_str(), max(0, statChange), vitalName), LTT_DEFAULT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NotifyContainedItemRemoved(pItem->id);
|
|
|
|
g_pWorld->RemoveEntity(pItem);
|
|
}
|
|
}
|
|
|
|
if (!bConsumed)
|
|
NotifyInventoryFailedEvent(useData.m_MotionUseTarget, 0);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_MotionUseData.Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<CCorpseWeenie> CMonsterWeenie::CreateCorpse(bool visible)
|
|
{
|
|
if (!InValidCell())
|
|
return NULL;
|
|
|
|
// spawn corpse
|
|
// TODO is this safe to assume? mwnciau
|
|
std::shared_ptr<CCorpseWeenie> pCorpse = g_pWeenieFactory->CreateWeenieByClassID(W_CORPSE_CLASS)->AsCorpse();
|
|
|
|
pCorpse->CopyDIDStat(SETUP_DID, GetPointer<CWeenieObject>());
|
|
pCorpse->CopyDIDStat(MOTION_TABLE_DID, GetPointer<CWeenieObject>());
|
|
// pCorpse->CopyDIDStat(SOUND_TABLE_DID, this);
|
|
// pCorpse->CopyDIDStat(PHYSICS_EFFECT_TABLE_DID, this);
|
|
pCorpse->CopyFloatStat(DEFAULT_SCALE_FLOAT, GetPointer<CWeenieObject>());
|
|
pCorpse->CopyFloatStat(TRANSLUCENCY_FLOAT, GetPointer<CWeenieObject>());
|
|
|
|
pCorpse->CopyIntStat(LEVEL_INT, GetPointer<CWeenieObject>()); //copy the level so the treasure generator can have access to that value.
|
|
|
|
ObjDesc desc;
|
|
GetObjDesc(desc);
|
|
pCorpse->SetObjDesc(desc);
|
|
|
|
pCorpse->SetInitialPosition(m_Position);
|
|
pCorpse->SetName(csprintf("Corpse of %s", GetName().c_str()));
|
|
pCorpse->InitPhysicsObj();
|
|
|
|
pCorpse->m_bDontClear = false;
|
|
|
|
pCorpse->m_Qualities.SetString(LONG_DESC_STRING, csprintf("Killed by %s.", m_DeathKillerNameForCorpse.empty() ? "a mysterious source" : m_DeathKillerNameForCorpse.c_str()));
|
|
pCorpse->m_Qualities.SetInstanceID(KILLER_IID, m_DeathKillerIDForCorpse);
|
|
pCorpse->m_Qualities.SetInstanceID(VICTIM_IID, GetID());
|
|
|
|
pCorpse->MakeMovementManager(TRUE);
|
|
|
|
MovementParameters params;
|
|
params.autonomous = 0;
|
|
pCorpse->last_move_was_autonomous = false;
|
|
pCorpse->DoMotion(GetCommandID(17), ¶ms, 0);
|
|
|
|
if (!visible)
|
|
pCorpse->m_Qualities.SetBool(VISIBILITY_BOOL, false);
|
|
|
|
if (!g_pWorld->CreateEntity(pCorpse))
|
|
{
|
|
SERVER_ERROR << "Unable to create corpse of" << GetName().c_str() << "at Landblock:" << csprintf("0x%08X", m_Position.objcell_id) <<
|
|
"(X, Y, Z):" << m_Position.frame.m_origin.x << "," << m_Position.frame.m_origin.y << m_Position.frame.m_origin.z;
|
|
pCorpse = NULL;
|
|
}
|
|
|
|
m_DeathKillerIDForCorpse = 0;
|
|
m_DeathKillerNameForCorpse.clear();
|
|
|
|
return pCorpse;
|
|
}
|
|
|
|
void CMonsterWeenie::DropAllLoot(std::shared_ptr<CCorpseWeenie> pCorpse)
|
|
{
|
|
}
|
|
|
|
void CMonsterWeenie::GenerateDeathLoot(std::shared_ptr<CCorpseWeenie> pCorpse)
|
|
{
|
|
if (m_Qualities._create_list)
|
|
g_pWeenieFactory->AddFromCreateList(pCorpse, m_Qualities._create_list, (DestinationType)(Contain_DestinationType | Treasure_DestinationType));
|
|
|
|
if (DWORD deathTreasureType = InqDIDQuality(DEATH_TREASURE_TYPE_DID, 0))
|
|
g_pWeenieFactory->GenerateFromTypeOrWcid(pCorpse, DestinationType::ContainTreasure_DestinationType, deathTreasureType);
|
|
|
|
std::list<std::shared_ptr<CWeenieObject> > removeList;
|
|
|
|
for each(auto item in pCorpse->m_Items)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pItem = item.lock();
|
|
|
|
if (!pItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pItem->IsDestroyedOnDeath())
|
|
removeList.push_back(pItem);
|
|
}
|
|
|
|
for (auto item : removeList)
|
|
item->Remove();
|
|
}
|
|
|
|
void CMonsterWeenie::OnDeathAnimComplete()
|
|
{
|
|
if (!m_bWaitingForDeathToFinish)
|
|
return;
|
|
|
|
m_bWaitingForDeathToFinish = false;
|
|
|
|
if (!_IsPlayer())
|
|
{
|
|
g_pWorld->RemoveEntity(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
// create corpse
|
|
std::shared_ptr<CPlayerWeenie> player = AsPlayer();
|
|
if (!player)
|
|
{
|
|
std::shared_ptr<CCorpseWeenie> pCorpse = CreateCorpse();
|
|
|
|
if (pCorpse)
|
|
GenerateDeathLoot(pCorpse);
|
|
}
|
|
|
|
}
|
|
|
|
void CMonsterWeenie::OnDeath(DWORD killer_id)
|
|
{
|
|
CWeenieObject::OnDeath(killer_id);
|
|
|
|
DWORD mostDamageSource = killer_id;
|
|
int mostDamage = 0;
|
|
int totalDamage = 0;
|
|
|
|
if (m_aDamageSources.size() == 0)
|
|
{
|
|
mostDamageSource = killer_id;
|
|
}
|
|
else {
|
|
for (auto it = m_aDamageSources.begin(); it != m_aDamageSources.end(); ++it)
|
|
{
|
|
totalDamage += it->second;
|
|
|
|
if (it->second > mostDamage)
|
|
{
|
|
mostDamage = it->second;
|
|
mostDamageSource = it->first;
|
|
}
|
|
}
|
|
}
|
|
int level = InqIntQuality(LEVEL_INT, 0);
|
|
|
|
int xpForKill = 0;
|
|
|
|
if (level <= 0)
|
|
xpForKill = 0;
|
|
else if (!m_Qualities.InqInt(XP_OVERRIDE_INT, xpForKill, 0, FALSE))
|
|
xpForKill = (int)GetXPForKillLevel(level);
|
|
|
|
xpForKill = (int)(xpForKill * g_pConfig->KillXPMultiplier(level));
|
|
|
|
if (xpForKill > 0)
|
|
{
|
|
// hand out xp proportionally
|
|
for (auto it = m_aDamageSources.begin(); it != m_aDamageSources.end(); ++it)
|
|
{
|
|
std::shared_ptr<CWeenieObject> pSource = g_pWorld->FindObject(it->first);
|
|
double dPercentage = (double)it->second / totalDamage;
|
|
|
|
if (pSource)
|
|
{
|
|
pSource->GiveSharedXP(dPercentage * xpForKill, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_DeathKillerIDForCorpse = mostDamageSource;
|
|
if (!g_pWorld->FindObjectName(mostDamageSource, m_DeathKillerNameForCorpse))
|
|
m_DeathKillerNameForCorpse.clear();
|
|
|
|
MakeMovementManager(TRUE);
|
|
StopCompletely(0);
|
|
|
|
bool bHardcoreDeath = false;
|
|
|
|
if (g_pConfig->HardcoreMode() && _IsPlayer())
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> pKiller = g_pWorld->FindObject(mostDamageSource))
|
|
{
|
|
if (!g_pConfig->HardcoreModePlayersOnly() || pKiller->_IsPlayer())
|
|
{
|
|
bHardcoreDeath = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bHardcoreDeath)
|
|
{
|
|
MovementStruct mvs;
|
|
MovementParameters params;
|
|
|
|
mvs.type = RawCommand;
|
|
mvs.motion = Motion_Dead;
|
|
mvs.params = ¶ms;
|
|
params.action_stamp = ++m_wAnimSequence;
|
|
last_move_was_autonomous = 0;
|
|
|
|
m_bWaitingForDeathToFinish = true;
|
|
if (movement_manager->PerformMovement(mvs))
|
|
{
|
|
// animation failed for some reason
|
|
OnDeathAnimComplete();
|
|
}
|
|
else
|
|
{
|
|
Animation_Update();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!m_DeathKillerNameForCorpse.empty())
|
|
{
|
|
g_pWorld->BroadcastGlobal(ServerText(csprintf("%s has been defeated by %s.", GetName().c_str(), m_DeathKillerNameForCorpse.c_str()), LTT_DEFAULT), PRIVATE_MSG);
|
|
}
|
|
else
|
|
{
|
|
g_pWorld->BroadcastGlobal(ServerText(csprintf("%s has been defeated.", GetName().c_str()), LTT_DEFAULT), PRIVATE_MSG);
|
|
}
|
|
|
|
if (std::shared_ptr<CCorpseWeenie> pCorpse = CreateCorpse())
|
|
{
|
|
DropAllLoot(pCorpse);
|
|
}
|
|
|
|
g_pDBIO->DeleteCharacter(GetID());
|
|
|
|
BeginLogout();
|
|
}
|
|
}
|
|
|
|
bool CMonsterWeenie::IsDead()
|
|
{
|
|
if (m_bReviveAfterAnim)
|
|
return true;
|
|
|
|
return CWeenieObject::IsDead();
|
|
}
|
|
|
|
void CMonsterWeenie::ChangeCombatMode(COMBAT_MODE mode, bool playerRequested)
|
|
{
|
|
COMBAT_MODE newCombatMode = (COMBAT_MODE) InqIntQuality(COMBAT_MODE_INT, COMBAT_MODE::NONCOMBAT_COMBAT_MODE, TRUE);
|
|
DWORD new_motion_style = get_minterp()->InqStyle();
|
|
|
|
switch (mode)
|
|
{
|
|
case NONCOMBAT_COMBAT_MODE:
|
|
new_motion_style = Motion_NonCombat;
|
|
newCombatMode = COMBAT_MODE::NONCOMBAT_COMBAT_MODE;
|
|
break;
|
|
|
|
case MELEE_COMBAT_MODE:
|
|
{
|
|
std::shared_ptr<CWeenieObject> weapon =NULL;
|
|
if(!GetWieldedMelee())
|
|
weapon = GetWieldedTwoHanded();
|
|
else {
|
|
weapon = GetWieldedMelee();
|
|
}
|
|
CombatStyle default_combat_style = weapon ? (CombatStyle)weapon->InqIntQuality(DEFAULT_COMBAT_STYLE_INT, Undef_CombatStyle) : Undef_CombatStyle;
|
|
|
|
switch (default_combat_style)
|
|
{
|
|
case Undef_CombatStyle:
|
|
case Unarmed_CombatStyle:
|
|
new_motion_style = Motion_HandCombat;
|
|
newCombatMode = COMBAT_MODE::MELEE_COMBAT_MODE;
|
|
break;
|
|
|
|
case OneHanded_CombatStyle:
|
|
{
|
|
new_motion_style = Motion_SwordCombat;
|
|
|
|
if (std::shared_ptr<CWeenieObject> shield = GetWieldedShield())
|
|
{
|
|
new_motion_style = Motion_SwordShieldCombat;
|
|
}
|
|
|
|
newCombatMode = COMBAT_MODE::MELEE_COMBAT_MODE;
|
|
break;
|
|
}
|
|
|
|
case TwoHanded_CombatStyle:
|
|
new_motion_style = Motion_2HandedSwordCombat;
|
|
newCombatMode = COMBAT_MODE::MELEE_COMBAT_MODE;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case MISSILE_COMBAT_MODE:
|
|
{
|
|
std::shared_ptr<CWeenieObject> weapon = GetWieldedMissile();
|
|
CombatStyle default_combat_style = weapon ? (CombatStyle)weapon->InqIntQuality(DEFAULT_COMBAT_STYLE_INT, Undef_CombatStyle) : Undef_CombatStyle;
|
|
|
|
switch (default_combat_style)
|
|
{
|
|
case Bow_CombatStyle:
|
|
if (std::shared_ptr<CWeenieObject> ammo = GetWieldedAmmo())
|
|
{
|
|
new_motion_style = Motion_BowCombat;
|
|
newCombatMode = COMBAT_MODE::MISSILE_COMBAT_MODE;
|
|
}
|
|
else
|
|
{
|
|
NotifyWeenieError(WERROR_COMBAT_OUT_OF_AMMO);
|
|
new_motion_style = Motion_NonCombat;
|
|
newCombatMode = COMBAT_MODE::NONCOMBAT_COMBAT_MODE;
|
|
}
|
|
break;
|
|
|
|
case Crossbow_CombatStyle:
|
|
if (std::shared_ptr<CWeenieObject> ammo = GetWieldedAmmo())
|
|
{
|
|
new_motion_style = Motion_CrossbowCombat;
|
|
newCombatMode = COMBAT_MODE::MISSILE_COMBAT_MODE;
|
|
}
|
|
else
|
|
{
|
|
NotifyWeenieError(WERROR_COMBAT_OUT_OF_AMMO);
|
|
new_motion_style = Motion_NonCombat;
|
|
newCombatMode = COMBAT_MODE::NONCOMBAT_COMBAT_MODE;
|
|
}
|
|
break;
|
|
|
|
case ThrownWeapon_CombatStyle:
|
|
new_motion_style = Motion_ThrownWeaponCombat;
|
|
newCombatMode = COMBAT_MODE::MISSILE_COMBAT_MODE;
|
|
break;
|
|
|
|
case Atlatl_CombatStyle:
|
|
if (std::shared_ptr<CWeenieObject> ammo = GetWieldedAmmo())
|
|
{
|
|
new_motion_style = Motion_AtlatlCombat;
|
|
newCombatMode = COMBAT_MODE::MISSILE_COMBAT_MODE;
|
|
}
|
|
else
|
|
{
|
|
NotifyWeenieError(WERROR_COMBAT_OUT_OF_AMMO);
|
|
new_motion_style = Motion_NonCombat;
|
|
newCombatMode = COMBAT_MODE::NONCOMBAT_COMBAT_MODE;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MAGIC_COMBAT_MODE:
|
|
new_motion_style = Motion_Magic;
|
|
newCombatMode = COMBAT_MODE::MAGIC_COMBAT_MODE;
|
|
break;
|
|
}
|
|
|
|
if (new_motion_style != get_minterp()->InqStyle())
|
|
{
|
|
MovementParameters params;
|
|
get_minterp()->DoMotion(new_motion_style, ¶ms);
|
|
|
|
_server_control_timestamp++;
|
|
last_move_was_autonomous = false;
|
|
|
|
Animation_Update();
|
|
}
|
|
|
|
m_Qualities.SetInt(COMBAT_MODE_INT, newCombatMode);
|
|
|
|
if (newCombatMode != mode || !playerRequested)
|
|
{
|
|
NotifyIntStatUpdated(COMBAT_MODE_INT);
|
|
}
|
|
}
|
|
|
|
DWORD CMonsterWeenie::DoForcedUseMotion(MotionUseType useType, DWORD motion, DWORD target, DWORD childID, DWORD childLoc, MovementParameters *params)
|
|
{
|
|
m_MotionUseData.m_MotionUseType = useType;
|
|
m_MotionUseData.m_MotionUseMotionID = motion;
|
|
m_MotionUseData.m_MotionUseTarget = target;
|
|
m_MotionUseData.m_MotionUseChildID = childID;
|
|
m_MotionUseData.m_MotionUseChildLocation = childLoc;
|
|
return DoForcedMotion(motion, params);
|
|
}
|
|
|
|
bool CMonsterWeenie::ClothingPrioritySorter(const std::shared_ptr<CWeenieObject> first, const std::shared_ptr<CWeenieObject> second)
|
|
{
|
|
return ((std::shared_ptr<CWeenieObject> )first)->InqIntQuality(CLOTHING_PRIORITY_INT, 0, TRUE) < ((std::shared_ptr<CWeenieObject> )second)->InqIntQuality(CLOTHING_PRIORITY_INT, 0, TRUE);
|
|
}
|
|
|
|
void CMonsterWeenie::GetObjDesc(ObjDesc &objDesc)
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > wieldedWearable;
|
|
Container_GetWieldedByMask(wieldedWearable, ARMOR_LOC|CLOTHING_LOC);
|
|
for (auto wearable : wieldedWearable)
|
|
{
|
|
if (wearable->IsAvatarJumpsuit())
|
|
{
|
|
objDesc = wearable->m_WornObjDesc;
|
|
return;
|
|
}
|
|
}
|
|
|
|
CWeenieObject::GetObjDesc(objDesc);
|
|
|
|
DWORD head_object_id;
|
|
if (m_Qualities.InqDataID(HEAD_OBJECT_DID, head_object_id))
|
|
objDesc.AddAnimPartChange(new AnimPartChange(16, head_object_id));
|
|
|
|
DWORD old_eye_texture_id, new_eye_texture_id;
|
|
if (m_Qualities.InqDataID(DEFAULT_EYES_TEXTURE_DID, old_eye_texture_id) && m_Qualities.InqDataID(EYES_TEXTURE_DID, new_eye_texture_id))
|
|
objDesc.AddTextureMapChange(new TextureMapChange(16, old_eye_texture_id, new_eye_texture_id));
|
|
|
|
DWORD old_nose_texture_id, new_nose_texture_id;
|
|
if (m_Qualities.InqDataID(DEFAULT_NOSE_TEXTURE_DID, old_nose_texture_id) && m_Qualities.InqDataID(NOSE_TEXTURE_DID, new_nose_texture_id))
|
|
objDesc.AddTextureMapChange(new TextureMapChange(16, old_nose_texture_id, new_nose_texture_id));
|
|
|
|
DWORD old_mouth_texture_id, new_mouth_texture_id;
|
|
if (m_Qualities.InqDataID(DEFAULT_MOUTH_TEXTURE_DID, old_mouth_texture_id) && m_Qualities.InqDataID(MOUTH_TEXTURE_DID, new_mouth_texture_id))
|
|
objDesc.AddTextureMapChange(new TextureMapChange(16, old_mouth_texture_id, new_mouth_texture_id));
|
|
|
|
DWORD skin_palette_id;
|
|
if (m_Qualities.InqDataID(SKIN_PALETTE_DID, skin_palette_id))
|
|
objDesc.AddSubpalette(new Subpalette(skin_palette_id, 0 << 3, 0x18 << 3));
|
|
|
|
DWORD hair_palette_id;
|
|
if (m_Qualities.InqDataID(HAIR_PALETTE_DID, hair_palette_id))
|
|
objDesc.AddSubpalette(new Subpalette(hair_palette_id, 0x18 << 3, 0x8 << 3));
|
|
|
|
DWORD eye_palette_id;
|
|
if (m_Qualities.InqDataID(EYES_PALETTE_DID, eye_palette_id))
|
|
objDesc.AddSubpalette(new Subpalette(eye_palette_id, 0x20 << 3, 0x8 << 3));
|
|
|
|
/*
|
|
std::list<std::shared_ptr<CWeenieObject> > wieldedClothings;
|
|
Container_GetWieldedByMask(wieldedClothings, CLOTHING_LOC);
|
|
wieldedClothings.sort(ClothingPrioritySorter);
|
|
|
|
for (auto clothing : wieldedClothings)
|
|
{
|
|
if (clothing->IsHelm())
|
|
{
|
|
if (!ShowHelm())
|
|
continue;
|
|
}
|
|
|
|
DWORD clothing_table_id = clothing->InqDIDQuality(CLOTHINGBASE_DID, 0);
|
|
DWORD palette_template_key = clothing->InqIntQuality(PALETTE_TEMPLATE_INT, 0);
|
|
|
|
if (clothing_table_id && !clothing->IsAvatarJumpsuit())
|
|
{
|
|
ClothingTable *clothingTable = ClothingTable::Get(clothing_table_id);
|
|
|
|
if (clothingTable)
|
|
{
|
|
ObjDesc od;
|
|
ShadePackage shades(clothing->InqFloatQuality(SHADE_FLOAT, 0.0));
|
|
|
|
double shadeVal;
|
|
if (clothing->m_Qualities.InqFloat(SHADE2_FLOAT, shadeVal))
|
|
shades._val[1] = shadeVal;
|
|
if (clothing->m_Qualities.InqFloat(SHADE3_FLOAT, shadeVal))
|
|
shades._val[2] = shadeVal;
|
|
if (clothing->m_Qualities.InqFloat(SHADE4_FLOAT, shadeVal))
|
|
shades._val[3] = shadeVal;
|
|
|
|
clothingTable->BuildObjDesc(GetSetupID(), palette_template_key, &shades, &od);
|
|
objDesc += od;
|
|
|
|
ClothingTable::Release(clothingTable);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
objDesc += clothing->m_WornObjDesc;
|
|
}
|
|
}
|
|
*/
|
|
|
|
std::list<std::shared_ptr<CWeenieObject> > wieldedArmors;
|
|
Container_GetWieldedByMask(wieldedArmors, CLOTHING_LOC|ARMOR_LOC);
|
|
wieldedArmors.sort(ClothingPrioritySorter);
|
|
|
|
for (auto armor : wieldedArmors)
|
|
{
|
|
if (armor->IsHelm())
|
|
{
|
|
if (!ShowHelm())
|
|
continue;
|
|
}
|
|
|
|
DWORD clothing_table_id = armor->InqDIDQuality(CLOTHINGBASE_DID, 0);
|
|
DWORD palette_template_key = armor->InqIntQuality(PALETTE_TEMPLATE_INT, 0);
|
|
|
|
if (clothing_table_id && !armor->IsAvatarJumpsuit())
|
|
{
|
|
ClothingTable *clothingTable = ClothingTable::Get(clothing_table_id);
|
|
|
|
if (clothingTable)
|
|
{
|
|
ObjDesc od;
|
|
ShadePackage shades(armor->InqFloatQuality(SHADE_FLOAT, 0.0));
|
|
|
|
double shadeVal;
|
|
if (armor->m_Qualities.InqFloat(SHADE2_FLOAT, shadeVal))
|
|
shades._val[1] = shadeVal;
|
|
if (armor->m_Qualities.InqFloat(SHADE3_FLOAT, shadeVal))
|
|
shades._val[2] = shadeVal;
|
|
if (armor->m_Qualities.InqFloat(SHADE4_FLOAT, shadeVal))
|
|
shades._val[3] = shadeVal;
|
|
|
|
clothingTable->BuildObjDesc(GetSetupID(), palette_template_key, &shades, &od);
|
|
objDesc += od;
|
|
|
|
ClothingTable::Release(clothingTable);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
objDesc += armor->m_WornObjDesc;
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD CMonsterWeenie::OnReceiveInventoryItem(std::shared_ptr<CWeenieObject> source, std::shared_ptr<CWeenieObject> item, DWORD desired_slot)
|
|
{
|
|
if (source != GetPointer<CWeenieObject>())
|
|
{
|
|
bool accepted = false;
|
|
// Use our emote table to determine what to do with this item.
|
|
if (m_Qualities._emote_table)
|
|
{
|
|
PackableList<EmoteSet> *emoteCategory = m_Qualities._emote_table->_emote_table.lookup(Give_EmoteCategory);
|
|
|
|
if (emoteCategory)
|
|
{
|
|
double dice = Random::GenFloat(0.0, 1.0);
|
|
double lastProbability = -1.0;
|
|
|
|
for (auto &emoteSet : *emoteCategory)
|
|
{
|
|
if (emoteSet.classID == item->m_Qualities.id)
|
|
{
|
|
if (dice >= emoteSet.probability)
|
|
continue;
|
|
|
|
if (lastProbability < 0.0 || lastProbability == emoteSet.probability)
|
|
{
|
|
if (item->InqIntQuality(STACK_SIZE_INT, 1) > 1)
|
|
{
|
|
item->DecrementStackNum(1, false);
|
|
SimulateGiveObject(source->AsContainer(), item);
|
|
}
|
|
else
|
|
g_pWorld->RemoveEntity(item);
|
|
|
|
MakeEmoteManager()->ExecuteEmoteSet(emoteSet, source->GetID());
|
|
|
|
lastProbability = emoteSet.probability;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (source)
|
|
{
|
|
ChanceExecuteEmoteSet(source->GetID(), Refuse_EmoteCategory);
|
|
SimulateGiveObject(source->AsContainer(), item);
|
|
}
|
|
else
|
|
g_pWorld->RemoveEntity(item);
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
// Unless the source is ourselves, that means the item is spawning.
|
|
item->ReleaseFromAnyWeenieParent(false, true);
|
|
item->SetWieldedLocation(INVENTORY_LOC::NONE_LOC);
|
|
|
|
item->SetWeenieContainer(GetID());
|
|
item->ReleaseFromBlock();
|
|
|
|
return Container_InsertInventoryItem(0, item, desired_slot);
|
|
}
|
|
}
|
|
|
|
double CMonsterWeenie::GetMeleeDefenseModUsingWielded()
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, WEAPON_LOC | HELD_LOC);
|
|
|
|
double defenseMod = 1.0;
|
|
for (auto item : wielded)
|
|
{
|
|
defenseMod *= item->GetMeleeDefenseMod();
|
|
}
|
|
|
|
defenseMod *= CWeenieObject::GetMeleeDefenseMod();
|
|
|
|
Container_GetWieldedByMask(wielded, ARMOR_LOC);
|
|
for (auto item : m_Wielded) //check all armor for appropriate imbue effects
|
|
{
|
|
std::shared_ptr<CWeenieObject> pItem = item.lock();
|
|
|
|
if (!pItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pItem->GetImbueEffects() & MeleeDefense_ImbuedEffectType)
|
|
defenseMod += 0.01;
|
|
}
|
|
|
|
return defenseMod;
|
|
}
|
|
|
|
double CMonsterWeenie::GetMissileDefenseModUsingWielded()
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, WEAPON_LOC | HELD_LOC);
|
|
|
|
double defenseMod = 1.0;
|
|
for (auto item : wielded)
|
|
{
|
|
defenseMod *= item->GetMissileDefenseMod();
|
|
}
|
|
|
|
defenseMod *= CWeenieObject::GetMissileDefenseMod();
|
|
|
|
Container_GetWieldedByMask(wielded, ARMOR_LOC);
|
|
for (auto item : m_Wielded) //check all armor for appropriate imbue effects
|
|
{
|
|
std::shared_ptr<CWeenieObject> pItem = item.lock();
|
|
|
|
if (!pItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pItem->GetImbueEffects() & MissileDefense_ImbuedEffectType)
|
|
defenseMod += 0.01;
|
|
}
|
|
|
|
return defenseMod;
|
|
}
|
|
|
|
double CMonsterWeenie::GetMagicDefenseModUsingWielded()
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, WEAPON_LOC | HELD_LOC);
|
|
|
|
double defenseMod = 1.0;
|
|
for (auto item : wielded)
|
|
{
|
|
defenseMod *= item->GetMagicDefenseMod();
|
|
}
|
|
|
|
defenseMod *= CWeenieObject::GetMagicDefenseMod();
|
|
|
|
/* Container_GetWieldedByMask(wielded, ARMOR_LOC);
|
|
for (auto item : m_Wielded) //check all armor for appropriate imbue effects - commented out as this is checked in TryMagicResist and should be adding 1 to skill, not 1%
|
|
{
|
|
if (item->GetImbueEffects() & MagicDefense_ImbuedEffectType)
|
|
defenseMod += 0.01;
|
|
}*/
|
|
|
|
return defenseMod;
|
|
}
|
|
|
|
int CMonsterWeenie::GetAttackTimeUsingWielded()
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, WEAPON_LOC);
|
|
|
|
for (auto item : wielded)
|
|
{
|
|
return item->GetAttackTime();
|
|
}
|
|
|
|
return CWeenieObject::GetAttackTime();
|
|
}
|
|
|
|
int CMonsterWeenie::GetAttackTime()
|
|
{
|
|
/*
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, WEAPON_LOC);
|
|
|
|
int totalTime = CWeenieObject::GetAttackTime();
|
|
|
|
for (auto item : wielded)
|
|
{
|
|
totalTime += item->GetAttackTime();
|
|
}
|
|
|
|
return totalTime;
|
|
*/
|
|
return CWeenieObject::GetAttackTime();
|
|
}
|
|
|
|
int CMonsterWeenie::GetAttackDamage()
|
|
{
|
|
/*
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, WEAPON_LOC);
|
|
|
|
int damage = CWeenieObject::GetAttackDamage();
|
|
|
|
for (auto item : wielded)
|
|
{
|
|
damage += item->GetAttackDamage();
|
|
}
|
|
*/
|
|
|
|
return CWeenieObject::GetAttackDamage();
|
|
}
|
|
|
|
float CMonsterWeenie::GetEffectiveArmorLevel(DamageEventData &damageData, bool bIgnoreMagicArmor)
|
|
{
|
|
std::list<std::shared_ptr<CWeenieObject> > wielded;
|
|
Container_GetWieldedByMask(wielded, ARMOR_LOC|CLOTHING_LOC|SHIELD_LOC);
|
|
|
|
EnchantedQualityDetails buffDetails;
|
|
|
|
//body part
|
|
GetBodyArmorEnchantmentDetails(damageData.hitPart, damageData.damage_type, &buffDetails);
|
|
|
|
//body
|
|
buffDetails.rawValue += CWeenieObject::GetEffectiveArmorLevel(damageData, bIgnoreMagicArmor);
|
|
|
|
//equipment
|
|
for (auto item : wielded)
|
|
buffDetails.rawValue += item->GetEffectiveArmorLevel(damageData, bIgnoreMagicArmor);
|
|
|
|
if (damageData.isArmorRending && damageData.armorRendingMultiplier < buffDetails.valueDecreasingMultiplier)
|
|
buffDetails.valueDecreasingMultiplier = damageData.armorRendingMultiplier;
|
|
buffDetails.CalculateEnchantedValue();
|
|
|
|
if (bIgnoreMagicArmor)
|
|
return buffDetails.rawValue; //Take the Raw value for Hollows. Debuffs should not count.
|
|
else
|
|
return buffDetails.enchantedValue;
|
|
}
|
|
|
|
void CMonsterWeenie::TryMeleeAttack(DWORD target_id, ATTACK_HEIGHT height, float power, DWORD motion)
|
|
{
|
|
if (IsDead())
|
|
{
|
|
NotifyAttackDone(WERROR_DEAD);
|
|
return;
|
|
}
|
|
|
|
if (!IsAttacking() && IsBusyOrInAction() || (_blockNewAttacksUntil != -1 && _blockNewAttacksUntil > Timer::cur_time))
|
|
{
|
|
NotifyAttackDone(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
if (!m_AttackManager)
|
|
{
|
|
m_AttackManager = new AttackManager(GetPointer<CWeenieObject>());
|
|
}
|
|
|
|
// duplicate attack
|
|
m_AttackManager->BeginMeleeAttack(target_id, height, power, m_MonsterAI ? m_MonsterAI->GetChaseDistance() : 15.0f, motion);
|
|
|
|
// ensure there's no heartbeat animation
|
|
_last_update_pos = Timer::cur_time;
|
|
}
|
|
|
|
void CMonsterWeenie::TryMissileAttack(DWORD target_id, ATTACK_HEIGHT height, float power, DWORD motion)
|
|
{
|
|
if (IsDead())
|
|
{
|
|
//NotifyWeenieError(WERROR_DEAD);
|
|
NotifyAttackDone(WERROR_DEAD);
|
|
return;
|
|
}
|
|
|
|
if (!IsAttacking() && IsBusyOrInAction() || (_blockNewAttacksUntil != -1 && _blockNewAttacksUntil > Timer::cur_time))
|
|
{
|
|
//NotifyWeenieError(WERROR_ACTIONS_LOCKED);
|
|
NotifyAttackDone(WERROR_ACTIONS_LOCKED);
|
|
return;
|
|
}
|
|
|
|
if (!m_AttackManager)
|
|
m_AttackManager = new AttackManager(GetPointer<CWeenieObject>());
|
|
|
|
m_AttackManager->BeginMissileAttack(target_id, height, power, motion);
|
|
|
|
// ensure there's no heartbeat animation
|
|
_last_update_pos = Timer::cur_time;
|
|
}
|
|
|
|
BOOL CMonsterWeenie::DoCollision(const class ObjCollisionProfile &prof)
|
|
{
|
|
if (prof.IsDoor() && !AsPlayer())
|
|
{
|
|
if (std::shared_ptr<CWeenieObject> weenie = g_pWorld->FindObject(prof.id))
|
|
{
|
|
if (std::shared_ptr<CBaseDoor> door = weenie->AsDoor())
|
|
{
|
|
if (!door->IsLocked() && door->IsClosed())
|
|
{
|
|
door->OpenDoor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return CContainerWeenie::DoCollision(prof);
|
|
}
|
|
|
|
int CMonsterWeenie::AdjustHealth(int amount)
|
|
{
|
|
CWeenieObject::AdjustHealth(amount);
|
|
|
|
DWORD maxHealth = 0;
|
|
m_Qualities.InqAttribute2nd(MAX_HEALTH_ATTRIBUTE_2ND, maxHealth, FALSE);
|
|
DWORD currentHealth = 0;
|
|
m_Qualities.InqAttribute2nd(HEALTH_ATTRIBUTE_2ND, currentHealth, FALSE);
|
|
|
|
if (maxHealth == currentHealth)
|
|
{
|
|
// reset damage sources
|
|
m_aDamageSources.clear();
|
|
}
|
|
|
|
return amount;
|
|
} |