#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(), m_Qualities._create_list, (DestinationType)(Wield_DestinationType | WieldTreasure_DestinationType)); if (DWORD wieldedTreasureType = InqDIDQuality(WIELDED_TREASURE_TYPE_DID, 0)) g_pWeenieFactory->GenerateFromTypeOrWcid(GetPointer(), 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 CMonsterWeenie::SpawnWielded(DWORD index, SmartArray possibleStyles, DWORD color, SmartArray 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 weenie = SpawnWielded(wcid, color, shade); weenie->m_Qualities.SetDataID(CLOTHINGBASE_DID, clothingTable); return weenie; } std::shared_ptr CMonsterWeenie::SpawnWielded(DWORD wcid, int ptid, float shade) { std::shared_ptr 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 CMonsterWeenie::SpawnWielded(std::shared_ptr 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 CMonsterWeenie::FindValidNearbyItem(DWORD itemId, float maxDistance) { //do we have it? std::shared_ptr item = FindContainedItem(itemId); if (!item) { // maybe it's in an external container if (_lastOpenedRemoteContainerId != 0) { std::shared_ptr 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 CMonsterWeenie::FindValidNearbyContainer(DWORD containerId, float maxDistance) { //do we have it? std::shared_ptr container = FindContainer(containerId); if (!container) { // maybe it's in the external container if (_lastOpenedRemoteContainerId != 0) { std::shared_ptr externalContainer = FindContainer(_lastOpenedRemoteContainerId); if (externalContainer && externalContainer->_openedById == GetID()) container = externalContainer->FindContainer(containerId); } if (!container) { // maybe it's on the ground std::shared_ptr 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 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 item, std::shared_ptr 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 sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR); if (!sourceItem) { NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE); return false; } std::shared_ptr 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 sourceItem, std::shared_ptr 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(); DWORD dwCell = GetLandcell(); //if (!sourceItem->HasOwner()) //{ if (std::shared_ptr 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()); if (bSendEvent) { if (sourceItem->AsContainer()) sourceItem->AsContainer()->MakeAwareViewContent(GetPointer()); SendNetMessage(InventoryMove(sourceItem->GetID(), targetContainer->GetID(), targetSlot, sourceItem->RequiresPackSlot() ? 1 : 0), PRIVATE_MSG, TRUE); } if (targetContainer->GetWorldTopLevelOwner() != GetPointer()) 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 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 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()); sourceItem->OnDropped(GetPointer()); } 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 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 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(); int error = CheckWieldRequirements(sourceItem, GetPointer(), WIELD_REQUIREMENTS_INT, WIELD_SKILLTYPE_INT, WIELD_DIFFICULTY_INT); if (error != WERROR_NONE) { NotifyInventoryFailedEvent(sourceItem->GetID(), error); return false; } error = CheckWieldRequirements(sourceItem, GetPointer(), 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(), 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(), 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 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()); sourceItem->OnWield(GetPointer()); 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 sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR); if (!sourceItem) { NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE); return false; } std::shared_ptr 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 sourceItem = FindValidNearbyItem(sourceItemId, USEDISTANCE_FAR); if (!sourceItem) { NotifyInventoryFailedEvent(sourceItemId, WERROR_OBJECT_GONE); return false; } std::shared_ptr 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 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 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 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 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 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 target = g_pWorld->FindObject(targetContainerId); std::shared_ptr 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 targetContainer, std::shared_ptr 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 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 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 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 topLevelOwnerObj = newStackItem->GetWorldTopLevelOwner(); assert(topLevelOwnerObj); assert(topLevelOwnerObj->AsContainer()); if (topLevelOwnerObj) { if (std::shared_ptr 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(), 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 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 pKilled) { // Prevent CWeenieObject::GivePerksForKill from running } void CMonsterWeenie::OnIdentifyAttempted(std::shared_ptr other) { if (m_MonsterAI) m_MonsterAI->OnIdentifyAttempted(other); } void CMonsterWeenie::OnResistSpell(std::shared_ptr attacker) { HandleAggro(attacker); if (m_MonsterAI) m_MonsterAI->OnResistSpell(attacker); } void CMonsterWeenie::OnEvadeAttack(std::shared_ptr attacker) { HandleAggro(attacker); if (m_MonsterAI) m_MonsterAI->OnEvadeAttack(attacker); } void CMonsterWeenie::HandleAggro(std::shared_ptr 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 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 CMonsterWeenie::CreateCorpse(bool visible) { if (!InValidCell()) return NULL; // spawn corpse // TODO is this safe to assume? mwnciau std::shared_ptr pCorpse = g_pWeenieFactory->CreateWeenieByClassID(W_CORPSE_CLASS)->AsCorpse(); pCorpse->CopyDIDStat(SETUP_DID, GetPointer()); pCorpse->CopyDIDStat(MOTION_TABLE_DID, GetPointer()); // pCorpse->CopyDIDStat(SOUND_TABLE_DID, this); // pCorpse->CopyDIDStat(PHYSICS_EFFECT_TABLE_DID, this); pCorpse->CopyFloatStat(DEFAULT_SCALE_FLOAT, GetPointer()); pCorpse->CopyFloatStat(TRANSLUCENCY_FLOAT, GetPointer()); pCorpse->CopyIntStat(LEVEL_INT, GetPointer()); //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 pCorpse) { } void CMonsterWeenie::GenerateDeathLoot(std::shared_ptr 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 > removeList; for each(auto item in pCorpse->m_Items) { std::shared_ptr 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()); } // create corpse std::shared_ptr player = AsPlayer(); if (!player) { std::shared_ptr 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 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 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 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 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 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 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 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 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 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 first, const std::shared_ptr second) { return ((std::shared_ptr )first)->InqIntQuality(CLOTHING_PRIORITY_INT, 0, TRUE) < ((std::shared_ptr )second)->InqIntQuality(CLOTHING_PRIORITY_INT, 0, TRUE); } void CMonsterWeenie::GetObjDesc(ObjDesc &objDesc) { std::list > 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 > 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 > 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 source, std::shared_ptr item, DWORD desired_slot) { if (source != GetPointer()) { bool accepted = false; // Use our emote table to determine what to do with this item. if (m_Qualities._emote_table) { PackableList *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 > 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 pItem = item.lock(); if (!pItem) { continue; } if (pItem->GetImbueEffects() & MeleeDefense_ImbuedEffectType) defenseMod += 0.01; } return defenseMod; } double CMonsterWeenie::GetMissileDefenseModUsingWielded() { std::list > 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 pItem = item.lock(); if (!pItem) { continue; } if (pItem->GetImbueEffects() & MissileDefense_ImbuedEffectType) defenseMod += 0.01; } return defenseMod; } double CMonsterWeenie::GetMagicDefenseModUsingWielded() { std::list > 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 > wielded; Container_GetWieldedByMask(wielded, WEAPON_LOC); for (auto item : wielded) { return item->GetAttackTime(); } return CWeenieObject::GetAttackTime(); } int CMonsterWeenie::GetAttackTime() { /* std::list > 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 > 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 > 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()); } // 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()); 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 weenie = g_pWorld->FindObject(prof.id)) { if (std::shared_ptr 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; }