knightonline/server/GameServer/Unit.cpp

1543 lines
43 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <cfloat>
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;
}