Files
Sun1602/Server/GameServer/CharacterFormula.cpp
T
2022-10-26 12:25:11 +08:00

2953 lines
104 KiB
C++

#include "StdAfx.h"
#include "CharacterFormula.h"
//==================================================================================================
// Program Common
#include <misc.h>
#include <NumericValues.h>
#include <Battle.h>
#include <PublicMath.h>
#include <FormulaRatioParser.h>
#include <EtherBulletInfoParser.h>
#include <Attribute.h>
//--------------------------------------------------------------------------------------------------
// SolarSlot
#include "ItemOptionHeader.h"
#include "SCItemSlot.h"
#include "SCItemSlotContainer.h" //..
//--------------------------------------------------------------------------------------------------
// Common
#include "ArmorDamageParser.h"
//--------------------------------------------------------------------------------------------------
#include "Player.h"
#include "ItemManager.h"
#include "./Status/StatusManager.h"
// Ability
#include "AbilityStatus.h"
#include "Skill.h"
//#ifdef _NA_006040_20120830_RESIS_BAD_STATUS_RATIO
#include "StateInfoParser.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// REGION : Ability
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace ns_formula {
namespace ability {
namespace impl {
const BASE_ABILITYINFO* GetAbilityInfo(
const Character& skill_user, const eCHAR_STATE_TYPE state_type)
{
const StatusManager* const status_manager = skill_user.GetStatusManager();
if (status_manager == NULL)
{
return NULL;
}
const BaseStatus* const status = status_manager->FindStatus(state_type);
if (status == NULL)
{
return NULL;
}
if (status->IsAbilityStatus() == false)
{
return NULL;
}
const AbilityStatus* const ability_status = static_cast<const AbilityStatus* const>(status);
if (ability_status == NULL)
{
return NULL;
}
return ability_status->GetAbilityInfo();
}
const BASE_ABILITYINFO* GetComboEffectInfo(
const Character& skill_user,
const Skill& skill,
const eABILITY effect_ability_code,
const int state_id)
{
// 콤보 상태가 활성화 되어 있는지 검사한다.
const BASE_ABILITYINFO* const active_combo_info =
GetAbilityInfo(skill_user, kCharStateActiveComboSkill);
if (active_combo_info == NULL)
{
return NULL;
}
// 사용 가능한 연계 스킬인지 검사한다.
const eSKILL skill_class_code = skill.GetSkillClassCode();
int temp_skill_class_code = active_combo_info->m_iOption1;
if (temp_skill_class_code != skill_class_code)
{
temp_skill_class_code = active_combo_info->m_iOption2;
if (temp_skill_class_code != skill_class_code)
{
temp_skill_class_code = active_combo_info->m_iParam[0];
if (temp_skill_class_code != skill_class_code)
{
temp_skill_class_code = active_combo_info->m_iParam[1];
if (temp_skill_class_code != skill_class_code)
{
return NULL;
}
}
}
}
// 발동 효과 어빌리티 정보를 얻는다.
const SkillScriptInfo* const skill_info = skill.GetSkillBaseInfo();
if (skill_info == NULL)
{
return NULL;
}
const BASE_ABILITYINFO* const combo_effect_info =
skill_info->GetAbilityInfo(effect_ability_code);
if (combo_effect_info == NULL)
{
return NULL;
}
// 상태이상 강화이면 강화할 상태인지 검사한다.
if (effect_ability_code == kAbilityComboSkillEffectAddBadStatus && state_id != 0)
{
int temp_state_id = combo_effect_info->m_iOption1;
if (temp_state_id != state_id)
{
temp_state_id = combo_effect_info->m_iOption2;
if (temp_state_id != state_id)
{
temp_state_id = combo_effect_info->m_iParam[0];
if (temp_state_id != state_id)
{
return NULL;
}
}
}
}
return combo_effect_info;
}
} // impl
#ifdef _DEBUG
#define DEBUG_ABILITY_CALC_
#endif // _DEBUG
int CalcIncreseHeal(
const IncreseHealAbilityType::Value heal_type,
const int heal_value,
const Character& skill_user)
{
const BASE_ABILITYINFO* const ability_info =
impl::GetAbilityInfo(skill_user, kCharStateIncreseHeal);
if (ability_info == NULL)
{
return 0;
}
const int temp_heal_type = ability_info->m_iOption1;
if (temp_heal_type != IncreseHealAbilityType::kSkillAndItem)
{
if (temp_heal_type != heal_type)
{
return 0;
}
}
int add_value = 0;
{
// 추가 회복 값
const int add_heal_value = ability_info->m_iParam[0];
if (add_heal_value != 0)
{
add_value += add_heal_value;
}
// 추가 회복 비율
const int add_heal_ratio = ability_info->m_iParam[1];
if (add_heal_ratio != 0)
{
add_value +=
static_cast<int>(static_cast<float>(heal_value) * (static_cast<float>(add_heal_ratio) / 1000.0f));
}
#ifdef DEBUG_ABILITY_CALC_
if (add_value != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|HealValue = %d, HealRatio = %d, AddValue = %d|"),
__FUNCTION__,
add_heal_value,
add_heal_ratio,
add_value
);
}
#endif // DEBUG_ABILITY_CALC_
}
return add_value;
}
int CalcIncreseCurse(
const eSKILL skill_class_code,
const int skill_value,
const Character& skill_user)
{
const BASE_ABILITYINFO* const ability_info =
impl::GetAbilityInfo(skill_user, eCHAR_STATE_CURSE_INCREASE);
if (ability_info == NULL)
{
return 0;
}
int add_ratio = 0;
switch (skill_class_code)
{
case eSKILL_PAIN:
add_ratio = ability_info->m_iOption1;
break;
case eSKILL_DARK_FIRE:
add_ratio = ability_info->m_iOption2;
break;
case eSKILL_ENCHANT_POISON:
add_ratio = ability_info->m_iParam[0];
break;
default:
return 0;
}
if (skill_value == 0 || add_ratio == 0)
{
return 0;
}
const int add_value = static_cast<int>(static_cast<float>(skill_value) * (static_cast<float>(add_ratio) / 1000.0f));
#ifdef DEBUG_ABILITY_CALC_
if (add_value != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|SkillClassCode = %d, AddRatio = %d, AddValue = %d|"),
__FUNCTION__,
skill_class_code,
add_ratio,
add_value
);
}
#endif // DEBUG_ABILITY_CALC_
return add_value;
}
int CalcComboDamage(
const Character& skill_user,
const Skill& skill,
const int skill_value)
{
const BASE_ABILITYINFO* const combo_effect_info =
impl::GetComboEffectInfo(skill_user, skill, kAbilityComboSkillEffectAddDamage, 0);
if (combo_effect_info == NULL)
{
return 0;
}
int calc_damage = 0;
{
const int add_value = combo_effect_info->m_iParam[0];
if (add_value != 0)
{
calc_damage += add_value;
}
const int add_ratio = combo_effect_info->m_iParam[1];
if (add_ratio != 0)
{
calc_damage +=
static_cast<int>(static_cast<float>(skill_value) *
(static_cast<float>(add_ratio) / 1000.0f));
}
#ifdef DEBUG_ABILITY_CALC_
if (calc_damage != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|AddValue = %d, AddRatio = %d, CalcDamage = %d|"),
__FUNCTION__,
add_value,
add_ratio,
calc_damage
);
}
#endif // DEBUG_ABILITY_CALC_
}
return calc_damage;
}
int CalcComboApplyTime(const Character& skill_user, const Skill& skill, const int state_id)
{
const BASE_ABILITYINFO* const combo_effect_info = impl::GetComboEffectInfo(
skill_user, skill, kAbilityComboSkillEffectAddBadStatus, state_id);
if (combo_effect_info == NULL)
{
return 0;
}
const int add_time = combo_effect_info->m_iParam[2];
#ifdef DEBUG_ABILITY_CALC_
if (add_time != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|AddTime = %d|"),
__FUNCTION__,
add_time
);
}
#endif // DEBUG_ABILITY_CALC_
return add_time;
}
// 콤보 사용으로 인한 추가 상태 성공률을 계산한다.
int CalcComboSuccessRatio(const Character& skill_user, const Skill& skill, const int state_id)
{
const BASE_ABILITYINFO* const combo_effect_info = impl::GetComboEffectInfo(
skill_user, skill, kAbilityComboSkillEffectAddBadStatus, state_id);
if (combo_effect_info == NULL)
{
return 0;
}
const int add_ratio = combo_effect_info->m_iParam[1];
#ifdef DEBUG_ABILITY_CALC_
if (add_ratio != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|AddRatio = %d|"),
__FUNCTION__,
add_ratio
);
}
#endif // DEBUG_ABILITY_CALC_
return add_ratio;
}
int CalcIncreseSkillDamage(const Character& skill_user, const Skill& skill, const int skill_value)
{
// 스킬 데미지 증가 상태가 걸려 있는지 검사한다.
const StatusManager* const status_manager = skill_user.GetStatusManager();
if (status_manager == NULL) {
return 0;
}
#ifdef _NA_007294_20140613_DRAGONKNIGHT_RENEWAL
const BaseStatus* const status_inc_skill_dmg = status_manager->FindStatus(kCharStateIncreseSkillDamage);
const BaseStatus* const status_transformation6 = status_manager->FindStatus(eCHAR_STATE_TRANSFORMATION6);
//데미지 증가 상태가 한가지라도 있고 어빌리티라면 진행한다
if ((status_inc_skill_dmg == NULL || status_inc_skill_dmg->IsAbilityStatus() == false)
&& (status_transformation6 == NULL || status_transformation6->IsAbilityStatus() == false))
{
return 0;
}
#else //_NA_007294_20140613_DRAGONKNIGHT_RENEWAL
const BaseStatus* const active_status = status_manager->FindStatus(kCharStateIncreseSkillDamage);
if (active_status == NULL || active_status->IsAbilityStatus() == false) {
return 0;
}
#endif //_NA_007294_20140613_DRAGONKNIGHT_RENEWAL
// 스킬 데미지 증가 정보를 얻는다.
const SkillScriptInfo* effect_skill_script_info = skill.GetSkillBaseInfo();
if (effect_skill_script_info == NULL) {
return 0;
}
const BASE_ABILITYINFO* const increse_damage_info =
effect_skill_script_info->GetAbilityInfo(kAbilityIncreseSkillDamage);
if (increse_damage_info == NULL) {
return 0;
}
// 증가값을 계산한다.
int add_damage = 0;
{
const int add_value = increse_damage_info->m_iParam[0];
if (add_value != 0)
{
add_damage += add_value;
}
const int add_ratio = increse_damage_info->m_iParam[1];
if (add_ratio != 0)
{
add_damage +=
static_cast<int>(static_cast<float>(skill_value) *
(static_cast<float>(add_ratio) / 1000.0f));
}
#ifdef DEBUG_ABILITY_CALC_
if (add_damage != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|AddValue = %d, AddRatio = %d, AddDamage = %d, SkillLevel = %d|"),
__FUNCTION__,
add_value,
add_ratio,
add_damage,
effect_skill_script_info->m_wSkillLv
);
}
#endif // DEBUG_ABILITY_CALC_
}
return add_damage;
}
int CalcFightingEnergy(Character& skill_user, const Skill& skill, const int skill_value)
{
const SkillScriptInfo* const skill_info = skill.GetSkillBaseInfo();
if (skill_info == NULL)
{
return 0;
}
const int used_size = skill_user.GetUsedFightingEnergySize();
if (used_size < 1)
{
return 0;
}
skill_user.SetUsedFightingEnergySize(0);
const BASE_ABILITYINFO* const ability_info =
skill_info->GetAbilityInfo(eABILITY_BONUS_DAMAGE_PER_FIGHTING_ENERGY);
if (ability_info == NULL)
{
return 0;
}
int add_damage = 0;
{
const short decrese_count = static_cast<short>(ability_info->m_iOption2);
const bool is_use_all = (decrese_count == 0);
if (is_use_all)
{
if (used_size >= 1 && used_size <= MAX_FIGHTING_ENERGY_NUM)
{
// 개수별 파라미터에 입력한 값만큼 증가 시킨다.
const int add_value = ability_info->m_iParam[used_size-1];
if (add_value != 0)
{
add_damage += add_value;
}
#ifdef DEBUG_ABILITY_CALC_
if (add_damage != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|AddDamage = %d, UsedSize = %d|"),
__FUNCTION__,
add_damage,
used_size
);
}
#endif // DEBUG_ABILITY_CALC_
}
}
else
{
// 값 증가
const int add_value = ability_info->m_iParam[0];
if (add_value != 0)
{
add_damage += add_value;
}
// 비율 증가
const int add_ratio = ability_info->m_iParam[1];
if (add_ratio != 0)
{
const float temp_add_ratio = add_ratio / 1000.0f;
add_damage += static_cast<int>(skill_value * temp_add_ratio);
}
#ifdef DEBUG_ABILITY_CALC_
if (add_damage != 0)
{
SUNLOG(
eCRITICAL_LOG,
_T("|%s|AddValue = %d, AddRatio = %d, AddDamage = %d, UsedSize = %d|"),
__FUNCTION__,
add_value,
add_ratio,
add_damage,
used_size
);
}
#endif // DEBUG_ABILITY_CALC_
}
}
return add_damage;
}
} // ability
} // ns_formula
////////////////////////////////////////////////////////////////////////////////////////////////////
// REGION : EP1
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace ns_formula { namespace ep1 {
;
//--------------------------------------------------------------------------------------------------
// internal interfaces
//
static bool IsPhysicalHit(Character* attacker, Character* target);
static int _CalcAttackPower(BOOL bBase, eATTACK_TYPE eAttackType, BOOL bMin, Character* attacker);
static int CalcFirstDamage(eATTACK_TYPE eAttackType, int iBaseAttackPower, int iSkillAttackPower,
short sSkillStat, float fSkillPercentDamage);
static int CalcSecondDamage(eATTACK_TYPE eAttackType, int iFirstMinDamage, int iFirstMaxDamage,
int iBonusDamage, LEVELTYPE attackerLevel, LEVELTYPE targetLevel,
int iTargetDefense,
float fArmorDamageRatio, float fDamageDecreaseRatio);
static int CalcBaseDamage(Character* attacker, Character* target,
eATTACK_TYPE eAttackType,
int iSkillAttackPower, float fSkillPercentDamage,
BYTE bySkillStatType, float fDefenseIgnore);
static int CalcMagicalDamage(Character* attacker, Character* target,
eATTACK_TYPE eAttackType, int iSkillAttackPower,
float fSkillPercentDamage, BYTE bySkillStatType);
static BOOL IsCriticalHit(eOBJECT_TYPE eObjectType, int criticalRatio, LEVELTYPE level);
static int _CalcCriticalDamage(eOBJECT_TYPE eObjectType,
LEVELTYPE targetLevel,
int iSecondDamage,
int iCriticalRatio,
int iCriticalDamageBonus,
float fCriticalDamagePercentBonus,
BOOL needMaxDamage = false);
static DAMAGETYPE _CalcDamage(BOOL bNormalAttack,
Character* attacker, Character* target,
eATTACK_TYPE eBaseAttackType,
eATTACK_TYPE eMagicalAttackType,
int iSkillAttackPower,
float fSkillPercentDamage,
WORD criticalRatioBonus,
BYTE* pEffect,
BYTE bySkillStatType,
float fDefenseIgnore,
BOOL bCritical = true);
// 방어력
static int CalcDefensePower(BOOL bBase, eATTACK_TYPE eAttackType,
Character* attacker, Character* target,
float fDefenseIgnore);
inline bool IsBypassDeffence(Character* attacker);
// Exp 배율수
inline float CalcExpRatioNum(DWORD partymemberNum);
}}; //end of namespace 'ns_formula::ep1'
namespace ns_formula { namespace ep1 {
#if SUN_CODE_BACKUP // NOTE: f101221.4L, remove unused section
DWORD ns_formula::FastRecoveryTime(DWORD delayTime, short dexterity,
DWORD itemFastRecovery, DWORD skillFastRecovery)
{
return DWORD(delayTime * (100 / (100 + dexterity / 30 + itemFastRecovery + skillFastRecovery)));
}
#endif //
inline bool ns_formula::ep1::IsBypassDeffence(Character* attacker)
{
int byPassRatio = attacker->GetAttr().GetBypassDeffenceRatio();
return (random(0, 100) < byPassRatio) ? true : false;
}
int ns_formula::ep1::CalcDoubleDamage(Character* attacker, int baseDamage)
{
int iDoubleDamageRatio = attacker->GetAttr().GetDoubleDamageRatio();
return (random(0, 100) < iDoubleDamageRatio) ? baseDamage : 0;
}
// 플레이어 HP, MP 회복량
// vitality 은 체력이다.
// 이 함수는 회복되는 단위 수치를 구하는 함수이다.
// 즉 회복이 일어날 때마다 일어나는 게 아니라, 특정 이벤트에 의해서 Recover값이 변경될 때
// 이 함수를 호출해야 한다.
int ns_formula::ep1::CalcHPRecover(eCHAR_TYPE char_type, DWORD vitality,
BYTE byCharState, LEVELTYPE level)
{
float recoverValue = 0;
int recoverHP = 0;
float fSitDownValue = 0.0f;
#ifdef _NA001605_EP2_ATTRIBUTES_RENEWAL_
switch (char_type)
{
case eCHAR_DRAGON :
recoverValue = 8;
fSitDownValue = 3;
break;
case eCHAR_BERSERKER :
recoverValue = 10;
fSitDownValue = 2;
break;
case eCHAR_SHADOW :
recoverValue = 8;
fSitDownValue = 1; // 섀도우는 임시로...
break;
case eCHAR_VALKYRIE :
recoverValue = 7;
fSitDownValue = 1.5;
break;
case eCHAR_ELEMENTALIST :
recoverValue = 6;
fSitDownValue = 3;
break;
case eCHAR_MYSTIC: // _NA_004965_20120613_NEW_CHARACTER_MYSTIC
recoverValue = 8;
fSitDownValue = 1;
break;
case eCHAR_HELLROID: //_NA_000000_20130812_NEW_CHARACTER_HELLROID
recoverValue = 11;
fSitDownValue = 2;
break;
case eCHAR_WITCHBLADE: //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
recoverValue = 8;
fSitDownValue = 3;
break;
default:
{
SASSERT(false, "Wrong Char_Type in CalcHPRecover()");
return 0;
}
}
#else
switch (char_type)
{
case eCHAR_DRAGON :
recoverValue = 4;
fSitDownValue = 3;
break;
case eCHAR_BERSERKER :
recoverValue = 5;
fSitDownValue = 2;
break;
case eCHAR_SHADOW :
recoverValue = 4;
fSitDownValue = 1; // 섀도우는 임시로...
break;
case eCHAR_VALKYRIE :
recoverValue = 3.5f;
fSitDownValue = 1.5;
break;
case eCHAR_ELEMENTALIST :
recoverValue = 3;
fSitDownValue = 3;
break;
case eCHAR_MYSTIC: // _NA_004965_20120613_NEW_CHARACTER_MYSTIC
recoverValue = 4;
fSitDownValue = 1;
break;
case eCHAR_HELLROID: //_NA_000000_20130812_NEW_CHARACTER_HELLROID
recoverValue = 4;
fSitDownValue = 1;
break;
default:
{
SASSERT(false, "Wrong Char_Type in CalcHPRecover()");
return 0;
}
}
#endif //
if (byCharState == eCHAR_CONDITION_STANDUP)
{ //1. 서있을 때
recoverHP = (int)(((vitality / recoverValue + level / 5.0f) - 2) * 2.0f);
}
else if (byCharState == eCHAR_CONDITION_SITDOWN)
{ //2. 앉아 있을 때
recoverHP = (int)((((vitality / recoverValue) * 2 + (level / 5.0f) * 2) - 2) * fSitDownValue);
}
if (recoverHP < 0) {
recoverHP = 1;
}
return recoverHP;
}
//SD 자동 회복 공식
int ns_formula::ep1::CalcSDRecover(eCHAR_TYPE char_type,
eCHAR_CONDITION byCharState, BYTE byCharAction,
LEVELTYPE level)
{
if (CHAR_ACTION_CONDITION_FIGHTING & byCharAction) {
return 0;
}
DWORD dwMaxSD = CalcSD(char_type, level);
if (CHAR_ACTION_CONDITION_MOVING & byCharAction) {
return (dwMaxSD/30/2) + (level/5);
}
if (eCHAR_CONDITION_SITDOWN == byCharState) {
return (dwMaxSD/30*2) + (level/5*2);
}
return (dwMaxSD/30) + (level/5);
}
int ns_formula::ep1::CalcMPRecover(eCHAR_TYPE char_type, DWORD spirit, BYTE byCharState)
{
int recoverValue = 0;
float recoverRatio = 1.0f;
// 앉기 상태일 경우 회복량이 틀리다.
if (byCharState == eCHAR_CONDITION_SITDOWN) {
recoverRatio = 3.0f;
}
#ifdef _NA001605_EP2_ATTRIBUTES_RENEWAL_
switch (char_type)
{
case eCHAR_DRAGON : recoverValue = 2; break;
// 버서커는 타격계 회복이라.. 공격/맞을때 MP가 회복된다.
case eCHAR_BERSERKER : return -1; // 18
case eCHAR_SHADOW : recoverValue = 2; break;
case eCHAR_VALKYRIE : recoverValue = 2; break;
case eCHAR_ELEMENTALIST : recoverValue = 2; break;
case eCHAR_MYSTIC: // _NA_004965_20120613_NEW_CHARACTER_MYSTIC
recoverValue = 2;
break;
case eCHAR_HELLROID: //_NA_000000_20130812_NEW_CHARACTER_HELLROID
recoverValue = 2;
break;
case eCHAR_WITCHBLADE: //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
recoverValue = 2;
break;
default:
{
SASSERT(false, "Wrong Char_Type in CalcMPRecover()");
return 0;
}
}
#else
switch (char_type)
{
case eCHAR_DRAGON : recoverValue = 1; break;
// 버서커는 타격계 회복이라.. 공격/맞을때 MP가 회복된다.
case eCHAR_BERSERKER : return -1; // 18
case eCHAR_SHADOW : recoverValue = 1; break;
case eCHAR_VALKYRIE : recoverValue = 1; break;
case eCHAR_ELEMENTALIST : recoverValue = 1; break;
case eCHAR_MYSTIC: // _NA_004965_20120613_NEW_CHARACTER_MYSTIC
recoverValue = 1;
break;
case eCHAR_HELLROID: //_NA_000000_20130812_NEW_CHARACTER_HELLROID
recoverValue = 1;
break;
default:
{
SASSERT(false, "Wrong Char_Type in CalcMPRecover()");
return 0;
}
}
#endif
int recoverMP = (int)(spirit * recoverRatio / recoverValue / 5) + 1;
return recoverMP;
}
DWORD ns_formula::ep1::CalcMPRecoverByAttack(eCHAR_TYPE char_type, DWORD spirit,
DWORD skillRecoverPercent, DWORD skillRequireMP)
{
DWORD recoverValue = 0;
switch (char_type)
{
case eCHAR_DRAGON : return 0;
// 버서커/섀도우는 타격계 회복이라.. 공격/맞을때 MP가 회복된다.
case eCHAR_BERSERKER : recoverValue = 18; break;
case eCHAR_VALKYRIE : return 0;
case eCHAR_ELEMENTALIST : return 0;
case eCHAR_SHADOW : return 0;
case eCHAR_MYSTIC: // _NA_004965_20120613_NEW_CHARACTER_MYSTIC
return 0;
case eCHAR_HELLROID: //_NA_000000_20130812_NEW_CHARACTER_HELLROID
return 0;
case eCHAR_WITCHBLADE: //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
return 0;
default:
{
SASSERT(false, "Wrong Char_Type in CalcMPRecover()");
return 0;
}
}
// MP/SP 회복양 = (징신력/ 캐릭터별 상수) * 퍼센트 회복 스킬 + 스킬 요구 SP,MP/5 + 3
DWORD regenMP = spirit / recoverValue * skillRecoverPercent / 100 + skillRequireMP / 5 + 1;
return regenMP;
}
DWORD ns_formula::ep1::CalcMPRecoverByAttacked(eCHAR_TYPE char_type, DWORD spirit, DAMAGETYPE damage)
{
DWORD recoverValue = 0;
switch (char_type)
{
case eCHAR_DRAGON : return 0;
// 버서커/섀도우는 타격계 회복이라.. 공격/맞을때 MP가 회복된다.
case eCHAR_BERSERKER : recoverValue = 18; break;
case eCHAR_VALKYRIE : return 0;
case eCHAR_ELEMENTALIST : return 0;
case eCHAR_SHADOW : return 0;
case eCHAR_MYSTIC: // _NA_004965_20120613_NEW_CHARACTER_MYSTIC
return 0;
case eCHAR_HELLROID: //_NA_000000_20130812_NEW_CHARACTER_HELLROID
return 0;
case eCHAR_WITCHBLADE: //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
return 0;
default:
{
SASSERT(false, "Wrong Char_Type in CalcMPRecover()");
return 0;
}
}
// (정신력/캐릭터별 상수) + 실제 받은 데미지 / 30 + 3
DWORD regenMP = spirit / recoverValue + damage / 30 + 1;
return regenMP;
}
// HP, MP 회복량(몬스터)
int ns_formula::ep1::CalcNPCHPRecover(DWORD maxHP, DWORD recoverRatio)
{
return (int)(maxHP * 0.005f * recoverRatio);
}
int ns_formula::ep1::CalcNPCMPRecover(DWORD maxMP, DWORD recoverRatio)
{
return (int)(maxMP * 0.01f * recoverRatio);
}
// NPC SD 회복 공식
int ns_formula::ep1::CalcNPCSDRecover(DWORD maxSD)
{
return (int)(maxSD * 0.15f);
}
// Exp 배율수
inline float ns_formula::ep1::CalcExpRatioNum(DWORD partymemberNum)
{
return (float)partymemberNum;
}
EXPTYPE ns_formula::ep1::GetKillExpOfParty(
BYTE /*room_bonus_type*/,
DWORD party_member_num,
LEVELTYPE party_total_lv,
LEVELTYPE attacker_lv,
LEVELTYPE target_lv,
float npc_grade_exp_ratio,
DWORD attack_damage,
DWORD target_max_hp,
eZONETYPE zone_type,
Player* player)
{
if (party_member_num == 0 || target_max_hp == 0 || party_total_lv == 0)
{
return 0;
}
#if defined(_KOREA) || defined(_TAIWAN) || defined(_JAPAN) || \
defined(_USA) || defined(_GLOBAL) || defined(_RUSSIA)
const float level_diff_ratio = GetExpLevelRatioKorea(attacker_lv - target_lv);
#elif defined(_CHINA)
const float level_diff_ratio = GetExpLevelRatioChina(attacker_lv - target_lv);
#else
const float level_diff_ratio = GetExpLevelRatio(attacker_lv - target_lv);
#endif
float script_ratio = 10.0f;
{
const BASE_FORMULA_RATIOINFO* formula_ratio = NULL;
FormulaRatioParser* const formula_ratio_parser = FormulaRatioParser::Instance();
switch (zone_type)
{
case eZONETYPE_FIELD:
case eZONETYPE_VILLAGE:
case eZONETYPE_DOMINATION_FIELD:
case eZONETYPE_SPA_FIELD: //_NA_008069_20150210_ADD_SPA_SYSTEM
formula_ratio = formula_ratio_parser->GetFormulaRatio(RATIO_TYPE_EXP_FIELD);
break;
case eZONETYPE_CHUNTING:
case eZONETYPE_HUNTING:
formula_ratio = formula_ratio_parser->GetFormulaRatio(RATIO_TYPE_EXP_HUNTING);
break;
case eZONETYPE_MISSION:
formula_ratio = formula_ratio_parser->GetFormulaRatio(RATIO_TYPE_EXP_MISSION);
break;
default:
formula_ratio = formula_ratio_parser->GetFormulaRatio(RATIO_TYPE_EXP_FIELD);
break;
}
if (formula_ratio != NULL)
{
script_ratio = formula_ratio->m_fRatio;
}
}
EXPTYPE decision_exp = 0;
{
float seed_exp = (target_lv + 45) * 0.03f;
{
const float seed_exp_x3 = seed_exp * seed_exp * seed_exp;
const float min_exp = EXPTYPE(seed_exp_x3 * seed_exp + 3 * seed_exp_x3 - 5) *
npc_grade_exp_ratio * script_ratio;
const StatusField& status = player->GetStatusManager()->Status;
if (status.FIELD & status.ENABLE_GM_MAX_EXP)
{
decision_exp = EXPTYPE(min_exp);
}
else
{
decision_exp = (EXPTYPE)dRandom(min_exp / 2, min_exp);
}
}
}
const EXPTYPE damage_exp = (attack_damage * decision_exp) / target_max_hp;
float add_exp_ratio = 1.0f;
{
if (eZONETYPE_FIELD == zone_type || eZONETYPE_DOMINATION_FIELD == zone_type)
{
add_exp_ratio = GetFieldExpPartyBonusFactor(party_member_num);
}
else
{
add_exp_ratio = GetRoomExpPartyBonusFactor(party_member_num);
}
}
#if defined(_CHINA)
const float party_ratio = CalcExpRatioNum(party_member_num);
const float level_ratio = party_ratio * attacker_lv / party_total_lv;
#else
const LEVELTYPE party_lv = (LEVELTYPE)(party_total_lv / party_member_num);
const float level_ratio = GetPartyLevelExpRatio(attacker_lv - party_lv);
#endif
EXPTYPE result_exp = (EXPTYPE)(damage_exp * level_diff_ratio * add_exp_ratio * level_ratio);
if (result_exp <= 0)
{
result_exp = 1;
}
return result_exp;
}
//----------------------------------------------------------------------------------------------------
// 데미지 계산 관련(기초 공식들)
//----------------------------------------------------------------------------------------------------
// 공격 성공 체크
static bool ns_formula::ep1::IsPhysicalHit(Character* attacker, Character* target)
{
const Attributes& attacker_attrs = attacker->GetAttr();
int phy_attack_rate = attacker_attrs.GetPhysicalAttackRate();
if (phy_attack_rate <= 0)
{
phy_attack_rate = 1;
}
// 레벨에 따른 블록 가중
float fLevelWeight;
{
int diff_level = int(attacker->GetDisplayLevel()) - target->GetDisplayLevel();
fLevelWeight = 1.0f - diff_level * 0.05f;
};
// 블록 확률
int iAvoidValue;
{ // (WAVERIX) (090608) Minor change 'Convert to float on pre-calculation'
int calced = int(10000 * target->GetPhysicalAvoidValue() / float(phy_attack_rate));
iAvoidValue = min(10000, calced);
}
//int iAvoidValue = min(10000, (int)(10000* target->GetPhysicalAvoidValue() / nPhysicalAttackRate));
iAvoidValue = int(iAvoidValue * fLevelWeight);
if (iAvoidValue < 500) {
iAvoidValue = 500;
}
else if (9000 < iAvoidValue) {
iAvoidValue = 9000;
}
int iSuccessValue = random(0, 10000);
if (iSuccessValue <= iAvoidValue) {
return false;
}
return true;
}
// (WAVERIX) (090602) (CHANGES) internal function, change name '' -> '_CalcAttackPower'
static int ns_formula::ep1::_CalcAttackPower(BOOL bBase, eATTACK_TYPE eAttackType, BOOL bMin,
Character* attacker)
{
int iBaseAttackPower = 0;
const Attributes& rAttackerAttr = attacker->GetAttr();
// 기본 공격력(Base + Option)
if (bBase)
{
if (eAttackType == eATTACK_TYPE_MELEE)
{
iBaseAttackPower = rAttackerAttr.GetOptionPhysicalAttPower();
if (bMin) iBaseAttackPower += rAttackerAttr.GetBaseMeleeMinAttPower();
else iBaseAttackPower += rAttackerAttr.GetBaseMeleeMaxAttPower();
}
else if (eAttackType == eATTACK_TYPE_RANGE)
{
iBaseAttackPower = rAttackerAttr.GetOptionPhysicalAttPower();
if (bMin) iBaseAttackPower += rAttackerAttr.GetBaseRangeMinAttPower();
else iBaseAttackPower += rAttackerAttr.GetBaseRangeMaxAttPower();
}
else
{
iBaseAttackPower = rAttackerAttr.GetOptionMagicAttPower();
if (bMin) iBaseAttackPower += rAttackerAttr.GetBaseMagicMinAttPower();
else iBaseAttackPower += rAttackerAttr.GetBaseMagicMaxAttPower();
}
}
// 속성 공격력(Magical)
else
{
iBaseAttackPower = rAttackerAttr.GetMagicalAttackPower(eAttackType);
}
iBaseAttackPower += (int)(iBaseAttackPower * rAttackerAttr.GetAttackIncRate() / 100.0f);
return iBaseAttackPower;
}
//#if !defined(_NA001605_EP2_ATTRIBUTES_RENEWAL_)
// 타겟 방어력
static int ns_formula::ep1::CalcDefensePower(BOOL bBase, eATTACK_TYPE eAttackType,
Character* attacker, Character* target,
float fDefenseIgnore)
{
int iTargetDefense = 0;
const Attributes& rAttackerAttr = attacker->GetAttr();
const Attributes& rTargetAttr = target->GetAttr();
// 기본 방어력(Base + Option)
if (bBase)
{
if (eAttackType == eATTACK_TYPE_MELEE)
{
iTargetDefense = rTargetAttr.GetBaseMeleeDefPower();
iTargetDefense += rTargetAttr.GetOptionPhysicalDefPower();
}
else if (eAttackType == eATTACK_TYPE_RANGE)
{
iTargetDefense = rTargetAttr.GetBaseRangeDefPower();
iTargetDefense += rTargetAttr.GetOptionPhysicalDefPower();
}
else
{
iTargetDefense = rTargetAttr.GetBaseMagicDefPower();
iTargetDefense += rTargetAttr.GetOptionMagicDefPower();
}
}
// 속성 방어력(Magical)
else
{
iTargetDefense = rTargetAttr.GetMagicalDefense(eAttackType);
}
// 추가 방어력
iTargetDefense += rTargetAttr.GetBonusDefense(eAttackType);
// 방어력 증가율, 감소율
int iIncreaseDefence = int(iTargetDefense * rTargetAttr.GetDefenseIncRate() / 100.0f);
int iDecreaseDefense = 0;
if (attacker) {
iDecreaseDefense = int(iTargetDefense * rAttackerAttr.GetReduceDefenseRate(eAttackType) / 100.0f);
}
iTargetDefense = iTargetDefense + iIncreaseDefence - iDecreaseDefense;
// 방어력 무시
if (0 < fDefenseIgnore &&
fDefenseIgnore <= 1.0f)
{
iTargetDefense -= (int)(iTargetDefense * fDefenseIgnore);
}
return iTargetDefense;
}
// 1차 데미지 계산
static int ns_formula::ep1::CalcFirstDamage(eATTACK_TYPE eAttackType,
int iBaseAttackPower, int iSkillAttackPower,
short sSkillStat, float fSkillPercentDamage)
{
float fRate = 1.0f;
#if WAVERIX_CODE_BACKUP
if (eAttackType == eATTACK_TYPE_MELEE || eAttackType == eATTACK_TYPE_RANGE) {
fRate = 1.0;
}
else {
fRate = 1.0;
}
#endif
int iFirstDamage = (int)((iBaseAttackPower + iSkillAttackPower + sSkillStat) *
(fRate + fSkillPercentDamage));
iFirstDamage = max(0, iFirstDamage);
return iFirstDamage;
}
// 2차 데미지 계산
static int ns_formula::ep1::CalcSecondDamage(eATTACK_TYPE eAttackType,
int iFirstMinDamage, int iFirstMaxDamage,
int iBonusDamage,
LEVELTYPE attackerLevel, LEVELTYPE targetLevel,
int iTargetDefense,
float fArmorDamageRatio, float fDamageDecreaseRatio)
{
int iFirstDamage = (int)dRandom(iFirstMinDamage, iFirstMaxDamage);
int iSecondDamage = 0;
float fRatio = 0;
iTargetDefense = max(0, iTargetDefense);
// (WAVERIX) (090602) (NOTE) 뭐 하자는 건가? AttackType이 구분되어 있지만, 정리해 보니까
// 둘 모두 동일한 계산 로직을 가지고 있다. 뭐... 장기적으로 공식이 구분될 가능성을 고려한
// 다면 필요한 형태일 지도 모르겠지만... 정리하고 나서야 동일하다는 것을 알게 되니, 어이만
// 없어라...
// (추가) 하나만 삐긋해도 문제가 크게 발생하는 정말 위태위태한...
// 물리 공격
if (eAttackType == eATTACK_TYPE_MELEE || eAttackType == eATTACK_TYPE_RANGE)
{
fRatio = (float)(80 + targetLevel * 6 - iTargetDefense) /
(float)(750 + targetLevel * 2) +
fDamageDecreaseRatio +
(int(attackerLevel) - targetLevel) * 0.03f;
if (fRatio < 0.1f) {
fRatio = 0.1f;
}
iSecondDamage = int(iFirstDamage * min(1.0f, fRatio));
iSecondDamage = int(iSecondDamage * fArmorDamageRatio);
}
// 마법 공격
else
{
fRatio = (float)(80 + targetLevel * 6 - iTargetDefense) /
(float)(750 + targetLevel * 2) +
fDamageDecreaseRatio +
(int(attackerLevel) - targetLevel) * 0.03f;
if (fRatio < 0.1f) {
fRatio = 0.1f;
}
iSecondDamage = (int)(iFirstDamage * min(1.0f, fRatio));
iSecondDamage = (int)(iSecondDamage * fArmorDamageRatio);
}
iSecondDamage += iBonusDamage;
if (iSecondDamage < 0) {
iSecondDamage = 0;
}
return iSecondDamage;
}
// 기본 데미지
static int ns_formula::ep1::CalcBaseDamage(Character* attacker, Character* target,
eATTACK_TYPE eAttackType,
int iSkillAttackPower, float fSkillPercentDamage,
BYTE bySkillStatType, float fDefenseIgnore)
{
// 기본공격력
int iBaseMinAttackPower = _CalcAttackPower(true, eAttackType, true, attacker);
int iBaseMaxAttackPower = _CalcAttackPower(true, eAttackType, false, attacker);
const Attributes& rAttackerAttr = attacker->GetAttr();
const Attributes& rTargetAttr = target->GetAttr();
int incBaseMinDamageRatio = 0; /* BaseMinAttackPower -= BaseMinAttackPower * MinDamage 감소률 */
{
if ((incBaseMinDamageRatio = rAttackerAttr.GetRatioIncreaseMinDamage()) > 0) {
iBaseMinAttackPower -= int(iBaseMinAttackPower * (incBaseMinDamageRatio / 100.0f));
}
iBaseMinAttackPower += rAttackerAttr.GetItemIncreaseMinDamage();
}
int incBaseMaxDamageRatio = 0; /* BaseMaxAttackPower -= BaseMaxAttackPower * MaxDamage감소률 */
{
if ((incBaseMaxDamageRatio = rAttackerAttr.GetRatioIncreaseMaxDamage()) > 0) {
iBaseMaxAttackPower -= int(iBaseMaxAttackPower * (incBaseMaxDamageRatio / 100.0f));
}
iBaseMaxAttackPower += rAttackerAttr.GetItemIncreaseMaxDamage();
}
int etherDamageRatio = rAttackerAttr.GetEhterDamageRatio();
if (etherDamageRatio > 0)
{
const float tmpEtherDamageRatio = float(etherDamageRatio) / 100.0f;
iBaseMinAttackPower = int(iBaseMaxAttackPower * (0.95f + tmpEtherDamageRatio));
iBaseMaxAttackPower = int(iBaseMaxAttackPower * (1.05f + tmpEtherDamageRatio));
}
// 스킬공격력
iSkillAttackPower += rAttackerAttr.GetSkillAttackPower();
// 스킬숙련도
short sSkillStat = 0;
if (bySkillStatType == 1) sSkillStat = rAttackerAttr.GetExperty1();
else if (bySkillStatType == 2) sSkillStat = rAttackerAttr.GetExperty2();
// 타겟방어력
if (IsBypassDeffence(attacker))
fDefenseIgnore = 1.0f;
int iTargetDefense = CalcDefensePower(true, eAttackType, attacker, target, fDefenseIgnore);
// 스킬퍼센트추가데미지
fSkillPercentDamage += (float)(rAttackerAttr.GetSkillPercentDamage() / 100.0f);
// 1차 데미지 계산
int iFirstMinDamage = CalcFirstDamage(eAttackType, iBaseMinAttackPower, iSkillAttackPower,
sSkillStat, fSkillPercentDamage);
int iFirstMaxDamage = CalcFirstDamage(eAttackType, iBaseMaxAttackPower, iSkillAttackPower,
sSkillStat, fSkillPercentDamage);
eARMOR_TYPE targetArmorType = target->GetArmorType();
eMELEE_TYPE meleeType = attacker->GetMeleeType();
// 보너스 데미지, 아머 타입별 데미지 적용률
int iBonusDamage = rAttackerAttr.GetBonusDamage(targetArmorType) -
rTargetAttr.GetReduceDamage(eAttackType);
float fBonusPercentDamage = rAttackerAttr.GetBonusPercentDamage(targetArmorType) / 100.0f;
float fArmorDamageRatio =
ArmorDamageParser::Instance()->FindDamageRatio(targetArmorType, meleeType);
fArmorDamageRatio = fArmorDamageRatio / 100.f + fBonusPercentDamage;
// 2차 데미지 계산
int iSecondDamage = 0;
StatusField& rStatus = attacker->GetStatusManager()->Status;
{
int tempDamage = iFirstMinDamage;
if (rStatus.FIELD & rStatus.ENABLE_GM_MAX_DAMAGE)
tempDamage = iFirstMaxDamage;
float fDamageDecreaseRatio = 0.70f;
if (false != target->IsEqualObjectKind(PLAYER_OBJECT) &&
false != attacker->IsEqualObjectKind(PLAYER_OBJECT)) //PVP 시 데미지 차감 공식
{
fDamageDecreaseRatio = 0.30f;
}
iSecondDamage = CalcSecondDamage(eAttackType,
tempDamage, iFirstMaxDamage, iBonusDamage,
attacker->GetDisplayLevel(), target->GetDisplayLevel(),
iTargetDefense, fArmorDamageRatio, fDamageDecreaseRatio);
}
// 최소보장을 체크한다.
if (iSecondDamage <= (int)(iFirstMinDamage / 20))
{
int damageBound = iFirstMinDamage / 40 + 1;
iSecondDamage = damageBound + (int)dRandom(0, damageBound);
}
return iSecondDamage;
}
// 속성 데미지
static int ns_formula::ep1::CalcMagicalDamage(Character* attacker, Character* target,
eATTACK_TYPE eAttackType, int iSkillAttackPower,
float fSkillPercentDamage, BYTE bySkillStatType)
{
// 속성공격력
int iBaseAttackPower = _CalcAttackPower(false, eAttackType, true, attacker);
const Attributes& rAttackerAttr = attacker->GetAttr();
const Attributes& rTargetAttr = target->GetAttr();
// (WAVERIX) (090602) (NOTE) FileVersion 182에서 Line 676과 일부 코드 중복 발생 중...
int iBaseMinAttackPower = 0, iBaseMaxAttackPower = 0;
int etherDamageRatio = rAttackerAttr.GetEhterDamageRatio();
if (etherDamageRatio > 0)
{
const float tmpEtherDamageRatio = etherDamageRatio / 100.0f;
iBaseMinAttackPower = int(iBaseAttackPower * (0.95f + tmpEtherDamageRatio));
iBaseMaxAttackPower = int(iBaseAttackPower * (1.05f + tmpEtherDamageRatio));
}
else
{
iBaseMinAttackPower = iBaseMaxAttackPower = iBaseAttackPower;
}
// 스킬퍼센트추가데미지
fSkillPercentDamage += (float)(rAttackerAttr.GetSkillPercentDamage() / 100.0f);
// 1차 데미지 계산
int iFirstMinDamage = CalcFirstDamage(eAttackType, iBaseMinAttackPower, 0, 0, fSkillPercentDamage);
int iFirstMaxDamage = CalcFirstDamage(eAttackType, iBaseMaxAttackPower, 0, 0, fSkillPercentDamage);
// 보너스 데미지, 아머 타입별 데미지 적용률
int iBonusDamage = -rTargetAttr.GetReduceDamage(eAttackType);
int iTargetDefense = CalcDefensePower(false, eAttackType, attacker, target, 0);
// 2차 데미지 계산
int iSecondDamage = 0;
StatusField& rStatus = attacker->GetStatusManager()->Status;
int tempDamage = iFirstMinDamage;
if (rStatus.FIELD & rStatus.ENABLE_GM_MAX_DAMAGE)
tempDamage = iFirstMaxDamage;
float fDamageDecreaseRatio = 0.70f;
if (false != target->IsEqualObjectKind(PLAYER_OBJECT) &&
false != attacker->IsEqualObjectKind(PLAYER_OBJECT))
{
fDamageDecreaseRatio = 0.30f;
}
iSecondDamage = CalcSecondDamage(eAttackType, tempDamage, iFirstMaxDamage, iBonusDamage,
attacker->GetDisplayLevel(), target->GetDisplayLevel(),
iTargetDefense, 1.0f, fDamageDecreaseRatio);
return iSecondDamage;
}
static BOOL ns_formula::ep1::IsCriticalHit(eOBJECT_TYPE eObjectType, int criticalRatio, LEVELTYPE level)
{
if ((eObjectType & PLAYER_OBJECT) == PLAYER_OBJECT)
{
int block_factor = 30 + level * 4;
int block_ratio = (int)dRandom(1, block_factor);
if (block_ratio <= criticalRatio)
{
return true;
}
}
else
{
//몬스터의 경우는 NPCInfo.txt에서 확률값(criticalRatio)을 얻어서 판정
if ((int)random(1, 100) <= criticalRatio) {
return true;
}
}
return false;
}
// 크리티컬 데미지 계산
// (WAVERIX) (090602) (NOTE) internal function
static int ns_formula::ep1::_CalcCriticalDamage(eOBJECT_TYPE eObjectType,
LEVELTYPE targetLevel,
int iSecondDamage,
int iCriticalRatio,
int iCriticalDamageBonus,
float fCriticalDamagePercentBonus,
BOOL needMaxDamage)
{
if (!IsCriticalHit(eObjectType, iCriticalRatio, targetLevel))
return 0;
int iCriticalDamage = 0;
if (needMaxDamage != false) {
iCriticalDamage = (int)(iSecondDamage * 1.2f + iSecondDamage * 0.5f);
}
else {
iCriticalDamage = (int)(iSecondDamage * 1.2f + (int)dRandom(0, iSecondDamage) * 0.5f);
}
// 크리티컬 데미지(퍼센트) 보너스
iCriticalDamage = (int)(iCriticalDamage * (1.0f + fCriticalDamagePercentBonus));
iCriticalDamage += iCriticalDamageBonus;
return iCriticalDamage;
}
//----------------------------------------------------------------------------------------------------
// 데미지 계산(일반+스킬)
//----------------------------------------------------------------------------------------------------
// (WAVERIX) (090602) (CHANGES) internal function
static DAMAGETYPE ns_formula::ep1::_CalcDamage(BOOL bNormalAttack,
Character* attacker, Character* target,
eATTACK_TYPE eBaseAttackType,
eATTACK_TYPE eMagicalAttackType,
int iSkillAttackPower,
float fSkillPercentDamage,
WORD criticalRatioBonus,
BYTE* pEffect,
BYTE bySkillStatType,
float fDefenseIgnore,
BOOL bCritical)
{
// 일반 공격일 경우는 블록율을 계산하고 스킬 공격일 경우는 계산하지 않음!!
if (bNormalAttack && !IsPhysicalHit(attacker, target)) {
return 0;
}
int iResultDamage = 0;
// 기본 데미지
int iBaseDamage = CalcBaseDamage(attacker, target,
eBaseAttackType, iSkillAttackPower,
fSkillPercentDamage, bySkillStatType, fDefenseIgnore);
iResultDamage += iBaseDamage;
// 속성 데미지
int iMagicalDamage = CalcMagicalDamage(attacker, target,
eMagicalAttackType, iSkillAttackPower,
fSkillPercentDamage, bySkillStatType);
iResultDamage += iMagicalDamage;
const Attributes& rAttackerAttr = attacker->GetAttr();
if (bCritical != false)
{ // 크리티컬 확률, 크리티컬 데미지 보너스, 크리티컬 데미지 퍼센트 보너스
int iCriticalRatio = criticalRatioBonus;
if (eBaseAttackType == eATTACK_TYPE_MELEE || eBaseAttackType == eATTACK_TYPE_RANGE) {
iCriticalRatio += rAttackerAttr.GetCriticalRatioChange();
}
else {
iCriticalRatio += rAttackerAttr.GetMagicCriticalRatio();
}
int iCriticalDamageBonus = rAttackerAttr.GetCriticalDamageBonus();
float fCriticalDamagePercentBonus = rAttackerAttr.GetRatioCriticalDamageBonus() / 100.0f;
// 크리티컬 데미지 계산
int iCriticalDamage = 0;
StatusField& rStatus = attacker->GetStatusManager()->Status;
{
BOOLEAN needMaxDamage = false;
if (rStatus.FIELD & rStatus.ENABLE_GM_MAX_DAMAGE) {
needMaxDamage = true;
}
iCriticalDamage = _CalcCriticalDamage(attacker->GetObjectType(),
target->GetDisplayLevel(), iResultDamage, iCriticalRatio, iCriticalDamageBonus,
fCriticalDamagePercentBonus, needMaxDamage);
}
if (iCriticalDamage && pEffect != 0)
{
*pEffect |= SKILL_EFFECT_CRITICAL;
}
else if (pEffect != 0)
{
*pEffect &= ~SKILL_EFFECT_CRITICAL;
}
else
{
// DISPMSG("CalcSkillDamage!\n");
}
// 최종 데미지
if (iCriticalDamage)
{
iResultDamage = iCriticalDamage;
}
}
// 더블 데미지: 확율로 데미지가 두배로 증가한다.
int iDoubleDamage = CalcDoubleDamage(attacker, iResultDamage);
if (iDoubleDamage && pEffect != 0)
{
iResultDamage += iDoubleDamage;
*pEffect |= SKILL_EFFECT_DOUBLEDAMAGE;
}
else if (pEffect != 0)
{
*pEffect &= ~SKILL_EFFECT_DOUBLEDAMAGE;
//DISPMSG("CalcSkillDamage!\n");
}
// 추가 데미지가 있다면..
iResultDamage += rAttackerAttr.GetAddDamage();
return iResultDamage;
}
//==================================================================================================
// 외부에서 쓰는 함수(일반, 스킬)
//==================================================================================================
DAMAGETYPE ns_formula::ep1::CalcNormalDamage(Character* attacker, Character* target,
eATTACK_TYPE eBaseAttackType,
eATTACK_TYPE eMagicalAttackType,
WORD criticalRatioBonus,
BYTE* pCritical,
float fDefenseIgnore)
{
DAMAGETYPE wDamage = _CalcDamage(true, attacker, target,
eBaseAttackType, eMagicalAttackType,
0, 0, criticalRatioBonus, pCritical, 0, fDefenseIgnore);
return wDamage;
}
DAMAGETYPE ns_formula::ep1::CalcSkillDamage(Character* attacker, Character* target,
eATTACK_TYPE eAttackType, int iSkillAttackPower,
float fSkillPercentDamage, WORD criticalRatioBonus,
BYTE* pCritical,
BYTE bySkillStatType,
BOOL bCritical)
{
eATTACK_TYPE eBaseAttackType = eATTACK_TYPE_MAGIC;
if (eAttackType == eATTACK_TYPE_MELEE || eAttackType == eATTACK_TYPE_RANGE)
eBaseAttackType = eAttackType;
DAMAGETYPE wDamage = _CalcDamage(false, attacker, target,
eBaseAttackType, eAttackType,
iSkillAttackPower, fSkillPercentDamage,
criticalRatioBonus, pCritical, bySkillStatType, 0, bCritical);
return wDamage;
}
//#endif //_NA001605_EP2_ATTRIBUTES_RENEWAL_
//_NA_001231_20081216_ADD_OPTION_KIND
bool ns_formula::ep1::IsStatusHit(
Character* const attacker,
Character* const target,
const int success_ratio,
const int state_id,
const Skill* const skill)
{
if (attacker == NULL || target == NULL)
{
return false;
}
#ifdef _NA_004530_20120315_1202_SKILL_RENEWAL
// 대상과의 관계에 상관없이 스크립트 입력 확률을 기준으로 검사한다.
#else
if (attacker->IsFriend(target) == USER_RELATION_FRIEND)
{
return true;
}
#endif
float calc_ratio = (float)success_ratio;
// 연계 스킬 사용에 의한 성공 확률 증가를 검사한다.
if (skill != NULL)
{
const int combo_ratio =
ns_formula::ability::CalcComboSuccessRatio(*attacker, *skill, state_id);
if (combo_ratio != 0)
{
calc_ratio += (float)combo_ratio;
}
}
if (attacker->IsEqualObjectKind(PLAYER_OBJECT) && target->IsEqualObjectKind(PLAYER_OBJECT))
{
calc_ratio = (float)success_ratio / 10.0f;
}
else
{
float grade_ratio = 1.0f;
if (target->IsEqualObjectKind(NPC_OBJECT))
{
const NPC* const npc = static_cast<const NPC*>(target);
grade_ratio = GetStatusRatioAsNPCGrade(npc->GetGrade());
#ifdef _NA_007123_20140324_SHADOW_SKILL_RENEWAL
switch(state_id)
{
case eCHAR_STATE_POISON:
case eCHAR_STATE_POISON2:
case kCharStatePoison3:
case eCHAR_STATE_WOUND:
case eCHAR_STATE_FIRE_WOUND:
case eCHAR_STATE_FIRE_WOUND2:
case eCHAR_STATE_PAIN:
case eCHAR_STATE_PAIN2:
case eCHAR_STATE_PHOENIX_BURN:
case eCHAR_STATE_ELECTRICSHOCK:
case eCHAR_STATE_HP_SUCTION:
case eCHAR_STATE_MP_SUCTION:
{
grade_ratio = 1.0f;
} break;
}
#else
#ifdef _NA_000000_20130529_STATUS_EXCEPTIONS_AS_NPC_GRADE
switch(state_id)
{
case eCHAR_STATE_POISON:
case eCHAR_STATE_WOUND:
case eCHAR_STATE_FIRE_WOUND:
case eCHAR_STATE_FIRE_WOUND2:
case eCHAR_STATE_PAIN:
case eCHAR_STATE_PAIN2:
case eCHAR_STATE_POISON2:
case eCHAR_STATE_PHOENIX_BURN:
{
const BYTE npc_grade = npc->GetGrade();
if (npc_grade == eNPC_ELITE || npc_grade == eNPC_MIDDLEBOSS || npc_grade == eNPC_BOSS)
{
grade_ratio = 1.0f;
}
} break;
}
#endif //_NA_000000_20130529_STATUS_EXCEPTIONS_AS_NPC_GRADE
#endif //_NA_007123_20140324_SHADOW_SKILL_RENEWAL
}
calc_ratio = ((float)success_ratio * grade_ratio) / 10.0f;
}
//#ifdef _NA_006040_20120830_RESIS_BAD_STATUS_RATIO
BASE_STATEINFO* const state_info = \
StateInfoParser::Instance()->GetStateInfo(state_id);
if (state_info->m_byType == 3 || state_info->m_byType == 4)
{
calc_ratio = 100.0f;
}
else
{
calc_ratio -= float(target->GetResistBadStatusRatio(state_id));
}
int calc_success_ratio = round(calc_ratio);
calc_success_ratio = min(calc_success_ratio, 100);
calc_success_ratio = max(calc_success_ratio, 0);
if (DrawLots((BYTE)calc_success_ratio))
{
return true;
}
return false;
}
float ns_formula::ep1::GetDuraDecRatio(eZONE_STATE zone_state,
eCOMPETITION_MODE_TYPE eCompetitionMode)
{
float fDecRatio = 0.0f;
switch (zone_state)
{
case ePRS_AT_MISSION:
fDecRatio = 0.1f;
break;
case ePRS_AT_FIELD:
fDecRatio = 0.1f;
break;
case ePRS_AT_DOMINATION_FIELD:
#ifdef _NA_006826_20130722_DOMINATION_RENEWAL
fDecRatio = 0.025f;
#else
fDecRatio = 0.05f;
#endif //_NA_006826_20130722_DOMINATION_RENEWAL
break;
case ePRS_AT_PVP:
fDecRatio = 0.0f;
break;
case ePRS_AT_CHUNTING:
switch (eCompetitionMode)
{
case eCOMPETITION_MODE_SURVIVAL: fDecRatio = 0.05f; break;
case eCOMPETITION_MODE_TIME_ATTACK: fDecRatio = 0.10f; break;
case eCOMPETITION_MODE_DEATH_MATCH: fDecRatio = 0.05f; break;
default: fDecRatio = 0.20f; break;
}
break;
default:
fDecRatio = 0.2f;
break;
}
return fDecRatio;
}
//_NA_005026_20120527_CHAOS_ZONE_BATTLE_GROUND
float ns_formula::ep1::GetDuraDecRatio(eZONE_STATE zone_state, ChaosZoneType chaos_zone_type)
{
float fDecRatio = 0.0f;
switch (zone_state)
{
case ePRS_AT_INSTANCE:
switch(chaos_zone_type)
{
#ifdef _NA_007065_20140217_CHAOSZONE_GOLDRUSH
case eCHAOSZONE_TYPE_GOLDRUSH:
#endif //_NA_007065_20140217_CHAOSZONE_GOLDRUSH
case kBattleGround: fDecRatio = 0.05f; break; // 전장 장비 내구도 감소 비율
case kFreePVP: fDecRatio = 0.00f; break; // 자유전투실 장비 내구도 감소 비율
#ifdef _NA_008334_20150608_SONNENSCHEIN
case eCHAOSZONE_TYPE_SONNENSCHEIN: fDecRatio = 0.025f; break;
#endif //_NA_008334_20150608_SONNENSCHEIN
default: fDecRatio = 0.10f; break; // 그외 모든 인스턴스 던전
}
break;
default:
fDecRatio = 0.2f;
break;
}
return fDecRatio;
}
}}; //end of namespace 'ns_formula::ep1'
////////////////////////////////////////////////////////////////////////////////////////////////////
// REGION : EP2
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _NA001605_EP2_ATTRIBUTES_RENEWAL_
namespace ns_formula { namespace ep2 {
;
// remove attributes list
// = { OptionMagicAttPower, BaseMagicMinAttPower, BaseMagicMaxAttPower,
// GetOptionMagicDefPower, GetBaseMagicDefPower,
// GetBaseMeleeDefPower, GetBaseRangeDefPower,
// GetMagicCriticalRatio
//
// GetMagicalAttackPower(eATTACK_TYPE attack_type, eATTR_KIND attr_kind = eATTR_KIND_CALC) const;
// GetMagicalDefense(eATTACK_TYPE attack_type) const;
// GetBonusDefense(eATTACK_TYPE attack_type) const;
// GetReduceDefenseRate(eATTACK_TYPE attack_type) const;
// GetBonusDamage(eARMOR_TYPE armor_type) const;
// GetBonusPercentDamage(eARMOR_TYPE armor_type) const;
// GetReduceDamage(eATTACK_TYPE attack_type) const;
// BypassDeffenceRatio
// renaming attributes list
// = {
// GetBaseMeleeDefPower -> GetBaseDefensePower,
/*
*/
//==================================================================================================
class DamageCalculator
{
public:
#ifdef _DEBUG
#define DamageMinMaxRandom(output_type, min_val, max_val) \
static_cast<output_type>(\
(this->kSeedValue / static_cast<double>(0xFFFFFFFFUL) * \
(max_val - min_val)) + \
min_val)
#else
#define DamageMinMaxRandom(output_type, min_val, max_val) \
static_cast<output_type>(\
(RandomNumberGenerator::GetRandomNumber() / static_cast<double>(0xFFFFFFFFUL) * \
(max_val - min_val)) + \
min_val)
#endif
DamageCalculator(Character* attacker, Character* defender,
eATTACK_TYPE base_attack_type, eATTACK_TYPE skill_attack_type,
bool handle_to_fix_damage, bool handle_to_fix_skill_damage);
//
int CalculateTotalDamage(
const bool handle_critical_calculation,
const uint16_t rate_of_critical_bonus,
uint8_t* const result_of_skill_effect_flag,
//
const int skill_attack_power, // + value
const float skill_added_ratio_damage) const; // * (100% + ratio value)
private:
// calculate the probability of it if this process is normal attack
bool CanPassNextStepOnNormalDamage() const;
// unused Episode II version
bool IsBypassDeffence() const;
//
bool IsApplyCriticalDamage(int rate_of_critical_occurrance) const;
// T = (Base + Option)
// [BaseAttackPower] = T + (T * increase_ratio)
void GetDefaultAttackPower(
int* const result_min, int* const result_max) const;
// T = (Base + Option)
// [BaseDefensePower] = T + (T * increase_ratio)
void GetDefaultDefensePower(int* const result_value) const;
//
void CalculateSharedDamageOption(
int* const base_attack_power_min, int* const base_attack_power_max) const;
#ifdef _NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
// 스킬 및 원소공격력에 에테르 탄환 추가 데미지 적용
void CalculateSharedDamageOption(int* const base_skill_attack_power) const;
#endif //_NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
//
void CalculateDefaultDamageOption(
int* const base_attack_power_min, int* const base_attack_power_max) const;
// skill_attack_power : input/output
void CalculateSkillDamage(
int* const skill_attack_power, const float skill_added_ratio_damage) const;
//
void CalculateElementDamage(
int* const attributes_attack_power,
eATTACK_TYPE attack_type,
const int skill_attack_power,
const float skill_added_ratio_damage) const;
//void CalcAttributeAttackPower(
// Character* attacker, eATTACK_TYPE attack_type, Character* defender,
// int* const result_max, int* const result_min);
//
int CalculateLastAdjustmentDamage(
int confirmed_damage, int confirmed_defense,
double damage_decrease_ratio) const;
//
int CalculateCriticalDamage(
bool handle_to_max_damage,
int adjustment_damage, int rate_of_critical_ratio) const;
#ifdef _NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
int CalculateAdditionFPDamage(
Character* skill_user,
int damage
) const;
int CalculateIncreaseDamageRatio(
Character* skill_user,
int damage
) const;
#endif //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
//
// data fields
//
const DWORD kSeedValue;
const bool handle_to_fix_damage_; // CHANGES: f1110215.3L, added to support periodic damage
const bool handle_to_fix_skill_damage_;
const bool is_pvp_; // CHANGES: f110601.4L, added pvp tags to support PvP, PvE brached formula
Character* const attacker_;
const Attributes& attacker_attr_;
const int attacker_level_;
Character* const defender_;
const Attributes& defender_attr_;
const int defender_level_;
//
const eATTACK_TYPE base_attack_type_;
const eATTACK_TYPE skill_attack_type_;
//
static DamageBalanceConstant kConstants;
//
friend bool ep2::ModifyDamageBalanceConstant(const DamageBalanceConstant&);
friend bool ep2::GetCurrentDamageBalanceConstant(DamageBalanceConstant* const);
__DISABLE_COPY(DamageCalculator);
};
//==================================================================================================
// CHANGES: f110706.4L, waverix, the #2997 post changes that are modified by 'youngmoon' change
// the damage_deduction_pvp_weight constant value.
ns_formula::ep2::DamageBalanceConstant
ns_formula::ep2::DamageCalculator::kConstants =
{
3.0f, // attaker_level_weight
4.0f, // diff_level_weight
0.1f, // damage_deduction_pvp_weight (CHANGES: f110706.4L)
0.6f // damage_deduction_pve_weight (CHANGES: f110518.8L, '0.7'->'0.6', planner's request)
};
//==================================================================================================
ns_formula::ep2::DamageCalculator::DamageCalculator(Character* attacker, Character* defender,
eATTACK_TYPE base_attack_type, eATTACK_TYPE skill_attack_type,
bool handle_to_fix_damage, bool handle_to_fix_skill_damage)
: attacker_(attacker)
, handle_to_fix_damage_(handle_to_fix_damage)
, handle_to_fix_skill_damage_(handle_to_fix_skill_damage)
, attacker_attr_(attacker->GetAttr())
, attacker_level_(attacker->GetDisplayLevel())
, defender_(defender)
, defender_attr_(defender->GetAttr())
, defender_level_(defender->GetDisplayLevel())
, is_pvp_(attacker->IsEqualObjectKind(PLAYER_OBJECT) &&
defender->IsEqualObjectKind(PLAYER_OBJECT))
, base_attack_type_(base_attack_type)
, skill_attack_type_(skill_attack_type)
, kSeedValue(RandomNumberGenerator::GetRandomNumber())
{
}
// calculate the probability of it if this process is normal attack
bool ns_formula::ep2::DamageCalculator::CanPassNextStepOnNormalDamage() const
{
int attacker_probablity = attacker_attr_.GetPhysicalAttackRate(); // 100 -> 100%
int defender_probablity = defender_attr_.GetPhysicalAvoidRate(); // 100 -> 100%
if (attacker_probablity >= 100) {
attacker_probablity = 99;
};
#ifdef _NA_005010_20120621_RATE_OF_DEFENCE_SUCCESS_MODIFY
if (defender_probablity >= 81)
{
defender_probablity = 80;
}
#else
if (defender_probablity >= 100) {
defender_probablity = 99;
}
#endif // _NA_005010_20120621_RATE_OF_DEFENCE_SUCCESS_MODIFY
int enable_attack_draw_number = DamageMinMaxRandom(int, 0, 100);
bool enable_attack = (enable_attack_draw_number <= attacker_probablity);
if (enable_attack)
{
int enable_block_draw_number = DamageMinMaxRandom(int, 0, 100);
bool enable_block = (enable_block_draw_number <= defender_probablity);
if (enable_block) {
enable_attack = false;
}
};
return enable_attack;
}
bool ns_formula::ep2::DamageCalculator::IsBypassDeffence() const
{
int bypass_ratio = attacker_attr_.GetBypassDeffenceRatio();
if (bypass_ratio < 0) {
return false;
}
int spreading_value = ((kSeedValue >> 3) % 100);
return (spreading_value < bypass_ratio) ? true : false;
}
bool ns_formula::ep2::DamageCalculator::IsApplyCriticalDamage(
int rate_of_critical_occurrance) const
{
if (attacker_->IsEqualObjectKind(PLAYER_OBJECT))
{
// CHANGES: f110103.2L, change critical occurrence probability
#if SUN_CODE_BACKUP
int block_factor = 30 + (defender_level_ * 4);
// block_ratio = [0, block_factor)
int block_ratio = (kSeedValue >> 3) % block_factor;
if (block_ratio < rate_of_critical_occurrance) {
return true;
}
#endif
int block_ratio = (kSeedValue >> 3) % 100;
if (block_ratio < rate_of_critical_occurrance) {
return true;
}
}
else
{
// BASE_NPCINFO::m_byCriticalRatio
// block_ratio = [0, 100)
int block_ratio = (kSeedValue >> 9) % 100;
if (block_ratio < rate_of_critical_occurrance) {
return true;
}
}
return false;
}
//==================================================================================================
void ns_formula::ep2::DamageCalculator::GetDefaultAttackPower(
int* const result_min, int* const result_max) const
{
// T = (Base + Option)
// [BaseAttackPower] = T + (T * increase_ratio)
int base_min = 0, base_max = 0;
int option_value = 0;
//
; if (base_attack_type_ == eATTACK_TYPE_MELEE)
{
option_value = attacker_attr_.GetOptionPhysicalAttPower();
base_min += attacker_attr_.GetBaseMeleeMinAttPower();
base_max += attacker_attr_.GetBaseMeleeMaxAttPower();
}
else if (base_attack_type_ == eATTACK_TYPE_RANGE)
{
option_value = attacker_attr_.GetOptionPhysicalAttPower();
base_min += attacker_attr_.GetBaseRangeMinAttPower();
base_max += attacker_attr_.GetBaseRangeMaxAttPower();
}
//
if (option_value)
{
base_min += option_value;
base_max += option_value;
};
if (int attack_increase_ratio = attacker_attr_.GetAttackIncRate())
{
if (base_min) {
base_min = base_min + static_cast<int>(base_min * attack_increase_ratio / 100.0f);
}
if (base_max) {
base_max = base_max + static_cast<int>(base_max * attack_increase_ratio / 100.0f);
}
};
if (result_max) {
*result_max = base_max;
}
if (result_min) {
*result_min = base_min;
}
}
//==================================================================================================
// T = (Base + Option)
// [BaseDefensePower] = T + (T * increase_ratio)
void ns_formula::ep2::DamageCalculator::GetDefaultDefensePower(
int* const result_value) const
{
int defense_power = 0;
//
; if (base_attack_type_ == eATTACK_TYPE_MELEE) {
defense_power = defender_attr_.GetBaseMeleeDefPower();
}
else if (base_attack_type_ == eATTACK_TYPE_RANGE) {
defense_power = defender_attr_.GetBaseRangeDefPower();
}
if (int option_value = defender_attr_.GetOptionPhysicalDefPower()) {
defense_power += option_value;
}
// removed added defense power based on attack type
if (int defense_increase_ratio = defender_attr_.GetDefenseIncRate())
{
defense_power = defense_power +
static_cast<int>(defense_power * defense_increase_ratio / 100.0f);
};
// NOTE: add a missing attribute about the reduce target defense rate against physical attack
if (int target_defense_decrease_ratio = attacker_attr_.GetReducePhysicalTargetDefenseRatio()) {
defense_power = defense_power -
static_cast<int>(defense_power * target_defense_decrease_ratio / 100.0f);
}
// f101229.1L, forced rule
if (defense_power < 0) {
defense_power = 0;
};
if (result_value) {
*result_value = defense_power;
}
}
//==================================================================================================
// return input damage if a damage has doubled by roulette.
int ns_formula::ep2::CalcDoubleDamage(Character* attacker, int damage)
{
const Attributes& attacker_attr = attacker->GetAttr();
if (int rate_of_double_damage = attacker_attr.GetDoubleDamageRatio()) {
// makes to positive value and makes to simple numerator
int fail_bound = ((RandomNumberGenerator::GetRandomNumber() >> 1) & 0xFFFFLL);
; fail_bound = fail_bound % 100;
if (fail_bound < rate_of_double_damage) {
return damage;
}
}
return 0;
}
// 상태적용 성공여부, _NA_001231_20081216_ADD_OPTION_KIND
bool ns_formula::ep2::IsStatusHit(
Character* const attacker,
Character* const defender,
const int rate_of_success,
const int state_id,
const Skill* const skill)
{
// TODO: do reorganize
return ep1::IsStatusHit(attacker, defender, rate_of_success, state_id, skill);
}
//==================================================================================================
// base_attack_power_min : input/output
// base_attack_power_max : input/output
void ns_formula::ep2::DamageCalculator::CalculateSharedDamageOption(
int* const base_attack_power_min, int* const base_attack_power_max) const
{
int attack_power_min = *base_attack_power_min;
int attack_power_max = *base_attack_power_max;
//----------------------------------------------------------------------------------------------
// #2-2: Ether weapon option
// accept (+) ranges
// CHANGES: f110601.4L, the ether weapons using ether bullets have various bullets options.
// only the bullets option has the ether weapon damage increase ratio and
// these only apply when player equipped and used bullets.
// the below attribute selection is designed on the script rule
// that is one bullet has only one of the two.
int ether_weapon_damage_ratio = attacker_attr_.GetEhterDamageRatio(); // custom option (PvP+PvE)
if (ether_weapon_damage_ratio == 0 && is_pvp_ == false) {
ether_weapon_damage_ratio = attacker_attr_.GetEhterPvEDamageRatio(); // new option (PvE)
};
if (ether_weapon_damage_ratio > 0)
{
// Min = min_damage * ( 95% + ether_ratio);
// Max = max_damage * (105% + ether_ratio);
attack_power_min = static_cast<int>(attack_power_min *
( 95 + ether_weapon_damage_ratio) / 100.0f);
attack_power_max = static_cast<int>(attack_power_max *
(105 + ether_weapon_damage_ratio) / 100.0f);
}
//
*base_attack_power_min = attack_power_min;
*base_attack_power_max = attack_power_max;
}
#ifdef _NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
//==================================================================================================
// skill_attack_power : input/output
void ns_formula::ep2::DamageCalculator::CalculateSharedDamageOption(int* const base_skill_attack_power) const
{
int skill_attack_power = *base_skill_attack_power;
//----------------------------------------------------------------------------------------------
// #2-2: Ether weapon option
// accept (+) ranges
// CHANGES: f110601.4L, the ether weapons using ether bullets have various bullets options.
// only the bullets option has the ether weapon damage increase ratio and
// these only apply when player equipped and used bullets.
// the below attribute selection is designed on the script rule
// that is one bullet has only one of the two.
int ether_weapon_damage_ratio = attacker_attr_.GetEhterDamageRatio(); // custom option (PvP+PvE)
if (ether_weapon_damage_ratio == 0 && is_pvp_ == false) {
ether_weapon_damage_ratio = attacker_attr_.GetEhterPvEDamageRatio(); // new option (PvE)
};
if (ether_weapon_damage_ratio > 0)
{
// Min = min_damage * ( 95% + ether_ratio);
// Max = max_damage * (105% + ether_ratio);
skill_attack_power = static_cast<int>(skill_attack_power *
( 100 + ether_weapon_damage_ratio) / 100.0f);
}
*base_skill_attack_power = skill_attack_power;
}
#endif
//==================================================================================================
// base_attack_power_min : input/output
// base_attack_power_max : input/output
void ns_formula::ep2::DamageCalculator::CalculateDefaultDamageOption(
int* const base_attack_power_min, int* const base_attack_power_max) const
{
int attack_power_min = *base_attack_power_min;
int attack_power_max = *base_attack_power_max;
//----------------------------------------------------------------------------------------------
// #2: apply options = { GetRatioIncrease(Min/Max)Damage, Ether weapon }
// fixed the usage about 'GetRatioIncrease(Min/Max)Damage'
// -> base_attack_power_XXX = base_attack_power_XXX + (base_attack_power_XXX * ratio)
// accept (+/-) ranges
if (int increase_min_damage_ratio = attacker_attr_.GetRatioIncreaseMinDamage()) {
attack_power_min = attack_power_min +
static_cast<int>(attack_power_min * increase_min_damage_ratio / 100.0f);
}
if (int increase_min_damage_item = attacker_attr_.GetItemIncreaseMinDamage()) {
attack_power_min += increase_min_damage_item;
};
//
if (int increase_max_damage_ratio = attacker_attr_.GetRatioIncreaseMaxDamage()) {
attack_power_max = attack_power_max +
static_cast<int>(attack_power_max * increase_max_damage_ratio / 100.0f);
}
if (int increase_max_damage_item = attacker_attr_.GetItemIncreaseMaxDamage()) {
attack_power_max += increase_max_damage_item;
};
#ifdef _NA_007330_20140620_GUILD_SYSTEM_EXTENSION
//----------------------------------------------------------------------------------------------
// 공성 오브젝트 데미지 증가 옵션
if (defender_->IsEqualObjectKind(MAPNPC_OBJECT))
{
BYTE npc_grade = (static_cast<NPC*>(defender_))->GetBaseInfo()->m_byGrade;
if(npc_grade == eNPC_DOMINATION_MAPOBJECT_NPC || npc_grade == eNPC_CRYSTAL_WARP)
{
attack_power_min = attacker_->GetAttr().GetValueAppliedAttr(attack_power_min,\
eATTR_DOMINATION_MAPOBJECT_DAMAGE_RATIO);
attack_power_max = attacker_->GetAttr().GetValueAppliedAttr(attack_power_max,\
eATTR_DOMINATION_MAPOBJECT_DAMAGE_RATIO);
}
}
#endif // _NA_007330_20140620_GUILD_SYSTEM_EXTENSION
//
*base_attack_power_min = attack_power_min;
*base_attack_power_max = attack_power_max;
//
CalculateSharedDamageOption(base_attack_power_min, base_attack_power_max);
}
// skill_attack_power : input/output
void ns_formula::ep2::DamageCalculator::CalculateSkillDamage(
int* const skill_attack_power, const float skill_added_ratio_damage) const
{
int skill_power = *skill_attack_power;
// formula
// skill_attack_power = skill attribute
// skill_added_ratio_damage = skill attribute + character.GetSkillPercentDamage
// ratio_of_intelligence_to_skill_attack_power
// BaseSkillAttackPower = _____________________________________________ * intelligence
// 100
// SkillAttackPower = BaseSkillAttackPower + other options of a character
// [SkillDamage] = (skill_attack_power + SkillAttackPower) *
// (1 + skill_added_ratio_damage) <- '%'
skill_power += attacker_attr_.GetSkillAttackPower();
skill_power = util::LL_Round(skill_power * (1.0f + skill_added_ratio_damage));
*skill_attack_power = skill_power;
#ifdef _NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
CalculateSharedDamageOption(skill_attack_power);
#endif // _NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
}
//==================================================================================================
//
void ns_formula::ep2::DamageCalculator::CalculateElementDamage(
int* const attributes_attack_power,
eATTACK_TYPE attack_type,
const int skill_attack_power,
const float skill_added_ratio_damage) const
{
Attributes::Elements attacker_power;
Attributes::Elements rate_of_defense_power_reduction_of_target;
Attributes::Elements damage_reduction_of_defender;
//
attacker_attr_.GetDefaultMagicalAttributes(
&attacker_power,
&rate_of_defense_power_reduction_of_target,
NULL);
//
defender_attr_.GetDefaultMagicalAttributes(
NULL,
NULL,
&damage_reduction_of_defender);
// renewal magic skill, magic skill = elemental skill
const bool is_elemental_skill = attack_type >= eATTACK_TYPE_WATER &&
attack_type <= eATTACK_TYPE_DARKNESS;
if (is_elemental_skill)
{
// magic skill = elemental skill
Attributes::Elements::eElement element = \
static_cast<Attributes::Elements::eElement>(attack_type - eATTACK_TYPE_WATER);
int power = attacker_power.values[element];
; power += skill_attack_power;
; power = static_cast<int>(power * (1.0f + skill_added_ratio_damage));
attacker_power.values[element] = power;
}
if (int attack_increase_ratio = attacker_attr_.GetAttackIncRate())
{
for (int* it = attacker_power.values;
it != &attacker_power.values[_countof(attacker_power.values)];
++it)
{
if (*it) {
(*it) = (*it) + static_cast<int>((*it) * attack_increase_ratio / 100.0f);
}
};
};
const bool is_player = this->defender_->IsEqualObjectKind(PLAYER_OBJECT);
// CHANGES: f110825.2L, fixed a missing calcuation modification with boundary
;{
int defense_increase_ratio = defender_attr_.GetDefenseIncRate();
for (int* it = damage_reduction_of_defender.values;
it != &damage_reduction_of_defender.values[_countof(damage_reduction_of_defender.values)];
++it)
{
// NOTE: f110118.4L, modified value under (MAX:80%) by game rule.
int value = (*it) + defense_increase_ratio;
if (is_player && value > 80) {
value = 80;
};
(*it) = value;
};
}
if (const int* reduction_it = rate_of_defense_power_reduction_of_target.values)
{
for (int* it = damage_reduction_of_defender.values;
it != &damage_reduction_of_defender.values[_countof(damage_reduction_of_defender.values)];
++it, ++reduction_it)
{
// CHANGES: f110825.2L, fixed a missing calcuation modification with boundary
//if ((*reduction_it) == 0) {
// continue;
//};
int reduction_ratio = (*reduction_it);
int decrease_damage_ratio = *it - reduction_ratio;
// ranges=(-100%, +XXX%)
if (decrease_damage_ratio < -100) {
decrease_damage_ratio = -100;
};
(*it) = decrease_damage_ratio;
};
};
//
int sum_of_element_damage = 0;
// Atk:100 - (Atk * reduction_ratio:-100%) = 100 - (-100) = 200
if (is_elemental_skill)
{
Attributes::Elements::eElement element = \
static_cast<Attributes::Elements::eElement>(attack_type - eATTACK_TYPE_WATER);
double element_damage = static_cast<double>(attacker_power.values[element]);
double defense_power =
(element_damage * damage_reduction_of_defender.values[element] / 100.0f);
element_damage -= defense_power;
if (element_damage > 0.0f) {
sum_of_element_damage += util::LL_Round(element_damage);
}
}
else
{
const int* reduction_it = damage_reduction_of_defender.values;
for (int* it = attacker_power.values;
it != &attacker_power.values[_countof(attacker_power.values)];
++it, ++reduction_it)
{
double element_damage = static_cast<double>(*it);
double defense_power = (element_damage * (*reduction_it) / 100.0f);
element_damage -= defense_power;
if (element_damage > 0.0f) {
sum_of_element_damage += util::LL_Round(element_damage);
}
}
};
if (sum_of_element_damage < 0) {
assert(!"expected to impossible");
sum_of_element_damage = 0;
}
// result
*attributes_attack_power = sum_of_element_damage;
#ifdef _NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
CalculateSharedDamageOption(attributes_attack_power);
#endif // _NA_006131_20120924_ETHER_DAMAGE_RULE_MODIFY
}
//==================================================================================================
//
int ns_formula::ep2::DamageCalculator::CalculateLastAdjustmentDamage(
int confirmed_damage, int confirmed_defense,
double damage_decrease_ratio) const
{
#if SUN_CODE_BACKUP
// The Episode I damage deduction formula
float adjustment_ratio = static_cast<float>(80 + defender_level_ * 6 - confirmed_defense) /
static_cast<float>(750 + defender_level_ * 2) +
damage_decrease_ratio +
(attacker_level_ - defender_level_) * 0.03f;
// The Episode II damage deduction formula
// Backup:
// (AtkLv * C1 + (AtkLv - DefLv) * C2 + INT(TotalDamage * C3))
// ___________________________________________________________
// (TotalDefense + 1)
// C1 : kConstants.attaker_level_weight (default = 3)
// C2 : kConstants.diff_level_weight (default = 4)
//double numerator =
// ((attacker_level_ * kConstants.attacker_level_weight) +
// ((attacker_level_ - defender_level_) * kConstants.diff_level_weight) +
// util::LL_Round(confirmed_damage * damage_decrease_ratio));
#endif
// The Episode II damage deduction formula
// 2nd renewal:
// TotalDamage * C3
// __________________
// (TotalDefense + 1)
//
// C3 : kConstants.damage_deduction_pvp_weight (default = 0.1),
// kConstants.damage_deduction_pve_weight (default = 0.6)
double numerator = confirmed_damage * damage_decrease_ratio;
double adjustment_ratio = numerator / static_cast<double>(confirmed_defense + 1);
//
const float min_value_damage = 0.01f; // #4240 데미지 차감율 관련 공식 변경 - arycoat 2012.01.31
if (adjustment_ratio < min_value_damage) {
adjustment_ratio = min_value_damage;
}
if (adjustment_ratio > 1.0f) {
adjustment_ratio = 1.0f;
};
//
int adjustment_damage = util::LL_Round(confirmed_damage * adjustment_ratio);
return adjustment_damage;
}
//==================================================================================================
// returns
// critical_damage = adjustment_damage + additional critical damage
int ns_formula::ep2::DamageCalculator::CalculateCriticalDamage(
bool handle_to_max_damage,
const int adjustment_damage, int rate_of_critical_bonus) const
{
if (adjustment_damage <= 0) {
return 0;
};
// rate_of_critical_bonus, critical_bonus_damage, critical_bonus_ratio_damage
rate_of_critical_bonus += attacker_attr_.GetCriticalRatioChange();
rate_of_critical_bonus += defender_attr_.GetEnemyCriticalRatioChange(); // 피격 시 상대의 크리티컬 확률 감소
if (rate_of_critical_bonus < 0) {
rate_of_critical_bonus = 0;
}
if (IsApplyCriticalDamage(rate_of_critical_bonus) == false) {
return 0;
}
// formula:
// EP1 = [1.2, 1.7]
// EP2 = [1.5, 2.0]
// critical_damage = adjustment_damage * 1.5f + ([0, adjustment_damage] * 0.5f)
// max_critical_damage = adjustment_damage * 2.0f
// (1.5f + 0.5f)
assert(adjustment_damage >= 0);
int calc_slot = adjustment_damage;
if (handle_to_max_damage)
{
calc_slot <<= 1;
}
else
{
int default_damage = (adjustment_damage >> 1);
int random_damage = RandomNumberGenerator::GetRandomNumberRange(\
0, default_damage);
calc_slot = calc_slot + default_damage + random_damage;
};
#ifdef _NA_006937_20131030_ABILITY_AND_STATE_CHANGE_CRITICAL
//크리티컬 데미지 증감
AttrValue attacker_critical_damage = attacker_attr_[eATTR_CRITICAL_DAMAGE_CHANGE];
attacker_critical_damage[eATTR_KIND_BASE] = calc_slot;
#ifdef _NA_008441_20150819_CRITICAL_DAMAGE_CHANGE_FROM_LEVEL_DIFFERENCCE
const int max_decrease = 10;
int critical_damage_change_decrease = defender_level_ - attacker_level_;
if (critical_damage_change_decrease > 0)
{
if (critical_damage_change_decrease > max_decrease)
{
critical_damage_change_decrease = max_decrease;
}
attacker_critical_damage[eATTR_KIND_ITEM_RATIO] -= critical_damage_change_decrease;
}
#endif // _NA_008441_20150819_CRITICAL_DAMAGE_CHANGE_FROM_LEVEL_DIFFERENCCE
attacker_critical_damage.Update();
//피격시 상대 크리티컬 데미지 증감
AttrValue defender_critical_damage = defender_attr_[eATTR_ENEMY_CRITICAL_DAMAGE_CHANGE];
defender_critical_damage[eATTR_KIND_BASE] = attacker_critical_damage[eATTR_KIND_CALC];
defender_critical_damage.Update();
calc_slot = defender_critical_damage[eATTR_KIND_CALC];
#else
float critical_bonus_ratio_damage = attacker_attr_.GetRatioCriticalDamageBonus() / 100.0f;
calc_slot = static_cast<int>(calc_slot * (1.0f + critical_bonus_ratio_damage));
int critical_bonus_damage = 0;
critical_bonus_damage = attacker_attr_.GetCriticalDamageBonus();
if (critical_bonus_damage > 0) {
calc_slot += critical_bonus_damage;
}
#endif //_NA_006937_20131030_ABILITY_AND_STATE_CHANGE_CRITICAL
return calc_slot;
}
#ifdef _NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
int ns_formula::ep2::DamageCalculator::CalculateAdditionFPDamage( Character* skill_user, int damage ) const
{
if (skill_user->IsEqualObjectKind(PLAYER_OBJECT) == false ) {
return damage;
}
if (damage <= 0) {
return damage;
}
Player* player = reinterpret_cast<Player*>(skill_user);
StatusManager* status_manager = player->GetStatusManager();
//피드온레이지 상태 찾는다
BaseStatus* status = status_manager->FindStatus(eCHAR_STATE_FP_POWER_INCREASE);
float overlap_count = 0;
float additional_damage_ratio = 0;
if (status != NULL)
{
overlap_count = status->GetStatusOverlapCount();
AbilityStatus* fp_buff_status = reinterpret_cast<AbilityStatus*>(status);
additional_damage_ratio = fp_buff_status->GetAbilityInfo()->m_iParam[1];
}
// 버림
float floor_fp =floor(player->GetFP());
if (floor_fp == 0) {
return damage;
}
float token = (1.0f+(floor_fp/25.0f)) + ((additional_damage_ratio / 100.0f)*overlap_count);
float cur_damage = (float)damage;
return (int)( cur_damage * token);
}
int ep2::DamageCalculator::CalculateIncreaseDamageRatio( Character* skill_user, int damage ) const
{
int attr_value = skill_user->GetAttr().GetIncreaseDamageRatio();
float increase_ratio = 1.0f + ((float)attr_value / 100.0f);
int result_damage = (int)((float)damage * increase_ratio);
return result_damage;
}
#endif //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
//==================================================================================================
int ns_formula::ep2::DamageCalculator::CalculateTotalDamage(
const bool handle_critical_calculation,
const uint16_t rate_of_critical_bonus,
uint8_t* const result_of_skill_effect_flag,
//
const int input_skill_attack_power, // + value
const float input_skill_added_ratio_damage) const // * (100% + ratio value)
{
const bool is_apply_skill_damage = \
(handle_to_fix_skill_damage_ || input_skill_attack_power != 0);
// renewal magic skill, magic skill = elemental skill
const bool is_elemental_skill = skill_attack_type_ >= eATTACK_TYPE_WATER &&
skill_attack_type_ <= eATTACK_TYPE_DARKNESS;
//----------------------------------------------------------------------------------------------
// calculate the probability of it if this process is normal attack
if (is_apply_skill_damage == false && CanPassNextStepOnNormalDamage() == false) {
return 0; // blocking
}
//----------------------------------------------------------------------------------------------
// preparation
int base_attack_power_max = 0;
int base_attack_power_min = 0;
int base_defense_power = 0;
int skill_attack_power = input_skill_attack_power;
//----------------------------------------------------------------------------------------------
// #1: acquire default attack/defense power
this->GetDefaultAttackPower(&base_attack_power_min, &base_attack_power_max);
// NOTE: this formula is unused currently. (style skill only)
// probability of the decrease defender_ defense power
//--- float bypass_defense_ratio = IsBypassDeffence(attacker_attr_) ? 1.0f : 0.0f;
//
this->GetDefaultDefensePower(&base_defense_power);
//----------------------------------------------------------------------------------------------
// #2: apply options = { GetRatioIncrease(Min/Max)Damage, Ether weapon }
// fixed the usage about 'GetRatioIncrease(Min/Max)Damage'
// -> base_attack_power_XXX = base_attack_power_XXX + (base_attack_power_XXX * ratio)
this->CalculateDefaultDamageOption(&base_attack_power_min, &base_attack_power_max);
//----------------------------------------------------------------------------------------------
float sum_of_skill_added_ratio_damage = 0.0f;
// #3: calculate skills
if (is_apply_skill_damage) {
sum_of_skill_added_ratio_damage = input_skill_added_ratio_damage +
static_cast<float>(attacker_attr_.GetSkillPercentDamage() / 100.0f);
this->CalculateSkillDamage(&skill_attack_power, sum_of_skill_added_ratio_damage);
}
//----------------------------------------------------------------------------------------------
// #4: calculate attributes
// TODO: list of attributes = { Water, Fire, Wind, Earth, Darkness }
int element_attack_power = 0;
this->CalculateElementDamage(&element_attack_power,
(is_elemental_skill ? skill_attack_type_ : base_attack_type_),
skill_attack_power, sum_of_skill_added_ratio_damage);
//
//----------------------------------------------------------------------------------------------
// #5 make to confirmed values
// #5.1: recalculate damage using defender's damage reduction attributes
// (base_attack_power + skill_attack_power) * damage_reduction_ratio
int confirmed_damage_max = base_attack_power_max + skill_attack_power;
int confirmed_damage_min = base_attack_power_min + skill_attack_power;
if (int physical_damage_reduction = defender_attr_.GetReducePhysicalDamageRatio()) {
if (physical_damage_reduction > 99) {
physical_damage_reduction = 99;
}
if (confirmed_damage_max) {
confirmed_damage_max = confirmed_damage_max -
static_cast<int>(confirmed_damage_max * physical_damage_reduction / 100.0f);
}
if (confirmed_damage_min) {
confirmed_damage_min = confirmed_damage_min -
static_cast<int>(confirmed_damage_min * physical_damage_reduction / 100.0f);
}
};
// #5.2: make to confirmed values
//; confirmed_damage_max = confirmed_damage_max + element_attack_power;
//; confirmed_damage_min = confirmed_damage_min + element_attack_power;
int confirmed_defense_power = base_defense_power;
// fixing
if (confirmed_damage_max < 0) {
confirmed_damage_max = 0;
}
if (confirmed_damage_min < 0) {
confirmed_damage_min = 0;
};
if (confirmed_damage_max < confirmed_damage_min) {
confirmed_damage_max = confirmed_damage_min;
}
// ... thinking 'value < 0'
//
//----------------------------------------------------------------------------------------------
// #6: calculate final damage that is considered PvP option and flag options
//kConstants.damage_deduction_pvp_weight = 0.10f;
//kConstants.damage_deduction_pve_weight = 0.70f;
const StatusField& status_field = attacker_->GetStatusManager()->Status;
const bool handle_to_max_damage = \
((status_field.FIELD & status_field.ENABLE_GM_MAX_DAMAGE) != 0);
///
int confirmed_damage = 0;
if (false) {
}
else if (handle_to_max_damage) {
confirmed_damage = confirmed_damage_max;
}
else if (handle_to_fix_damage_ || handle_to_fix_skill_damage_) { // CHANGES: f110215.3L
confirmed_damage = ((confirmed_damage_max + confirmed_damage_min) >> 1);
}
else {
#ifdef _NA_007330_20140620_GUILD_SYSTEM_EXTENSION
// 최대 데미지 발생확률 어트리뷰트 적용
int max_damage_ratio = attacker_->GetAttr().GetMaxDamageRatio();
(max_damage_ratio > 0 && random_percent(max_damage_ratio)) \
? confirmed_damage = confirmed_damage_max \
: confirmed_damage = DamageMinMaxRandom(int, confirmed_damage_min, confirmed_damage_max);
#else // _NA_007330_20140620_GUILD_SYSTEM_EXTENSION
confirmed_damage = DamageMinMaxRandom(int, confirmed_damage_min, confirmed_damage_max);
#endif // _NA_007330_20140620_GUILD_SYSTEM_EXTENSION
};
int adjustment_damage = this->CalculateLastAdjustmentDamage(
confirmed_damage, confirmed_defense_power,
is_pvp_ ? kConstants.damage_deduction_pvp_weight : kConstants.damage_deduction_pve_weight);
// #6.2: make to confirmed values
#ifdef _NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
adjustment_damage = this->CalculateAdditionFPDamage(attacker_, adjustment_damage);
adjustment_damage = this->CalculateIncreaseDamageRatio(attacker_, adjustment_damage);
#endif //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
; adjustment_damage = adjustment_damage + element_attack_power;
// #6.3: guarantee minimum damage
if (adjustment_damage <= (confirmed_damage_min / 20))
{
int lower_bound_value = confirmed_damage_min / 40 + 1;
adjustment_damage = lower_bound_value + DamageMinMaxRandom(int, 0, lower_bound_value);
};
//----------------------------------------------------------------------------------------------
// #7: calculate additional damage after final calculated damage
// #7-1: critical damage
if (handle_critical_calculation)
{
int critical_damage = this->CalculateCriticalDamage(
handle_to_max_damage, adjustment_damage, rate_of_critical_bonus);
if (result_of_skill_effect_flag)
{
if (critical_damage != 0) {
(*result_of_skill_effect_flag) |= SKILL_EFFECT_CRITICAL;
}
else {
(*result_of_skill_effect_flag) &= ~SKILL_EFFECT_CRITICAL;
}
}
if (critical_damage != 0) {
adjustment_damage = critical_damage;
}
};
// #7-2: add damage
if (int add_damage = attacker_attr_.GetAddDamage()) {
adjustment_damage += add_damage;
};
// #8: limit maximum damage by upper boundary
if (adjustment_damage > DAMAGETYPE_MAX) {
adjustment_damage = DAMAGETYPE_MAX;
};
return adjustment_damage;
}
#ifdef _NA001605_EP2_ATTRIBUTES_RENEWAL_
// damage formula specification (version:EP2.01)
// tree diagram
//--------------------------------------------------------
// [Normal] [Skill]
// | |----------------|
// [Normal] [Normal Skill] [Magic Skill]
//--------------------------------------------------------
// [Normal] - [Player] with default style attack
// - [Monster] simple attack
// [Normal Skill] - no elemental option
// [Magic Skill] - with elemental option
//
//==================================================================================================
// NOTE: calculate (simulate) physical normal damage
// rate_of_critical_bonus : increase damage if the result is succeed
int ep2::CalcNormalDamage(
Character* attacker, Character* defender,
eATTACK_TYPE attack_type,
const bool handle_critical_calculation,
const uint16_t rate_of_critical_bonus,
uint8_t* const result_of_skill_effect_flag)
{
ep2::DamageCalculator calculator(
attacker, defender, attack_type, eATTACK_TYPE_INVALID, false, false);
int total_damage = calculator.CalculateTotalDamage(
handle_critical_calculation, rate_of_critical_bonus, result_of_skill_effect_flag,
0, 0);
return total_damage;
}
// NOTE: calculate (simulate) physical | elemental skill damage
// rate_of_critical_bonus : increase damage if the result is succeed
int ep2::CalcSkillDamage(
Character* attacker, Character* defender,
eATTACK_TYPE attack_type,
const bool handle_critical_calculation,
const uint16_t rate_of_critical_bonus,
uint8_t* const result_of_skill_effect_flag,
//
eATTACK_TYPE skill_attack_type,
const int skill_attack_power, // + value
const float skill_added_ratio_damage) // * (100% + ratio value)
{
ep2::DamageCalculator calculator(
attacker, defender, attack_type, skill_attack_type, false, false);
int total_damage = calculator.CalculateTotalDamage(
handle_critical_calculation, rate_of_critical_bonus, result_of_skill_effect_flag,
skill_attack_power, skill_added_ratio_damage);
return total_damage;
}
// NOTE: f110215.3L, calculate (simulate) fixed physical | elemental skill damage
// rate_of_critical_bonus : increase damage if the result is succeed
int ep2::CalcFixedSkillDamage(
Character* attacker, Character* target,
eATTACK_TYPE attack_type,
const bool handle_critical_calculation, const uint16_t rate_of_critical_bonus,
uint8_t* const result_of_skill_effect_flag,
//
eATTACK_TYPE skill_attack_type,
const int skill_attack_power, // + value
const float skill_added_ratio_damage) // * (100% + ratio value)
{
ep2::DamageCalculator calculator(
attacker, target, attack_type, skill_attack_type, false, true);
int total_damage = calculator.CalculateTotalDamage(
handle_critical_calculation, rate_of_critical_bonus, result_of_skill_effect_flag,
skill_attack_power, skill_added_ratio_damage);
return total_damage;
}
#endif //_NA001605_EP2_ATTRIBUTES_RENEWAL_
//==================================================================================================
// 내구도 감소비율
float ep2::GetDuraDecRatio(eZONE_STATE zone_state, eCOMPETITION_MODE_TYPE eCompetitionMode)
{
return ep1::GetDuraDecRatio(zone_state, eCompetitionMode);
}
//_NA_005026_20120527_CHAOS_ZONE_BATTLE_GROUND
float ep2::GetDuraDecRatio(eZONE_STATE zone_state, ChaosZoneType chaos_zone_type)
{
return ep1::GetDuraDecRatio(zone_state, chaos_zone_type);
}
//
EXPTYPE ep2::GetKillExpOfParty(
BYTE byRoomBonusType, DWORD partymemberNum,
LEVELTYPE partyTotalLV, LEVELTYPE attackerLV, LEVELTYPE targetLV,
float fNPCGradeExpRatio,
DWORD attackDamage,
DWORD targetMaxHP,
eZONETYPE ZoneType,
Player* player)
{
return ep1::GetKillExpOfParty(byRoomBonusType, partymemberNum,
partyTotalLV, attackerLV, targetLV,
fNPCGradeExpRatio,
attackDamage,
targetMaxHP,
ZoneType,
player);
}
// HP,MP 회복량(플레이어)
int ep2::CalcHPRecover(eCHAR_TYPE char_type, DWORD maxHP, BYTE byCharState, LEVELTYPE Level)
{
return ep1::CalcHPRecover(char_type, maxHP, byCharState, Level);
}
int ep2::CalcSDRecover(
eCHAR_TYPE char_type, eCHAR_CONDITION byCharState,
BYTE byCharAction, LEVELTYPE level)
{
return ep1::CalcSDRecover(char_type, byCharState, byCharAction, level);
}
int ep2::CalcMPRecover(eCHAR_TYPE char_type, DWORD spirit, BYTE byCharState)
{
return ep1::CalcMPRecover(char_type, spirit, byCharState);
}
DWORD ep2::CalcMPRecoverByAttack(
eCHAR_TYPE char_type, DWORD spirit,
DWORD skillRecoverPercent, DWORD skillRequireMP) // 공격할때 회복량
{
return ep1::CalcMPRecoverByAttack(char_type, spirit, skillRecoverPercent, skillRequireMP);
}
DWORD ep2::CalcMPRecoverByAttacked(
eCHAR_TYPE char_type, DWORD spirit, DAMAGETYPE damage) // 타격 받았을떄 회복량
{
return ep1::CalcMPRecoverByAttacked(char_type, spirit, damage);
}
// HP, MP 회복량(몬스터)
int ep2::CalcNPCHPRecover(DWORD maxHP, DWORD recoverRatio)
{
return ep1::CalcNPCHPRecover(maxHP, recoverRatio);
}
int ep2::CalcNPCMPRecover(DWORD maxMP, DWORD recoverRatio)
{
return ep1::CalcNPCMPRecover(maxMP, recoverRatio);
}
int ep2::CalcNPCSDRecover(DWORD maxSD)
{
return ep1::CalcNPCSDRecover(maxSD);
}
#ifdef _NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
PLAYERFP CalcFpRecover( eCHAR_TYPE char_type )
{
PLAYERFP recover_value = 0;
switch (char_type)
{
case eCHAR_WITCHBLADE:
recover_value = -0.5f;
break;
default:
break;
}
return recover_value;
}
PLAYERFP CalcFPRecoverByAttack( eCHAR_TYPE char_type )
{
PLAYERFP recover_value = 0;
switch (char_type)
{
case eCHAR_WITCHBLADE:
recover_value = 1.0f;
break;
default:
break;
}
return recover_value;
}
#endif //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
// GM utility
//double ns_formula::ep2::DamageCalculator::kConstants.attaker_level_weight = 3.0f;
//double ns_formula::ep2::DamageCalculator::kConstants.diff_level_weight = 4.0f;
//double ns_formula::ep2::DamageCalculator::kConstants.damage_deduction_pvp_weight = 0.1f;
//double ns_formula::ep2::DamageCalculator::kConstants.damage_deduction_pve_weight = 0.6f;
bool ep2::ModifyDamageBalanceConstant(const DamageBalanceConstant& modifying_constants)
{
DamageBalanceConstant modified = modifying_constants;
// > 0.0f
if (modified.attacker_level_weight < 0.0f) {
modified.attacker_level_weight = 3.0f;
}
if (modified.diff_level_weight < 0.0f) {
modified.diff_level_weight = 4.0f;
}
if (modified.damage_deduction_pvp_weight < 0.0f) {
modified.damage_deduction_pvp_weight = 0.1f;
}
if (modified.damage_deduction_pve_weight < 0.0f) {
modified.damage_deduction_pve_weight = 0.6f;
}
ns_formula::ep2::DamageCalculator::kConstants = modified;
return true;
}
bool ep2::GetCurrentDamageBalanceConstant(DamageBalanceConstant* const current_constants)
{
*current_constants = ns_formula::ep2::DamageCalculator::kConstants;
return true;
}
//==================================================================================================
// CHANGES: f110215.3L, f110217.5L, changes periodic damage formula
// (Base + Element damage) * [apply ratio (iOption2)] + [skill attack power]
int DamagePerCycleCalculator::Simulate(
Character* attacker, Character* target,
eATTACK_TYPE /*attack_type*/,
eATTACK_TYPE skill_attack_type,
const int apply_added_base_damage_ratio, // INPUT : permilage (ability_info->m_iOption2)
const int skill_attack_power, // + value
const float skill_added_ratio_damage) // * (100% + ratio value)
{
// CHANGES: f110215.3L, f110217.5L, changes periodic damage formula
// (Base + Element damage) * [apply ratio (iOption2)] + [skill attack power]
uint8_t dummy_effects = 0;
const eATTACK_TYPE base_attack_type = attacker->GetWeaponBaseAttType();
int base_skill_damage = ep2::CalcFixedSkillDamage(
attacker, target, base_attack_type, false, 0, &dummy_effects, skill_attack_type, 0, 0);
int result_skill_damage = ep2::CalcFixedSkillDamage(
attacker,
target,
base_attack_type,
false,
0,
&dummy_effects,
skill_attack_type,
skill_attack_power,
skill_added_ratio_damage
);
const int max_temp = max(result_skill_damage, base_skill_damage);
base_skill_damage = min(result_skill_damage, base_skill_damage);
result_skill_damage = max_temp;
result_skill_damage -= base_skill_damage;
const float apply_added_base_damage = (apply_added_base_damage_ratio / 1000.0f);
base_skill_damage = util::LL_Round(base_skill_damage * apply_added_base_damage);
result_skill_damage += base_skill_damage;
return result_skill_damage;
}
}}; //end of namespace 'ns_formula::ep2'
#endif //_NA001605_EP2_ATTRIBUTES_RENEWAL_