#include "stdafx.h" #include "Map.h" #ifdef GAMESERVER # include "GameServerDlg.h" # include "MagicInstance.h" #else # include "../AIServer/ServerDlg.h" # include "../AIServer/Npc.h" # include "../AIServer/User.h" #endif #include Unit::Unit(UnitType unitType) : m_pMap(nullptr), m_pRegion(nullptr), m_sRegionX(0), m_sRegionZ(0), m_unitType(unitType) { Initialize(); } void Unit::Initialize() { InitType4(true); m_pMap = nullptr; m_pRegion = nullptr; m_reblvl = 0; SetPosition(0.0f, 0.0f, 0.0f); m_bLevel = 0; m_bNation = 0; m_sTotalHit = 0; m_sTotalAc = 0; m_fTotalHitrate = 0.0f; m_fTotalEvasionrate = 0.0f; m_bResistanceBonus = 0; m_bAttackAmount = 100; m_sFireR = m_sColdR = m_sLightningR = m_sMagicR = m_sDiseaseR = m_sPoisonR = 0; m_sDaggerR = m_sSwordR = m_sAxeR = m_sMaceR = m_sSpearR = m_sBowR = 0; m_byDaggerRAmount = m_byBowRAmount = 0; m_equippedItemBonuses.clear(); m_bCanStealth = true; m_bReflectArmorType = 0; m_bIsBlinded = false; m_bCanUseSkills = m_bCanUsePotions = m_bCanTeleport = true; m_bInstantCast = false; m_bIsUndead = m_bIsKaul = false; m_bisReturnee = false; m_bIsDevil = false; m_bBlockPhysical = m_bBlockMagic = false; m_bBlockCurses = m_bReflectCurses = false; m_bMirrorDamage = false; m_byMirrorAmount = 0; m_sAttackSpeedAmount = 100; // this is for the duration spells Type 4 m_bSpeedAmount = 100; m_sACAmount = 0; m_sACPercent = 100; m_bAttackAmount = 100; m_sMagicAttackAmount = 0; m_sMaxHPAmount = m_sMaxMPAmount = 0; m_bHitRateAmount = 100; m_sAvoidRateAmount = 100; m_bAddFireR = m_bAddColdR = m_bAddLightningR = 0; m_bAddMagicR = m_bAddDiseaseR = m_bAddPoisonR = 0; m_bPctFireR = m_bPctColdR = m_bPctLightningR = 100; m_bPctMagicR = m_bPctDiseaseR = m_bPctPoisonR = 100; m_bMagicDamageReduction = 100; m_bManaAbsorb = 0; AbsorbCount = 0; m_bRadiusAmount = 0; m_buffCount = 0; m_bIceSpeedAmount = 0; AbsorbedAmmount = 0; m_oSocketID = -1; m_bEventRoom = 0; SetUnitEventRoom(0); InitType3(); } /* NOTE: Due to KO messiness, we can really only calculate a 2D distance/ There are a lot of instances where the y (height level, in this case) coord isn't set, which understandably screws things up a lot. */ // Calculate the distance between 2 2D points. float Unit::GetDistance(float fx, float fz) { return GetDistance(GetX(), GetZ(), fx, fz); } // Calculate the 2D distance between Units. float Unit::GetDistance(Unit * pTarget) { if (pTarget == nullptr) return false; ASSERT(pTarget != nullptr); if (GetZoneID() != pTarget->GetZoneID()) return -FLT_MAX; return GetDistance(pTarget->GetX(), pTarget->GetZ()); } float Unit::GetDistanceSqrt(Unit * pTarget) { ASSERT(pTarget != nullptr); if (GetZoneID() != pTarget->GetZoneID()) return -FLT_MAX; return sqrtf(GetDistance(pTarget->GetX(), pTarget->GetZ())); } // Check to see if the Unit is in 2D range of another Unit. // Range MUST be squared already. bool Unit::isInRange(Unit * pTarget, float fSquaredRange) { return (GetDistance(pTarget) <= fSquaredRange); } // Check to see if we're in the 2D range of the specified coordinates. // Range MUST be squared already. bool Unit::isInRange(float fx, float fz, float fSquaredRange) { return (GetDistance(fx, fz) <= fSquaredRange); } // Check to see if the Unit is in 2D range of another Unit. // Range must NOT be squared already. // This is less preferable to the more common precalculated range. bool Unit::isInRangeSlow(Unit * pTarget, float fNonSquaredRange) { return isInRange(pTarget, pow(fNonSquaredRange, 2.0f)); } // Check to see if the Unit is in 2D range of the specified coordinates. // Range must NOT be squared already. // This is less preferable to the more common precalculated range. bool Unit::isInRangeSlow(float fx, float fz, float fNonSquaredRange) { return isInRange(fx, fz, pow(fNonSquaredRange, 2.0f)); } float Unit::GetDistance(float fStartX, float fStartZ, float fEndX, float fEndZ) { return pow(fStartX - fEndX, 2.0f) + pow(fStartZ - fEndZ, 2.0f); } bool Unit::isInRange(float fStartX, float fStartZ, float fEndX, float fEndZ, float fSquaredRange) { return (GetDistance(fStartX, fStartZ, fEndX, fEndZ) <= fSquaredRange); } bool Unit::isInRangeSlow(float fStartX, float fStartZ, float fEndX, float fEndZ, float fNonSquaredRange) { return isInRange(fStartX, fStartZ, fEndX, fEndZ, pow(fNonSquaredRange, 2.0f)); } #ifdef GAMESERVER void Unit::SetRegion(uint16 x /*= -1*/, uint16 z /*= -1*/) { m_sRegionX = x; m_sRegionZ = z; m_pRegion = m_pMap->GetRegion(x, z); // TODO: Clean this up } bool Unit::RegisterRegion() { if(this == nullptr) return false; uint16 new_region_x = GetNewRegionX(), new_region_z = GetNewRegionZ(), old_region_x = GetRegionX(), old_region_z = GetRegionZ(); if (GetRegion() == nullptr || (old_region_x == new_region_x && old_region_z == new_region_z)) return false; AddToRegion(new_region_x, new_region_z); RemoveRegion(old_region_x - new_region_x, old_region_z - new_region_z); InsertRegion(new_region_x - old_region_x, new_region_z - old_region_z); return true; } void Unit::RemoveRegion(int16 del_x, int16 del_z) { ASSERT(GetMap() != nullptr); Packet result; GetInOut(result, INOUT_OUT); g_pMain->Send_OldRegions(&result, del_x, del_z, GetMap(), GetRegionX(), GetRegionZ(),nullptr,GetEventRoom()); } void Unit::InsertRegion(int16 insert_x, int16 insert_z) { ASSERT(GetMap() != nullptr); Packet result; GetInOut(result, INOUT_IN); g_pMain->Send_NewRegions(&result, insert_x, insert_z, GetMap(), GetRegionX(), GetRegionZ(),nullptr,GetEventRoom()); } #endif /* These should not be declared here, but it's necessary until the AI server better shares unit code */ /** * @brief Calculates damage for players attacking either monsters/NPCs or other players. * * @param pTarget Target unit. * @param pSkill The skill used in the attack, if applicable.. * @param bPreviewOnly true to preview the damage only and not apply any item bonuses. * Used by AI logic to determine who to target (by checking who deals the most damage). * * @return The damage. */ short CUser::GetDamage(Unit *pTarget, _MAGIC_TABLE *pSkill /*= nullptr*/, bool bPreviewOnly /*= false*/) { /* This seems identical to users attacking NPCs/monsters. The only differences are: - GetACDamage() is not called - the resulting damage is not divided by 3. */ int32 damage = 0; int random = 0; int32 temp_hit = 0, temp_ac = 0, temp_ap = 0, temp_hit_B = 0; uint8 result; if (pTarget == nullptr || pTarget->isDead()) return -1; // Trigger item procs if (pTarget->isPlayer() && !bPreviewOnly) { OnAttack(pTarget, AttackTypePhysical); pTarget->OnDefend(this, AttackTypePhysical); } temp_ac = pTarget->m_sTotalAc; // A unit's total AC shouldn't ever go below 0. if ((pTarget->m_sACAmount) <= 0) pTarget->m_sACAmount = 0; else temp_ac += pTarget->m_sACAmount; if (pTarget->m_sACPercent > 0 && pTarget->m_sACPercent < 100) temp_ac -= temp_ac * (100 - pTarget->m_sACPercent) / 100; temp_ap = m_sTotalHit * m_bAttackAmount; #ifdef GAMESERVER // Apply player vs player AC/AP bonuses. if (pTarget->isPlayer()) { CUser * pTUser = TO_USER(pTarget); // NOTE: using a = a*v instead of a *= v because the compiler assumes different // types being multiplied, which results in these calcs not behaving correctly. // adjust player AP by percent, for skills like "Critical Point" temp_ap = temp_ap * m_bPlayerAttackAmount / 100; // Now handle class-specific AC/AP bonuses. temp_ac = temp_ac * (100 + pTUser->m_byAcClassBonusAmount[GetBaseClassType() - 1]) / 100; temp_ap = temp_ap * (100 + m_byAPClassBonusAmount[pTUser->GetBaseClassType() - 1]) / 100; } #endif // Allow for complete physical damage blocks. // NOTE: Unsure if we need to count skill usage as magic damage or if we // should only block that in explicit magic damage skills (CMagicProcess::GetMagicDamage()). if (pTarget->m_bBlockPhysical) return 0; temp_hit_B = (int)((temp_ap * 200 / 100) / (temp_ac + 240)); // Skill/arrow hit. if (pSkill != nullptr) { // SKILL HIT! YEAH! if (pSkill->bType[0] == 1) { _MAGIC_TYPE1 *pType1 = g_pMain->m_Magictype1Array.GetData(pSkill->iNum); if (pType1 == nullptr) return -1; // Non-relative hit. if (pType1->bHitType) { result = (pType1->sHitRate <= myrand(0, 100) ? FAIL : SUCCESS); } // Relative hit. else { result = GetHitRate((m_fTotalHitrate / pTarget->m_fTotalEvasionrate) * (pType1->sHitRate / 100.0f)); } temp_hit = (int32)(temp_hit_B * (pType1->sHit / 100.0f)); } // ARROW HIT! YEAH! else if (pSkill->bType[0] == 2) { _MAGIC_TYPE2 *pType2 = g_pMain->m_Magictype2Array.GetData(pSkill->iNum); if (pType2 == nullptr) return -1; // Non-relative/Penetration hit. if (pType2->bHitType == 1 || pType2->bHitType == 2) { result = (pType2->sHitRate <= myrand(0, 100) ? FAIL : SUCCESS); } // Relative hit/Arc hit. else { result = GetHitRate((m_fTotalHitrate / pTarget->m_fTotalEvasionrate) * (pType2->sHitRate / 100.0f)); } if (pType2->bHitType == 1 /* || pType2->bHitType == 2 */) temp_hit = (int32)(m_sTotalHit * m_bAttackAmount * (pType2->sAddDamage / 100.0f) / 100); else temp_hit = (int32)(temp_hit_B * (pType2->sAddDamage / 100.0f)); } } // Normal hit (R attack) else { temp_hit = temp_ap / 100; result = GetHitRate(m_fTotalHitrate / pTarget->m_fTotalEvasionrate); } switch (result) { // 1. Magical item damage.... case GREAT_SUCCESS: case SUCCESS: case NORMAL: if (pSkill != nullptr) { // Skill Hit. damage = temp_hit; random = myrand(0, damage); if (pSkill->bType[0] == 1) damage = (short)((temp_hit + 0.3f * random) + 0.99f); else damage = (short)(((temp_hit * 0.6f) + 1.0f * random) + 0.99f); } else { // Normal Hit. #ifdef GAMESERVER if (isGM() && !pTarget->isPlayer()) { if (g_pMain->m_nGameMasterRHitDamage != 0) { damage = g_pMain->m_nGameMasterRHitDamage; return damage; } } #endif damage = temp_hit_B; random = myrand(0, damage); damage = (short)((0.85f * temp_hit_B) + 0.3f * random); } break; case FAIL: if (pSkill != nullptr) { // Skill Hit. } else { // Normal Hit. #ifdef GAMESERVER if (isGM() && !pTarget->isPlayer()) { damage = 30000; return damage; } #endif } } // Apply item bonuses damage = GetMagicDamage(damage, pTarget, bPreviewOnly); // These two only apply to players if (pTarget->isPlayer()) { damage = GetACDamage(damage, pTarget); // 3. Additional AC calculation.... // Give increased damage in war zones (as per official 1.298~1.325) // This may need to be revised to use the last nation to win the war, or more accurately, // the nation that last won the war 3 times in a row (whether official behaves this way now is unknown). if (GetMap()->isWarZone()) damage /= 2; else damage /= 2; } // Enforce damage cap if (damage > MAX_DAMAGE) damage = MAX_DAMAGE; if(pTarget->GetID() > NPC_BAND) { switch(TO_NPC(pTarget)->GetType()) { case NPC_FOSSIL: damage = 0; break; case NPC_TREE: damage = 0; break; } } return damage; } #if GAMESERVER void CUser::OnAttack(Unit * pTarget, AttackType attackType) { if (!pTarget->isPlayer() || attackType == AttackTypeMagic) return; // Trigger weapon procs for the attacker on attack static const uint8 itemSlots[] = { RIGHTHAND, LEFTHAND }; foreach_array (i, itemSlots) { // If we hit an applicable weapon, don't try proc'ing the other weapon. // It is only ever meant to proc on the dominant weapon. if (TriggerProcItem(itemSlots[i], pTarget, TriggerTypeAttack)) break; } } void CUser::OnDefend(Unit * pAttacker, AttackType attackType) { if (!pAttacker->isPlayer()) return; // Trigger defensive procs for the defender when being attacked static const uint8 itemSlots[] = { LEFTHAND }; foreach_array (i, itemSlots) TriggerProcItem(itemSlots[i], pAttacker, TriggerTypeDefend); } /** * @brief Trigger item procs. * * @param bSlot Slot of item to attempt to proc. * @param pTarget Target of the skill (attacker/defender depending on the proc type). * @param triggerType Which type of item to proc (offensive/defensive). * * @return true if there's an applicable item to proc, false if not. */ bool CUser::TriggerProcItem(uint8 bSlot, Unit * pTarget, ItemTriggerType triggerType) { // Don't proc weapon skills if our weapon is disabled. if (triggerType == TriggerTypeAttack && isWeaponsDisabled()) return false; // Ensure there's an item in this slot, _ITEM_DATA * pItem = GetItem(bSlot); if (pItem == nullptr // and that it doesn't need to be repaired. || pItem->sDuration == 0) return false; // not an applicable item // Ensure that this item has an attached proc skill in the table _ITEM_OP * pData = g_pMain->m_ItemOpArray.GetData(pItem->nNum); if (pData == nullptr // no skill to proc || pData->bTriggerType != triggerType) // don't use an offensive proc when we're defending (or vice versa) return false; // not an applicable item // At this point, we have determined there is an applicable item in the slot. // Should it proc now? (note: CheckPercent() checks out of 1000) if (!CheckPercent(pData->bTriggerRate * 10)) return true; // it is an applicable item, it just didn't proc. No need to proc subsequent items. MagicInstance instance; instance.bIsItemProc = true; instance.sCasterID = GetID(); instance.sTargetID = pTarget->GetID(); instance.nSkillID = pData->nSkillID; // For AOE skills such as "Splash", the AOE should be focus on the target. instance.sData[0] = (uint16) pTarget->GetX(); instance.sData[2] = (uint16) pTarget->GetZ(); instance.Run(); return true; // it is an applicable item, and it proc'd. No need to proc subsequent items. } #endif short CNpc::GetDamage(Unit *pTarget, _MAGIC_TABLE *pSkill /*= nullptr*/, bool bPreviewOnly /*= false*/) { if (pTarget->isPlayer()) return GetDamage(TO_USER(pTarget), pSkill); return GetDamage(TO_NPC(pTarget), pSkill); } /** * @brief Calculates damage for monsters/NPCs attacking players. * * @param pTarget Target player. * @param pSkill The skill (not used here). * @param bPreviewOnly true to preview the damage only and not apply any item bonuses (not used here). * * @return The damage. */ short CNpc::GetDamage(CUser *pTarget, _MAGIC_TABLE *pSkill /*= nullptr*/, bool bPreviewOnly /*= false*/) { if (pTarget == nullptr) return 0; int32 damage = 0, HitB; int32 Ac = pTarget->m_sTotalAc; // A unit's total AC shouldn't ever go below 0. if ((pTarget->m_sACAmount) <= 0) pTarget->m_sACAmount = 0; else Ac += pTarget->m_sACAmount; Ac = TO_USER(pTarget)->m_sItemAc + pTarget->GetLevel() + (Ac - pTarget->GetLevel() - TO_USER(pTarget)->m_sItemAc); HitB = (int)((m_sTotalHit * m_bAttackAmount * 200 / 100) / (Ac + 240)); if (HitB <= 0) return 0; uint8 result = GetHitRate(m_fTotalHitrate / pTarget->m_fTotalEvasionrate); switch (result) { case GREAT_SUCCESS: damage = (int)(0.3f * myrand(0, HitB)); damage += (short)(0.85f * (float)HitB); damage = (damage * 3) / 2; break; case SUCCESS: case NORMAL: damage = (int)(0.3f * myrand(0, HitB)); damage += (short)(0.85f * (float)HitB); break; } int nMaxDamage = (int)(2.6 * m_sTotalHit); if (damage > nMaxDamage) damage = nMaxDamage; // Enforce overall damage cap if (damage > MAX_DAMAGE) damage = MAX_DAMAGE; return (short) damage; } /** * @brief Calculates damage for monsters/NPCs attacking other monsters/NPCs. * * @param pTarget Target NPC/monster. * @param pSkill The skill (not used here). * @param bPreviewOnly true to preview the damage only and not apply any item bonuses (not used here). * * @return The damage. */ short CNpc::GetDamage(CNpc *pTarget, _MAGIC_TABLE *pSkill /*= nullptr*/, bool bPreviewOnly /*= false*/) { if (pTarget == nullptr) return 0; short damage = 0, Hit = m_sTotalHit, Ac = pTarget->m_sTotalAc; uint8 result = GetHitRate(m_fTotalHitrate / pTarget->m_fTotalEvasionrate); switch (result) { case GREAT_SUCCESS: damage = (short)(0.6 * Hit); if (damage <= 0) { damage = 0; break; } damage = myrand(0, damage); damage += (short)(0.7 * Hit); break; case SUCCESS: case NORMAL: if (Hit - Ac > 0) { damage = (short)(0.6 * (Hit - Ac)); if (damage <= 0) { damage = 0; break; } damage = myrand(0, damage); damage += (short)(0.7 * (Hit - Ac)); } break; } // Enforce damage cap if (damage > MAX_DAMAGE) damage = MAX_DAMAGE; return damage; } short Unit::GetMagicDamage(int damage, Unit *pTarget, bool bPreviewOnly /*= false*/) { if (pTarget->isDead() || pTarget-> isBlinking()) return 0; Guard lock(_unitlock); int16 sReflectDamage = 0; bool sKontrol = false; // Check each item that has a bonus effect. int aa = m_equippedItemBonuses.size(); foreach (itr, m_equippedItemBonuses) { // Each item can support multiple bonuses. // Thus, we must handle each bonus. int ss = itr->second.size(); foreach (bonusItr, itr->second) { short total_r = 0, temp_damage = 0; uint8 bType = bonusItr->first; int16 sAmount = bonusItr->second; bool bIsDrain = (bType >= ITEM_TYPE_HP_DRAIN && bType <= ITEM_TYPE_MP_DRAIN); if (bIsDrain) temp_damage = damage * sAmount / 100; switch (bType) { case ITEM_TYPE_FIRE: total_r = (pTarget->m_sFireR + pTarget->m_bAddFireR) * pTarget->m_bPctFireR / 100; break; case ITEM_TYPE_COLD: total_r = (pTarget->m_sColdR + pTarget->m_bAddColdR) * pTarget->m_bPctColdR / 100; break; case ITEM_TYPE_LIGHTNING: total_r = (pTarget->m_sLightningR + pTarget->m_bAddLightningR) * pTarget->m_bPctLightningR / 100; break; case ITEM_TYPE_HP_DRAIN: pTarget->HpChange(temp_damage); sKontrol = true; break; case ITEM_TYPE_MP_DAMAGE: pTarget->MSpChange(-temp_damage); sKontrol = true; break; case ITEM_TYPE_MP_DRAIN: MSpChange(temp_damage); sKontrol = true; break; case ITEM_TYPE_MIRROR_DAMAGE: sReflectDamage += sAmount; break; } total_r += pTarget->m_bResistanceBonus; if (!bIsDrain) { if (total_r > 200) total_r = 200; temp_damage = sAmount - sAmount * total_r / 200; damage += temp_damage; } else if(bType == ITEM_TYPE_HP_DRAIN) { HpChange(temp_damage); temp_damage = sAmount - sAmount * total_r / 200; return damage += temp_damage; } } } // If any items have have damage reflection enabled, we should reflect this back to the client. // NOTE: This should only apply to shields, so it should only ever apply once. // We do this here to ensure it's taking into account the total calculated damage. if (sReflectDamage > 0 && !sKontrol) { short temp_damage = damage * sReflectDamage / 100; HpChange(-temp_damage); } if(pTarget->GetID() > NPC_BAND) { switch(TO_NPC(pTarget)->GetType()) { case NPC_FOSSIL: damage = 0; break; case NPC_TREE: damage = 0; break; } } return damage; } short Unit::GetACDamage(int damage, Unit *pTarget) { // This isn't applicable to NPCs. if (!isPlayer() || !pTarget->isPlayer()) return damage; #ifdef GAMESERVER if (pTarget->isDead()) return 0; CUser * pUser = TO_USER(this); if (pUser->isWeaponsDisabled()) return damage; uint8 weaponSlots[] = { LEFTHAND, RIGHTHAND }; int firstdamage = damage; foreach_array (slot, weaponSlots) { _ITEM_TABLE * pWeapon = pUser->GetItemPrototype(weaponSlots[slot]); if (pWeapon == nullptr) continue; if (pWeapon->isDagger()) damage -= damage * pTarget->m_sDaggerR / 200; else if (pWeapon->isSword()) damage -= damage * pTarget->m_sSwordR / 200; else if (pWeapon->isAxe()) damage -= damage * pTarget->m_sAxeR / 200; else if (pWeapon->isMace()) damage -= damage * pTarget->m_sMaceR / 200; else if (pWeapon->isSpear()) damage -= damage * pTarget->m_sSpearR / 200; else if (pWeapon->isBow()) damage -= damage * pTarget->m_sBowR / 200; else if (pWeapon->isStaff()) damage -= damage * pTarget->m_sSpearR / 200; } #endif return damage; } uint8 Unit::GetHitRate(float rate) { int random = myrand(1, 10000); if (rate >= 5.0f) { if (random >= 1 && random <= 3500) return GREAT_SUCCESS; else if (random >= 3501 && random <= 7500) return SUCCESS; else if (random >= 7501 && random <= 9800) return NORMAL; } else if (rate < 5.0f && rate >= 3.0f) { if (random >= 1 && random <= 2500) return GREAT_SUCCESS; else if (random >= 2501 && random <= 6000) return SUCCESS; else if (random >= 6001 && random <= 9600) return NORMAL; } else if (rate < 3.0f && rate >= 2.0f) { if (random >= 1 && random <= 2000) return GREAT_SUCCESS; else if (random >= 2001 && random <= 5000) return SUCCESS; else if (random >= 5001 && random <= 9400) return NORMAL; } else if (rate < 2.0f && rate >= 1.25f) { if (random >= 1 && random <= 1500) return GREAT_SUCCESS; else if (random >= 1501 && random <= 4000) return SUCCESS; else if (random >= 4001 && random <= 9200) return NORMAL; } else if (rate < 1.25f && rate >= 0.8f) { if (random >= 1 && random <= 1000) return GREAT_SUCCESS; else if (random >= 1001 && random <= 3000) return SUCCESS; else if (random >= 3001 && random <= 9000) return NORMAL; } else if (rate < 0.8f && rate >= 0.5f) { if (random >= 1 && random <= 800) return GREAT_SUCCESS; else if (random >= 801 && random <= 2500) return SUCCESS; else if (random >= 2501 && random <= 8000) return NORMAL; } else if (rate < 0.5f && rate >= 0.33f) { if (random >= 1 && random <= 600) return GREAT_SUCCESS; else if (random >= 601 && random <= 2000) return SUCCESS; else if (random >= 2001 && random <= 7000) return NORMAL; } else if (rate < 0.33f && rate >= 0.2f) { if (random >= 1 && random <= 400) return GREAT_SUCCESS; else if (random >= 401 && random <= 1500) return SUCCESS; else if (random >= 1501 && random <= 6000) return NORMAL; } else { if (random >= 1 && random <= 200) return GREAT_SUCCESS; else if (random >= 201 && random <= 1000) return SUCCESS; else if (random >= 1001 && random <= 5000) return NORMAL; } return FAIL; } #ifdef GAMESERVER void Unit::SendToRegion(Packet *result) { g_pMain->Send_Region(result, GetMap(), GetRegionX(), GetRegionZ(), nullptr, GetEventRoom()); } // Handle it here so that we don't need to ref the class everywhere void Unit::Send_AIServer(Packet *result) { g_pMain->Send_AIServer(result); } #endif void Unit::InitType3() { for (int i = 0; i < MAX_TYPE3_REPEAT; i++) m_durationalSkills[i].Reset(); m_bType3Flag = false; } void Unit::InitType4(bool bRemoveSavedMagic /*= false*/, uint8 buffType /* = 0 */) { // Remove all buffs that should not be recast. Guard lock(_unitlock); Type4BuffMap buffMap = m_buffMap; // copy the map for (auto itr = buffMap.begin(); itr != buffMap.end(); itr++) { #ifdef GAMESERVER if (buffType > 0 && itr->second.m_bBuffType != buffType) continue; CMagicProcess::RemoveType4Buff(itr->first, this, bRemoveSavedMagic, buffType > 0 ? true : false); #endif } } /** * @brief Determine if this unit is basically able to attack the specified unit. * This should only be called to handle the minimal shared logic between * NPCs and players. * * You should use the more appropriate CUser or CNpc specialization. * * @param pTarget Target for the attack. * * @return true if we can attack, false if not. */ bool Unit::CanAttack(Unit * pTarget) { if (pTarget == nullptr) return false; // Units cannot attack units in different zones. if (GetZoneID() != pTarget->GetZoneID()) return false; // We cannot attack our target if we are incapacitated // (should include debuffs & being blinded) if (isIncapacitated() // or if our target is in a state in which // they should not be allowed to be attacked || pTarget->isDead() || pTarget->isBlinking()) return false; // Finally, we can only attack the target if we are hostile towards them. return isHostileTo(pTarget); } /** * @brief Determine if this unit is basically able to attack the specified unit. * This should only be called to handle the minimal shared logic between * NPCs and players. * * You should use the more appropriate CUser or CNpc specialization. * * @param pTarget Target for the attack. * * @return true if we attackable, false if not. */ bool Unit::isAttackable(Unit * pTarget) { if (pTarget == nullptr) pTarget = this; if (pTarget) { if (pTarget->isNPC()) { CNpc * pNpc = TO_NPC(pTarget); if (pNpc != nullptr) { #if defined(GAMESERVER) if (pNpc->GetType() == NPC_BIFROST_MONUMENT) return (g_pMain->m_bAttackBifrostMonument); else if (pNpc->GetType() == NPC_PVP_MONUMENT) { if(pNpc->GetSpid() == MONUMENT_ENEMY_SPID) return true; if ((GetNation() == KARUS && pNpc->GetSpid() == MONUMENT_KARUS_SPID) || (GetNation() == ELMORAD && pNpc->GetSpid() == MONUMENT_ELMORAD_SPID)) return false; else return true; } else if (pNpc->GetType() == NPC_GUARD_TOWER1 || pNpc->GetType() == NPC_GUARD_TOWER2 || pNpc->GetType() == NPC_GATE2 || pNpc->GetType() == NPC_VICTORY_GATE || pNpc->GetType() == NPC_PHOENIX_GATE || pNpc->GetType() == NPC_SPECIAL_GATE || pNpc->GetType() == NPC_GATE_LEVER || pNpc->GetType() == NPC_BORDER_MONUMENT || pNpc->GetType() == NPC_BYGROUP3) return false; else if (pNpc->m_sSid == 8850 && !GetMap()->canAttackOtherNation()) return false; #endif } } } return true; } bool Unit::CanCastRHit(uint16 m_socketID) { #if defined(GAMESERVER) CUser *pUser = g_pMain->GetUserPtr(m_socketID); if (pUser == nullptr) return true; if (pUser->m_RHitRepeatList.find(m_socketID) != pUser->m_RHitRepeatList.end()) { RHitRepeatList::iterator itr = pUser->m_RHitRepeatList.find(m_socketID); if (float(UNIXTIME - itr->second) < PLAYER_R_HIT_REQUEST_INTERVAL) return false; else { pUser->m_RHitRepeatList.erase(m_socketID); return true; } } #endif return true; } void Unit::OnDeath(Unit *pKiller) { #ifdef GAMESERVER SendDeathAnimation(pKiller); #endif } void Unit::SendDeathAnimation(Unit * pKiller /*= nullptr*/) { #ifdef GAMESERVER Packet result(WIZ_DEAD); result << GetID(); SendToRegion(&result); #else Packet result(AG_DEAD); int16 tid = (pKiller == nullptr ? -1 : pKiller->GetID()); result << GetID() << tid; g_pMain->Send(&result); #endif } void Unit::AddType4Buff(uint8 bBuffType, _BUFF_TYPE4_INFO & pBuffInfo) { Guard lock(_unitlock); m_buffMap.insert(std::make_pair(bBuffType, pBuffInfo)); if (pBuffInfo.isBuff()) m_buffCount++; } /************************************************************************** * The following methods should not be here, but it's necessary to avoid * code duplication between AI and GameServer until they're better merged. **************************************************************************/ /** * @brief Sets zone attributes for the loaded zone. * * @param zoneNumber The zone number. */ void KOMap::SetZoneAttributes(int zoneNumber) { m_zoneFlags = 0; #if defined(GAMESERVER) m_byTariff = g_pMain->GetTariffByZone(zoneNumber); // defaults to 10 officially for zones that don't use it. #else m_byTariff = 0; // defaults to 10 officially for zones that don't use it. #endif m_byMinLevel = 1; m_byMaxLevel = 83; switch (zoneNumber) { case ZONE_KARUS: case ZONE_ELMORAD: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_CLAN_UPDATE; m_byMinLevel = MIN_LEVEL_NATION_BASE, m_byMaxLevel = MAX_LEVEL; break; case ZONE_KARUS_ESLANT: case ZONE_ELMORAD_ESLANT: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_ESLANT, m_byMaxLevel = MAX_LEVEL; break; case ZONE_MORADONM2: case ZONE_MORADON: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS | ZF_CLAN_UPDATE; break; case ZONE_DELOS: m_zoneType = ZoneAbilitySiegeDisabled; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = MIN_LEVEL_NATION_BASE, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BIFROST: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = MIN_LEVEL_BIFROST, m_byMaxLevel = MAX_LEVEL; break; case ZONE_DESPERATION_ABYSS: case ZONE_HELL_ABYSS: case ZONE_DRAGON_CAVE: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = MIN_LEVEL_NATION_BASE, m_byMaxLevel = MAX_LEVEL; break; case ZONE_ARENA: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION | ZF_ATTACK_SAME_NATION | ZF_FRIENDLY_NPCS; break; case ZONE_ORC_ARENA: case ZONE_GOBLIN_ARENA: case ZONE_BLOOD_DON_ARENA: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; break; case ZONE_CAITHAROS_ARENA: m_zoneType = ZoneAbilityCaitharosArena; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION | ZF_FRIENDLY_NPCS; break; case ZONE_FORGOTTEN_TEMPLE: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMaxLevel = MAX_LEVEL; break; case ZONE_LOST_TEMPLE: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = MIN_LEVEL_RONARK_LAND, m_byMaxLevel = MAX_LEVEL; break; case ZONE_DARK_LAND: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_RONARK_LAND, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BATTLE3: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; m_byMinLevel = MIN_LEVEL_FOR_BATTLES, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BATTLE: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; m_byMinLevel = MIN_LEVEL_FOR_BATTLES, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BATTLE2: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; m_byMinLevel = MIN_LEVEL_FOR_BATTLES, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BATTLE4: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; m_byMinLevel = MIN_LEVEL_FOR_BATTLES, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BATTLE5: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; m_byMinLevel = MIN_LEVEL_FOR_BATTLES, m_byMaxLevel = MAX_LEVEL; break; case ZONE_BATTLE6: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; m_byMinLevel = MIN_LEVEL_FOR_BATTLES, m_byMaxLevel = MAX_LEVEL; break; case ZONE_SNOW_BATTLE: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_WAR_ZONE; break; case ZONE_KROWAZ_DOMINION: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_RONARK_LAND, m_byMaxLevel = MAX_LEVEL; break; case ZONE_PVP_EVENT: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_ARDREAM, m_byMaxLevel = MAX_LEVEL_ARDREAM; break; case ZONE_CLAN_EVENT: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION; break; case ZONE_RONARK_LAND: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_RONARK_LAND, m_byMaxLevel = MAX_LEVEL; break; case ZONE_RONARK_LAND_BASE: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_RONARK_LAND_BASE, m_byMaxLevel = MAX_LEVEL_RONARK_LAND_BASE; break; case ZONE_ARDREAM: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; m_byMinLevel = MIN_LEVEL_ARDREAM, m_byMaxLevel = MAX_LEVEL_ARDREAM; break; case ZONE_BORDER_DEFENSE_WAR: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION; break; case ZONE_CHAOS_DUNGEON: m_zoneType = ZoneAbilityPVP; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_ATTACK_SAME_NATION; break; case ZONE_JURAD_MOUNTAIN: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_ATTACK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = MIN_LEVEL_JURAD_MOUNTAIN, m_byMaxLevel = MAX_LEVEL; break; case ZONE_PRISON: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_FRIENDLY_NPCS; break; case ZONE_ISILOON_ARENA: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION | ZF_FRIENDLY_NPCS; break; case ZONE_FELANKOR_ARENA: m_zoneType = ZoneAbilityPVPNeutralNPCs; m_zoneFlags = ZF_TALK_OTHER_NATION | ZF_ATTACK_OTHER_NATION | ZF_FRIENDLY_NPCS; break; case ZONE_WINNER_CASTLE2: case ZONE_WINNER_CASTLE: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; break; case ZONE_STONE1: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = 1, m_byMaxLevel = 39; break; case ZONE_STONE2: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = 39, m_byMaxLevel = 60; break; case ZONE_STONE3: m_zoneType = ZoneAbilityNeutral; m_zoneFlags = ZF_TRADE_OTHER_NATION | ZF_TALK_OTHER_NATION | ZF_FRIENDLY_NPCS; m_byMinLevel = 59, m_byMaxLevel = MAX_LEVEL; break; default: m_zoneType = ZoneAbilityPVP; break; } } /** * @brief Determines if an NPC is hostile to a unit. * Non-hostile units cannot be attacked. * * @param pTarget Target unit * * @return true if hostile to, false if not. */ bool CNpc::isHostileTo(Unit * pTarget) { if (pTarget == nullptr) return false; if (m_oSocketID == pTarget->GetID()) return true; if (GetType() == NPC_GATE && (GetZoneID() == ZONE_BATTLE || GetZoneID() == ZONE_BATTLE2 || GetZoneID() == ZONE_BATTLE3 || GetZoneID() == ZONE_BATTLE4 || GetZoneID() == ZONE_BATTLE5 || GetZoneID() == ZONE_BATTLE6) && GetNation() != pTarget->GetNation()) return true; // Only players can attack these targets. if (pTarget->isPlayer()) { // Scarecrows are NPCs that the client allows us to attack // however, since they're not a monster, and all NPCs in neutral zones // are friendly, we need to override to ensure we can attack them server-side. #if defined(GAMESERVER) if(GetType() == NPC_FOSSIL) { _ITEM_DATA * pItem; _ITEM_TABLE * pTable = TO_USER(pTarget)->GetItemPrototype(RIGHTHAND, pItem); if (pItem == nullptr || pTable == nullptr || pItem->sDuration <= 0 // are we supposed to wear the pickaxe on use? Need to verify. || !pTable->isPickaxe()) return false; else return true; } if (GetType() == NPC_SCARECROW || GetType() == NPC_BIFROST_MONUMENT && g_pMain->m_bAttackBifrostMonument) return true; #else if (GetType() == NPC_SCARECROW || GetType() == NPC_BIFROST_MONUMENT) return true; #endif } #if defined(GAMESERVER) if (g_pMain->m_byBattleSiegeWarOpen && !TO_USER(pTarget)->isInClan() && GetZoneID() == ZONE_DELOS) return false; CKnights * pKnights ; _KNIGHTS_SIEGE_WARFARE * pSiegeWars ; if (g_pMain->m_byBattleSiegeWarOpen && GetZoneID() == ZONE_DELOS && TO_USER(pTarget)->GetClanID() != 0) { pKnights = g_pMain->GetClanPtr(TO_USER(pTarget)->GetClanID()); pSiegeWars = g_pMain->GetSiegeMasterKnightsPtr(1); } if (g_pMain->m_byBattleSiegeWarOpen && GetZoneID() == ZONE_DELOS && pKnights->GetID() != pSiegeWars->sMasterKnights && m_sSid == 541 && GetType() == NPC_DESTROYED_ARTIFACT) return true;// CSW Açık Kale sahibi clanda değil atack yapabilir else if(!g_pMain->m_byBattleSiegeWarOpen && GetZoneID() == ZONE_DELOS) return false;// CSW kapalı delosta atack yok else if(g_pMain->m_byBattleSiegeWarOpen && GetZoneID() == ZONE_DELOS && (pKnights->GetID() == pSiegeWars->sMasterKnights || GetNation() == Nation::ALL)) return false;// CSW açık ve kale sahibi clanda atack yok if (GetNation() == Nation::ALL || (!isMonster() && GetMap()->areNPCsFriendly() && GetNation() != Nation::NONE)) return false; #else // A nation of 0 indicates friendliness to all if (GetNation() == Nation::ALL // Also allow for cases when all NPCs in this zone are inherently friendly. || (!isMonster() && GetMap()->areNPCsFriendly())) return false; #endif // A nation of 3 indicates hostility to all (or friendliness to none) if (GetNation() == Nation::NONE) return true; // An NPC cannot attack a unit of the same nation return (GetNation() != pTarget->GetNation()); } /** * @brief Determines if a player is hostile to a unit. * Non-hostile units cannot be attacked. * * @param pTarget Target unit * * @return true if hostile to, false if not. */ bool CUser::isHostileTo(Unit * pTarget) { if (pTarget == nullptr) return false; // For non-player hostility checks, refer to the appropriate override. if (!pTarget->isPlayer()) return pTarget->isHostileTo(this); // Players can attack other players in the arena. if ((isInArena() && TO_USER(pTarget)->isInArena()) || (isInPartyArena() && TO_USER(pTarget)->isInPartyArena() && (GetPartyID() != TO_USER(pTarget)->GetPartyID() || GetPartyID() == uint16(-1) || TO_USER(pTarget)->GetPartyID() == uint16(-1)))) return true; // Players can attack other players in the safety area. if (TO_USER(pTarget)->isInSafetyArea() || TO_USER(pTarget)->hasBuff(BUFF_TYPE_FREEZE) || TO_USER(pTarget)->isBlinking()) return false; if(TO_USER(pTarget)->GetEventRoom() != GetEventRoom()) return false; // Players can attack opposing nation players when they're in PVP zones. if (GetNation() != pTarget->GetNation() && isInPVPZone()) return true; if (GetNation() != pTarget->GetNation() && JuraidTempleEventZone()) return true; if (GetNation() != pTarget->GetNation() && BorderTempleEventZone()) return true; if (ChaosTempleEventZone()) return true; #if GAMESERVER if (g_pMain->m_byBattleSiegeWarOpen && GetZoneID() == ZONE_DELOS) { CUser *pUser = g_pMain->GetUserPtr(GetName(), TYPE_CHARACTER); CUser *pTargetUser = g_pMain->GetUserPtr(TO_USER(pTarget)->m_strUserID, TYPE_CHARACTER); if(pUser == nullptr || pTargetUser == nullptr) return false; if (pUser->GetClanID() > 0 && pTargetUser->GetClanID() > 0) return g_pMain->CastleSiegeWarAttack(pUser, pTargetUser); else return false; } #endif // Players cannot attack other players in any other circumstance. return false; } /** * @brief Determine if this user is in an arena area. * * @return true if in arena, false if not. */ bool CUser::isInArena() { /* All of this needs to be handled more generically (i.e. bounds loaded from the database, or their existing SMD method). */ // If we're in the Arena zone, assume combat is acceptable everywhere. // NOTE: This is why we need generic bounds checks, to ensure even attacks in the Arena zone are in one of the 4 arena locations. if (GetZoneID() == ZONE_ARENA) return true; // The only other arena is located in Moradon. If we're not in Moradon, then it's not an Arena. if (GetZoneID() != ZONE_MORADON && GetZoneID() != ZONE_MORADONM2) return false; // Moradon outside arena spawn bounds. return ((GetX() < 735.0f && GetX() > 684.0f) && (GetZ() < 491.0f && GetZ() > 440.0f)/* || (GetZ() < 411.0f && GetZ() > 360.0f))*/); } bool CUser::isInPartyArena() { /* All of this needs to be handled more generically (i.e. bounds loaded from the database, or their existing SMD method). */ // If we're in the Arena zone, assume combat is acceptable everywhere. // NOTE: This is why we need generic bounds checks, to ensure even attacks in the Arena zone are in one of the 4 arena locations. if (GetZoneID() == ZONE_ARENA) return true; // The only other arena is located in Moradon. If we're not in Moradon, then it's not an Arena. if (GetZoneID() != ZONE_MORADON && GetZoneID() != ZONE_MORADONM2) return false; // Moradon outside arena spawn bounds. return ((GetX() < 735.0f && GetX() > 684.0f) && (GetZ() < 411.0f && GetZ() > 360.0f)); } /** * @brief Determine if this user is in a normal PVP zone. * That is, they're in an PK zone that allows combat * against the opposite nation. * * @return true if in PVP zone, false if not. */ bool CUser::isInPVPZone() { if (GetMap()->canAttackOtherNation()) return true; #if defined(GAMESERVER) // Native/home zones are classed as PVP zones during invasions. if ((GetZoneID() == KARUS && g_pMain->m_byKarusOpenFlag) || (GetZoneID() == ELMORAD && g_pMain->m_byElmoradOpenFlag)) return true; #endif return false; } /** * @brief Determine if this user is in an safety area. * * @return true if in safety area, false if not. */ bool CUser::isInSafetyArea() { switch (GetZoneID()) { case ZONE_BIFROST: if (GetNation() == KARUS) return ((GetX() < 124.0f && GetX() > 56.0f) && ((GetZ() < 840.0f && GetZ() > 700.0f))); else if (GetNation() == ELMORAD) return ((GetX() < 270.0f && GetX() > 190.0f) && ((GetZ() < 970.0f && GetZ() > 870.0f))); case ZONE_RONARK_LAND_BASE: if (GetNation() == KARUS || GetNation() == ELMORAD) return ((GetX() < 589.0f && GetX() > 403.0f) && ((GetZ() < 297.0f && GetZ() > 156.0f))); case ZONE_DARK_LAND: if (GetNation() == KARUS || GetNation() == ELMORAD) return ((GetX() < 110.0f && GetX() > 4.0f) && ((GetZ() < 507.0f && GetZ() > 318.0f)) || (GetX() < 225.0f && GetX() > 4.0f) && ((GetZ() < 507.0f && GetZ() > 388.0f))); case ZONE_ARENA: if (GetNation() == KARUS || GetNation() == ELMORAD) return ((GetX() < 148.0f && GetX() > 106.0f) && ((GetZ() < 149.0f && GetZ() > 50.0f)) || (GetX() < 169.0f && GetX() > 86.0f) && ((GetZ() < 127.0f && GetZ() > 100.0f)) || (GetX() < 150.0f && GetX() > 102.0f) && ((GetZ() < 144.0f && GetZ() > 82.0f)) || (GetX() < 157.0f && GetX() > 99.0f) && ((GetZ() < 139.0f && GetZ() > 88.0f))); case ZONE_ELMORAD: if (GetNation() == KARUS) return ((GetX() < 244.0f && GetX() > 176.0f) && ((GetZ() < 1880.0f && GetZ() > 1820.0f))); case ZONE_BORDER_DEFENSE_WAR: if (GetNation() == ELMORAD) return ((GetX() < 224.0f && GetX() > 179.0f) && ((GetZ() < 222.0f && GetZ() > 169.0f))); else if (GetNation() == KARUS) return ((GetX() < 74.0f && GetX() > 22.0f) && ((GetZ() < 96.0f && GetZ() > 36.0f))); case ZONE_KARUS: if (GetNation() == ELMORAD) return ((GetX() < 1876.0f && GetX() > 1820.0f) && ((GetZ() < 212.0f && GetZ() > 136.0f))); case ZONE_BATTLE: if (GetNation() == ELMORAD) return ((GetX() < 125.0f && GetX() > 98.0f) && ((GetZ() < 780.0f && GetZ() > 755.0f))); else if (GetNation() == KARUS) return ((GetX() < 831.0f && GetX() > 805.0f) && ((GetZ() < 110.0f && GetZ() > 85.0f))); case ZONE_BATTLE2: if (GetNation() == ELMORAD) return ((GetX() < 977.0f && GetX() > 942.0f) && ((GetZ() < 904.0f && GetZ() > 863.0f))); else if (GetNation() == KARUS) return ((GetX() < 80.0f && GetX() > 46.0f) && ((GetZ() < 174.0f && GetZ() > 142.0f))); case ZONE_DELOS: return ((GetX() > 411.0f && GetX() < 597.0f) && ((GetZ() < 296.0f && GetZ() > 113.0f))); } return false; } bool Unit::isSameEventRoom(Unit *pTarget) { if (pTarget == nullptr) return false; if (GetEventRoom() == pTarget->GetEventRoom()) return true; return false; }