rathena/src/map/battle.cpp
Playtester 0ef5fa905c
Fixed Pre-Re SoftDEF Formula (#6766)
* Fixed Pre-Re SoftDEF Formula
* Fixes #6648

Note: This does not fix the order of processing which is also wrong, but at least the damage is official now in 1vs1 combat when if no item bonuses or status changes are involved that directly impact DEF.
2022-03-31 18:09:53 +02:00

10397 lines
395 KiB
C++

// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
#include "battle.hpp"
#include <math.h>
#include <stdlib.h>
#include "../common/cbasetypes.hpp"
#include "../common/ers.hpp"
#include "../common/malloc.hpp"
#include "../common/nullpo.hpp"
#include "../common/random.hpp"
#include "../common/showmsg.hpp"
#include "../common/socket.hpp"
#include "../common/strlib.hpp"
#include "../common/timer.hpp"
#include "../common/utils.hpp"
#include "atcommand.hpp"
#include "battleground.hpp"
#include "chrif.hpp"
#include "clif.hpp"
#include "elemental.hpp"
#include "guild.hpp"
#include "homunculus.hpp"
#include "log.hpp"
#include "map.hpp"
#include "mercenary.hpp"
#include "mob.hpp"
#include "party.hpp"
#include "path.hpp"
#include "pc.hpp"
#include "pc_groups.hpp"
#include "pet.hpp"
struct Battle_Config battle_config;
static struct eri *delay_damage_ers; //For battle delay damage structures.
/**
* Returns the current/list skill used by the bl
* @param bl
* @return skill_id
*/
uint16 battle_getcurrentskill(struct block_list *bl)
{
struct unit_data *ud;
if( bl->type == BL_SKILL ) {
struct skill_unit *su = (struct skill_unit*)bl;
return (su && su->group?su->group->skill_id:0);
}
ud = unit_bl2ud(bl);
return (ud?ud->skill_id:0);
}
/**
* Get random targeting enemy
* @param bl
* @param ap
* @return Found target (1) or not found (0)
*/
static int battle_gettargeted_sub(struct block_list *bl, va_list ap)
{
struct block_list **bl_list;
struct unit_data *ud;
int target_id;
int *c;
bl_list = va_arg(ap, struct block_list **);
c = va_arg(ap, int *);
target_id = va_arg(ap, int);
if (bl->id == target_id)
return 0;
if (*c >= 24)
return 0;
if ( !(ud = unit_bl2ud(bl)) )
return 0;
if (ud->target == target_id || ud->skilltarget == target_id) {
bl_list[(*c)++] = bl;
return 1;
}
return 0;
}
/**
* Returns list of targets
* @param target
* @return Target list
*/
struct block_list* battle_gettargeted(struct block_list *target)
{
struct block_list *bl_list[24];
int c = 0;
nullpo_retr(NULL, target);
memset(bl_list, 0, sizeof(bl_list));
map_foreachinallrange(battle_gettargeted_sub, target, AREA_SIZE, BL_CHAR, bl_list, &c, target->id);
if ( c == 0 )
return NULL;
if( c > 24 )
c = 24;
return bl_list[rnd()%c];
}
/**
* Returns the ID of the current targeted character of the passed bl
* @param bl
* @return Target Unit ID
* @author [Skotlex]
*/
int battle_gettarget(struct block_list* bl)
{
switch (bl->type) {
case BL_PC: return ((struct map_session_data*)bl)->ud.target;
case BL_MOB: return ((struct mob_data*)bl)->target_id;
case BL_PET: return ((struct pet_data*)bl)->target_id;
case BL_HOM: return ((struct homun_data*)bl)->ud.target;
case BL_MER: return ((s_mercenary_data*)bl)->ud.target;
case BL_ELEM: return ((s_elemental_data*)bl)->ud.target;
}
return 0;
}
/**
* Get random enemy
* @param bl
* @param ap
* @return Found target (1) or not found (0)
*/
static int battle_getenemy_sub(struct block_list *bl, va_list ap)
{
struct block_list **bl_list;
struct block_list *target;
int *c;
bl_list = va_arg(ap, struct block_list **);
c = va_arg(ap, int *);
target = va_arg(ap, struct block_list *);
if (bl->id == target->id)
return 0;
if (*c >= 24)
return 0;
if (status_isdead(bl))
return 0;
if (battle_check_target(target, bl, BCT_ENEMY) > 0) {
bl_list[(*c)++] = bl;
return 1;
}
return 0;
}
/**
* Returns list of enemies within given range
* @param target
* @param type
* @param range
* @return Target list
* @author [Skotlex]
*/
struct block_list* battle_getenemy(struct block_list *target, int type, int range)
{
struct block_list *bl_list[24];
int c = 0;
memset(bl_list, 0, sizeof(bl_list));
map_foreachinallrange(battle_getenemy_sub, target, range, type, bl_list, &c, target);
if ( c == 0 )
return NULL;
if( c > 24 )
c = 24;
return bl_list[rnd()%c];
}
/**
* Get random enemy within area
* @param bl
* @param ap
* @return Found target (1) or not found (0)
*/
static int battle_getenemyarea_sub(struct block_list *bl, va_list ap)
{
struct block_list **bl_list, *src;
int *c, ignore_id;
bl_list = va_arg(ap, struct block_list **);
c = va_arg(ap, int *);
src = va_arg(ap, struct block_list *);
ignore_id = va_arg(ap, int);
if( bl->id == src->id || bl->id == ignore_id )
return 0; // Ignores Caster and a possible pre-target
if( *c >= 23 )
return 0;
if( status_isdead(bl) )
return 0;
if( battle_check_target(src, bl, BCT_ENEMY) > 0 ) {// Is Enemy!...
bl_list[(*c)++] = bl;
return 1;
}
return 0;
}
/**
* Returns list of enemies within an area
* @param src
* @param x
* @param y
* @param range
* @param type
* @param ignore_id
* @return Target list
*/
struct block_list* battle_getenemyarea(struct block_list *src, int x, int y, int range, int type, int ignore_id)
{
struct block_list *bl_list[24];
int c = 0;
memset(bl_list, 0, sizeof(bl_list));
map_foreachinallarea(battle_getenemyarea_sub, src->m, x - range, y - range, x + range, y + range, type, bl_list, &c, src, ignore_id);
if( c == 0 )
return NULL;
if( c >= 24 )
c = 23;
return bl_list[rnd()%c];
}
/*========================================== [Playtester]
* Deals damage without delay, applies additional effects and triggers monster events
* This function is called from battle_delay_damage or battle_delay_damage_sub
* @param src: Source of damage
* @param target: Target of damage
* @param damage: Damage to be dealt
* @param delay: Damage delay
* @param skill_lv: Level of skill used
* @param skill_id: ID o skill used
* @param dmg_lv: State of the attack (miss, etc.)
* @param attack_type: Type of the attack (BF_NORMAL|BF_SKILL|BF_SHORT|BF_LONG|BF_WEAPON|BF_MAGIC|BF_MISC)
* @param additional_effects: Whether additional effect should be applied
* @param isspdamage: If the damage is done to SP
* @param tick: Current tick
*------------------------------------------*/
void battle_damage(struct block_list *src, struct block_list *target, int64 damage, t_tick delay, uint16 skill_lv, uint16 skill_id, enum damage_lv dmg_lv, unsigned short attack_type, bool additional_effects, t_tick tick, bool isspdamage) {
map_freeblock_lock();
if (isspdamage)
status_fix_spdamage(src, target, damage, delay, skill_id);
else
status_fix_damage(src, target, damage, delay, skill_id); // We have to separate here between reflect damage and others [icescope]
if (attack_type && !status_isdead(target) && additional_effects)
skill_additional_effect(src, target, skill_id, skill_lv, attack_type, dmg_lv, tick);
if (dmg_lv > ATK_BLOCK && attack_type)
skill_counter_additional_effect(src, target, skill_id, skill_lv, attack_type, tick);
// This is the last place where we have access to the actual damage type, so any monster events depending on type must be placed here
if (target->type == BL_MOB) {
struct mob_data* md = BL_CAST(BL_MOB, target);
if (!status_isdead(target) && src != target) {
if (damage > 0 )
mobskill_event(md, src, tick, attack_type);
if (skill_id)
mobskill_event(md, src, tick, MSC_SKILLUSED|(skill_id<<16));
}
if (damage && (attack_type&BF_NORMAL)) // Monsters differentiate whether they have been attacked by a skill or a normal attack
md->norm_attacked_id = md->attacked_id;
}
map_freeblock_unlock();
}
/// Damage Delayed Structure
struct delay_damage {
int src_id;
int target_id;
int64 damage;
t_tick delay;
unsigned short distance;
uint16 skill_lv;
uint16 skill_id;
enum damage_lv dmg_lv;
unsigned short attack_type;
bool additional_effects;
enum bl_type src_type;
bool isspdamage;
};
TIMER_FUNC(battle_delay_damage_sub){
struct delay_damage *dat = (struct delay_damage *)data;
if ( dat ) {
struct block_list* src = map_id2bl(dat->src_id);
struct block_list* target = map_id2bl(dat->target_id);
if (target && !status_isdead(target)) {
if( src && target->m == src->m &&
(target->type != BL_PC || ((TBL_PC*)target)->invincible_timer == INVALID_TIMER) &&
check_distance_bl(src, target, dat->distance) ) //Check to see if you haven't teleported. [Skotlex]
{
//Deal damage
battle_damage(src, target, dat->damage, dat->delay, dat->skill_lv, dat->skill_id, dat->dmg_lv, dat->attack_type, dat->additional_effects, tick, dat->isspdamage);
} else if( !src && dat->skill_id == CR_REFLECTSHIELD ) { // it was monster reflected damage, and the monster died, we pass the damage to the character as expected
map_freeblock_lock();
status_fix_damage(target, target, dat->damage, dat->delay, dat->skill_id);
map_freeblock_unlock();
}
}
struct map_session_data *sd = BL_CAST(BL_PC, src);
if (sd && --sd->delayed_damage == 0 && sd->state.hold_recalc) {
sd->state.hold_recalc = false;
status_calc_pc(sd, SCO_FORCE);
}
}
ers_free(delay_damage_ers, dat);
return 0;
}
int battle_delay_damage(t_tick tick, int amotion, struct block_list *src, struct block_list *target, int attack_type, uint16 skill_id, uint16 skill_lv, int64 damage, enum damage_lv dmg_lv, t_tick ddelay, bool additional_effects, bool isspdamage)
{
struct delay_damage *dat;
struct status_change *sc;
struct block_list *d_tbl = NULL;
struct block_list *e_tbl = NULL;
nullpo_ret(src);
nullpo_ret(target);
sc = status_get_sc(target);
if (sc) {
if (sc->data[SC_DEVOTION] && sc->data[SC_DEVOTION]->val1)
d_tbl = map_id2bl(sc->data[SC_DEVOTION]->val1);
if (sc->data[SC_WATER_SCREEN_OPTION] && sc->data[SC_WATER_SCREEN_OPTION]->val1)
e_tbl = map_id2bl(sc->data[SC_WATER_SCREEN_OPTION]->val1);
}
if( ((d_tbl && check_distance_bl(target, d_tbl, sc->data[SC_DEVOTION]->val3)) || e_tbl) &&
damage > 0 && skill_id != CR_REFLECTSHIELD
#ifndef RENEWAL
&& skill_id != PA_PRESSURE
#endif
) {
struct map_session_data* tsd = BL_CAST( BL_PC, target );
if( tsd && pc_issit( tsd ) && battle_config.devotion_standup_fix ){
pc_setstand( tsd, true );
skill_sit( tsd, 0 );
}
damage = 0;
}
if ( !battle_config.delay_battle_damage || amotion <= 1 ) {
//Deal damage
battle_damage(src, target, damage, ddelay, skill_lv, skill_id, dmg_lv, attack_type, additional_effects, gettick(), isspdamage);
return 0;
}
dat = ers_alloc(delay_damage_ers, struct delay_damage);
dat->src_id = src->id;
dat->target_id = target->id;
dat->skill_id = skill_id;
dat->skill_lv = skill_lv;
dat->attack_type = attack_type;
dat->damage = damage;
dat->dmg_lv = dmg_lv;
dat->delay = ddelay;
dat->distance = distance_bl(src, target) + (battle_config.snap_dodge ? 10 : AREA_SIZE);
dat->additional_effects = additional_effects;
dat->src_type = src->type;
dat->isspdamage = isspdamage;
if (src->type != BL_PC && amotion > 1000)
amotion = 1000; //Aegis places a damage-delay cap of 1 sec to non player attacks. [Skotlex]
if( src->type == BL_PC )
((TBL_PC*)src)->delayed_damage++;
add_timer(tick+amotion, battle_delay_damage_sub, 0, (intptr_t)dat);
return 0;
}
/**
* Does attribute fix modifiers.
* Added passing of the chars so that the status changes can affect it. [Skotlex]
* Note: Passing src/target == NULL is perfectly valid, it skips SC_ checks.
* @param src
* @param target
* @param damage
* @param atk_elem
* @param def_type
* @param def_lv
* @param flag
* @return damage
*/
int64 battle_attr_fix(struct block_list *src, struct block_list *target, int64 damage,int atk_elem,int def_type, int def_lv)
{
struct status_change *sc = NULL, *tsc = NULL;
int ratio;
if (src) sc = status_get_sc(src);
if (target) tsc = status_get_sc(target);
if (!CHK_ELEMENT(atk_elem))
atk_elem = rnd()%ELE_ALL;
if (!CHK_ELEMENT(def_type) || !CHK_ELEMENT_LEVEL(def_lv)) {
ShowError("battle_attr_fix: unknown attribute type: atk=%d def_type=%d def_lv=%d\n",atk_elem,def_type,def_lv);
return damage;
}
ratio = elemental_attribute_db.getAttribute(def_lv-1, atk_elem, def_type);
if (sc && sc->count) { //increase dmg by src status
switch(atk_elem){
case ELE_FIRE:
if (sc->data[SC_VOLCANO])
#ifdef RENEWAL
ratio += sc->data[SC_VOLCANO]->val3;
#else
damage += (int64)((damage*sc->data[SC_VOLCANO]->val3) / 100);
#endif
break;
case ELE_WIND:
if (sc->data[SC_VIOLENTGALE])
#ifdef RENEWAL
ratio += sc->data[SC_VIOLENTGALE]->val3;
#else
damage += (int64)((damage*sc->data[SC_VIOLENTGALE]->val3) / 100);
#endif
break;
case ELE_WATER:
if (sc->data[SC_DELUGE])
#ifdef RENEWAL
ratio += sc->data[SC_DELUGE]->val3;
#else
damage += (int64)((damage*sc->data[SC_DELUGE]->val3) / 100);
#endif
break;
case ELE_GHOST:
if (sc->data[SC_TELEKINESIS_INTENSE])
ratio += sc->data[SC_TELEKINESIS_INTENSE]->val3;
break;
}
}
if( target && target->type == BL_SKILL ) {
if( atk_elem == ELE_FIRE && battle_getcurrentskill(target) == GN_WALLOFTHORN ) {
struct skill_unit *su = (struct skill_unit*)target;
std::shared_ptr<s_skill_unit_group> sg;
struct block_list *src2;
if( !su || !su->alive || (sg = su->group) == NULL || !sg || sg->val3 == -1 ||
(src2 = map_id2bl(sg->src_id)) == NULL || status_isdead(src2) )
return 0;
if( sg->unit_id != UNT_FIREWALL ) {
int x,y;
x = sg->val3 >> 16;
y = sg->val3 & 0xffff;
skill_unitsetting(src2,su->group->skill_id,su->group->skill_lv,x,y,1);
sg->val3 = -1;
sg->limit = DIFF_TICK(gettick(),sg->tick)+300;
}
}
}
if (tsc && tsc->count) { //increase dmg by target status
switch(atk_elem) {
case ELE_FIRE:
if (tsc->data[SC_SPIDERWEB]) { //Double damage
#ifdef RENEWAL
ratio += 100;
#else
damage *= 2;
#endif
//Remove a unit group or end whole status change
status_change_end(target, SC_SPIDERWEB, INVALID_TIMER);
}
if (tsc->data[SC_THORNSTRAP] && battle_getcurrentskill(src) != GN_CARTCANNON)
status_change_end(target, SC_THORNSTRAP, INVALID_TIMER);
if (tsc->data[SC_CRYSTALIZE])
status_change_end(target, SC_CRYSTALIZE, INVALID_TIMER);
if (tsc->data[SC_EARTH_INSIGNIA])
#ifdef RENEWAL
ratio += 50;
#else
damage += (int64)(damage * 50 / 100);
#endif
if( tsc->data[SC_WIDEWEB] ) {
#ifdef RENEWAL
ratio += 100;
#else
damage *= 2;
#endif
status_change_end(target,SC_WIDEWEB,INVALID_TIMER);
}
if( tsc->data[SC_BURNT] ) {
#ifdef RENEWAL
ratio += 400;
#else
damage += (int64)(damage * 400 / 100);
#endif
}
break;
case ELE_HOLY:
if (tsc->data[SC_ORATIO])
#ifdef RENEWAL
ratio += tsc->data[SC_ORATIO]->val1 * 2;
#else
damage += (int64)(damage * (tsc->data[SC_ORATIO]->val1 * 2) / 100);
#endif
break;
case ELE_POISON:
if (tsc->data[SC_VENOMIMPRESS])
#ifdef RENEWAL
ratio += tsc->data[SC_VENOMIMPRESS]->val2;
#else
damage += (int64)(damage * tsc->data[SC_VENOMIMPRESS]->val2 / 100);
#endif
if (tsc->data[SC_CLOUD_POISON]) {
#ifdef RENEWAL
ratio += 5 * tsc->data[SC_CLOUD_POISON]->val1;
#else
damage += (int64)(damage * 5 * tsc->data[SC_CLOUD_POISON]->val1 / 100);
#endif
}
break;
case ELE_WIND:
if (tsc->data[SC_WATER_INSIGNIA])
#ifdef RENEWAL
ratio += 50;
#else
damage += (int64)(damage * 50 / 100);
#endif
if (tsc->data[SC_CRYSTALIZE]) {
uint16 skill_id = battle_getcurrentskill(src);
if (skill_get_type(skill_id)&BF_MAGIC)
#ifdef RENEWAL
ratio += 50;
#else
damage += (int64)(damage * 50 / 100);
#endif
}
break;
case ELE_WATER:
if (tsc->data[SC_FIRE_INSIGNIA])
#ifdef RENEWAL
ratio += 50;
#else
damage += (int64)(damage * 50 / 100);
#endif
break;
case ELE_EARTH:
if (tsc->data[SC_WIND_INSIGNIA])
#ifdef RENEWAL
ratio += 50;
#else
damage += (int64)(damage * 50 / 100);
#endif
status_change_end(target, SC_MAGNETICFIELD, INVALID_TIMER); //freed if received earth dmg
break;
case ELE_NEUTRAL:
if (tsc->data[SC_ANTI_M_BLAST])
#ifdef RENEWAL
ratio += tsc->data[SC_ANTI_M_BLAST]->val2;
#else
damage += (int64)(damage * tsc->data[SC_ANTI_M_BLAST]->val2 / 100);
#endif
break;
case ELE_DARK:
if (tsc->data[SC_SOULCURSE]) {
if (status_get_class_(target) == CLASS_BOSS)
#ifdef RENEWAL
ratio += 20;
#else
damage += (int64)(damage * 20 / 100);
#endif
else
#ifdef RENEWAL
ratio += 100;
#else
damage *= 2;
#endif
}
break;
}
if (tsc->data[SC_MAGIC_POISON])
#ifdef RENEWAL
ratio += 50;
#else
damage += (int64)(damage * 50 / 100);
#endif
}
if (battle_config.attr_recover == 0 && ratio < 0)
ratio = 0;
#ifdef RENEWAL
//In renewal, reductions are always rounded down so damage can never reach 0 unless ratio is 0
damage = damage - (int64)((damage * (100 - ratio)) / 100);
#else
damage = (int64)((damage*ratio)/100);
#endif
//Damage can be negative, see battle_config.attr_recover
return damage;
}
/**
* Calculates card bonuses damage adjustments.
* @param attack_type @see enum e_battle_flag
* @param src Attacker
* @param target Target
* @param nk Skill's nk @see enum e_skill_nk [NK_IGNOREATKCARD|NK_IGNOREELEMENT|NK_IGNOREDEFCARD]
* @param rh_ele Right-hand weapon element
* @param lh_ele Left-hand weapon element (BF_MAGIC and BF_MISC ignore this value)
* @param damage Original damage
* @param left Left hand flag (BF_MISC and BF_MAGIC ignore flag value)
* 3: Calculates attacker bonuses in both hands.
* 2: Calculates attacker bonuses in right-hand only.
* 0 or 1: Only calculates target bonuses.
* @param flag Misc value of skill & damage flags
* @return damage Damage diff between original damage and after calculation
*/
int battle_calc_cardfix(int attack_type, struct block_list *src, struct block_list *target, std::bitset<NK_MAX> nk, int rh_ele, int lh_ele, int64 damage, int left, int flag){
struct map_session_data *sd, ///< Attacker session data if BL_PC
*tsd; ///< Target session data if BL_PC
int cardfix = 1000;
int s_class, ///< Attacker class
t_class; ///< Target class
std::vector<e_race2> s_race2, /// Attacker Race2
t_race2; ///< Target Race2
enum e_element s_defele; ///< Attacker Element (not a weapon or skill element!)
struct status_data *sstatus, ///< Attacker status data
*tstatus; ///< Target status data
int64 original_damage;
if( !damage )
return 0;
original_damage = damage;
sd = BL_CAST(BL_PC, src);
tsd = BL_CAST(BL_PC, target);
t_class = status_get_class(target);
s_class = status_get_class(src);
sstatus = status_get_status_data(src);
tstatus = status_get_status_data(target);
s_race2 = status_get_race2(src);
t_race2 = status_get_race2(target);
s_defele = (tsd) ? (enum e_element)status_get_element(src) : ELE_NONE;
//Official servers apply the cardfix value on a base of 1000 and round down the reduction/increase
#define APPLY_CARDFIX(damage, fix) { (damage) = (damage) - (int64)(((damage) * (1000 - max(0, fix))) / 1000); }
switch( attack_type ) {
case BF_MAGIC:
// Affected by attacker ATK bonuses
if( sd && !nk[NK_IGNOREATKCARD] ) {
int32 race2_val = 0;
for (const auto &raceit : t_race2)
race2_val += sd->indexed_bonus.magic_addrace2[raceit];
cardfix = cardfix * (100 + sd->indexed_bonus.magic_addrace[tstatus->race] + sd->indexed_bonus.magic_addrace[RC_ALL] + race2_val) / 100;
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
cardfix = cardfix * (100 + sd->indexed_bonus.magic_addele[tstatus->def_ele] + sd->indexed_bonus.magic_addele[ELE_ALL] +
sd->indexed_bonus.magic_addele_script[tstatus->def_ele] + sd->indexed_bonus.magic_addele_script[ELE_ALL]) / 100;
cardfix = cardfix * (100 + sd->indexed_bonus.magic_atk_ele[rh_ele] + sd->indexed_bonus.magic_atk_ele[ELE_ALL]) / 100;
}
cardfix = cardfix * (100 + sd->indexed_bonus.magic_addsize[tstatus->size] + sd->indexed_bonus.magic_addsize[SZ_ALL]) / 100;
cardfix = cardfix * (100 + sd->indexed_bonus.magic_addclass[tstatus->class_] + sd->indexed_bonus.magic_addclass[CLASS_ALL]) / 100;
if (sd->status.weapon == W_2HSTAFF)// 2-Handed Staff Mastery
cardfix = cardfix * (100 + pc_checkskill(sd, AG_TWOHANDSTAFF)) / 100;
for (const auto &it : sd->add_mdmg) {
if (it.id == t_class) {
cardfix = cardfix * (100 + it.val) / 100;
break;
}
}
APPLY_CARDFIX(damage, cardfix);
}
// Affected by target DEF bonuses
if( tsd && !nk[NK_IGNOREDEFCARD] ) {
cardfix = 1000; // reset var for target
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
int ele_fix = tsd->indexed_bonus.subele[rh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[rh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL];
for (const auto &it : tsd->subele2) {
if (it.ele != ELE_ALL && it.ele != rh_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
if (s_defele != ELE_NONE)
ele_fix += tsd->indexed_bonus.magic_subdefele[s_defele] + tsd->indexed_bonus.magic_subdefele[ELE_ALL];
cardfix = cardfix * (100 - ele_fix) / 100;
}
cardfix = cardfix * (100 - tsd->indexed_bonus.subsize[sstatus->size] - tsd->indexed_bonus.subsize[SZ_ALL]) / 100;
cardfix = cardfix * (100 - tsd->indexed_bonus.magic_subsize[sstatus->size] - tsd->indexed_bonus.magic_subsize[SZ_ALL]) / 100;
int32 race_fix = 0;
for (const auto &raceit : s_race2)
race_fix += tsd->indexed_bonus.subrace2[raceit];
cardfix = cardfix * (100 - race_fix) / 100;
race_fix = tsd->indexed_bonus.subrace[sstatus->race] + tsd->indexed_bonus.subrace[RC_ALL];
for (const auto &it : tsd->subrace3) {
if (it.race != RC_ALL && it.race != sstatus->race)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
race_fix += it.rate;
}
cardfix = cardfix * (100 - race_fix) / 100;
cardfix = cardfix * (100 - tsd->indexed_bonus.subclass[sstatus->class_] - tsd->indexed_bonus.subclass[CLASS_ALL]) / 100;
for (const auto &it : tsd->add_mdef) {
if (it.id == s_class) {
cardfix = cardfix * (100 - it.val) / 100;
break;
}
}
#ifndef RENEWAL
//It was discovered that ranged defense also counts vs magic! [Skotlex]
if( flag&BF_SHORT )
cardfix = cardfix * (100 - tsd->bonus.near_attack_def_rate) / 100;
else if (!nk[NK_IGNORELONGCARD])
cardfix = cardfix * (100 - tsd->bonus.long_attack_def_rate) / 100;
#endif
cardfix = cardfix * (100 - tsd->bonus.magic_def_rate) / 100;
if( tsd->sc.data[SC_MDEF_RATE] )
cardfix = cardfix * (100 - tsd->sc.data[SC_MDEF_RATE]->val1) / 100;
APPLY_CARDFIX(damage, cardfix);
}
break;
case BF_WEAPON:
// Affected by attacker ATK bonuses
if( sd && !nk[NK_IGNOREATKCARD] && (left&2) ) {
short cardfix_ = 1000;
if( sd->state.arrow_atk ) { // Ranged attack
cardfix = cardfix * (100 + sd->right_weapon.addrace[tstatus->race] + sd->indexed_bonus.arrow_addrace[tstatus->race] +
sd->right_weapon.addrace[RC_ALL] + sd->indexed_bonus.arrow_addrace[RC_ALL]) / 100;
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->indexed_bonus.arrow_addele[tstatus->def_ele] +
sd->right_weapon.addele[ELE_ALL] + sd->indexed_bonus.arrow_addele[ELE_ALL];
for (const auto &it : sd->right_weapon.addele2) {
if (it.ele != ELE_ALL && it.ele != tstatus->def_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
cardfix = cardfix * (100 + ele_fix) / 100;
}
cardfix = cardfix * (100 + sd->right_weapon.addsize[tstatus->size] + sd->indexed_bonus.arrow_addsize[tstatus->size] +
sd->right_weapon.addsize[SZ_ALL] + sd->indexed_bonus.arrow_addsize[SZ_ALL]) / 100;
int32 race_fix = 0;
for (const auto &raceit : t_race2)
race_fix += sd->right_weapon.addrace2[raceit];
cardfix = cardfix * (100 + race_fix) / 100;
cardfix = cardfix * (100 + sd->right_weapon.addclass[tstatus->class_] + sd->indexed_bonus.arrow_addclass[tstatus->class_] +
sd->right_weapon.addclass[CLASS_ALL] + sd->indexed_bonus.arrow_addclass[CLASS_ALL]) / 100;
} else { // Melee attack
int skill = 0;
// Calculates each right & left hand weapon bonuses separatedly
if( !battle_config.left_cardfix_to_right ) {
// Right-handed weapon
cardfix = cardfix * (100 + sd->right_weapon.addrace[tstatus->race] + sd->right_weapon.addrace[RC_ALL]) / 100;
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->right_weapon.addele[ELE_ALL];
for (const auto &it : sd->right_weapon.addele2) {
if (it.ele != ELE_ALL && it.ele != tstatus->def_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
cardfix = cardfix * (100 + ele_fix) / 100;
}
cardfix = cardfix * (100 + sd->right_weapon.addsize[tstatus->size] + sd->right_weapon.addsize[SZ_ALL]) / 100;
for (const auto &raceit : t_race2)
cardfix = cardfix * (100 + sd->right_weapon.addrace2[raceit]) / 100;
cardfix = cardfix * (100 + sd->right_weapon.addclass[tstatus->class_] + sd->right_weapon.addclass[CLASS_ALL]) / 100;
if( left&1 ) { // Left-handed weapon
cardfix_ = cardfix_ * (100 + sd->left_weapon.addrace[tstatus->race] + sd->left_weapon.addrace[RC_ALL]) / 100;
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
int ele_fix_lh = sd->left_weapon.addele[tstatus->def_ele] + sd->left_weapon.addele[ELE_ALL];
for (const auto &it : sd->left_weapon.addele2) {
if (it.ele != ELE_ALL && it.ele != tstatus->def_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix_lh += it.rate;
}
cardfix_ = cardfix_ * (100 + ele_fix_lh) / 100;
}
cardfix_ = cardfix_ * (100 + sd->left_weapon.addsize[tstatus->size] + sd->left_weapon.addsize[SZ_ALL]) / 100;
for (const auto &raceit : t_race2)
cardfix_ = cardfix_ * (100 + sd->left_weapon.addrace2[raceit]) / 100;
cardfix_ = cardfix_ * (100 + sd->left_weapon.addclass[tstatus->class_] + sd->left_weapon.addclass[CLASS_ALL]) / 100;
}
}
// Calculates right & left hand weapon as unity
else {
//! CHECKME: If 'left_cardfix_to_right' is yes, doesn't need to check NK_IGNOREELEMENT?
//if( !nk[&]K_IGNOREELEMENT) ) { // Affected by Element modifier bonuses
int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->left_weapon.addele[tstatus->def_ele]
+ sd->right_weapon.addele[ELE_ALL] + sd->left_weapon.addele[ELE_ALL];
for (const auto &it : sd->right_weapon.addele2) {
if (it.ele != ELE_ALL && it.ele != tstatus->def_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
for (const auto &it : sd->left_weapon.addele2) {
if (it.ele != ELE_ALL && it.ele != tstatus->def_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
cardfix = cardfix * (100 + ele_fix) / 100;
//}
cardfix = cardfix * (100 + sd->right_weapon.addrace[tstatus->race] + sd->left_weapon.addrace[tstatus->race] +
sd->right_weapon.addrace[RC_ALL] + sd->left_weapon.addrace[RC_ALL]) / 100;
cardfix = cardfix * (100 + sd->right_weapon.addsize[tstatus->size] + sd->left_weapon.addsize[tstatus->size] +
sd->right_weapon.addsize[SZ_ALL] + sd->left_weapon.addsize[SZ_ALL]) / 100;
for (const auto &raceit : t_race2)
cardfix = cardfix * (100 + sd->right_weapon.addrace2[raceit] + sd->left_weapon.addrace2[raceit]) / 100;
cardfix = cardfix * (100 + sd->right_weapon.addclass[tstatus->class_] + sd->left_weapon.addclass[tstatus->class_] +
sd->right_weapon.addclass[CLASS_ALL] + sd->left_weapon.addclass[CLASS_ALL]) / 100;
}
if( sd->status.weapon == W_KATAR && (skill = pc_checkskill(sd,ASC_KATAR)) > 0 ) // Adv. Katar Mastery functions similar to a +%ATK card on official [helvetica]
cardfix = cardfix * (100 + (10 + 2 * skill)) / 100;
}
//! CHECKME: These right & left hand weapon ignores 'left_cardfix_to_right'?
for (const auto &it : sd->right_weapon.add_dmg) {
if (it.id == t_class) {
cardfix = cardfix * (100 + it.val) / 100;
break;
}
}
if( left&1 ) {
for (const auto &it : sd->left_weapon.add_dmg) {
if (it.id == t_class) {
cardfix_ = cardfix_ * (100 + it.val) / 100;
break;
}
}
}
#ifndef RENEWAL
if (flag & BF_SHORT)
cardfix = cardfix * (100 + sd->bonus.short_attack_atk_rate) / 100;
if( flag&BF_LONG )
cardfix = cardfix * (100 + sd->bonus.long_attack_atk_rate) / 100;
#endif
if (left&1) {
APPLY_CARDFIX(damage, cardfix_);
} else {
APPLY_CARDFIX(damage, cardfix);
}
}
// Affected by target DEF bonuses
else if( tsd && !nk[NK_IGNOREDEFCARD] && !(left&2) ) {
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
int ele_fix = tsd->indexed_bonus.subele[rh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[rh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL];
for (const auto &it : tsd->subele2) {
if (it.ele != ELE_ALL && it.ele != rh_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
cardfix = cardfix * (100 - ele_fix) / 100;
if( left&1 && lh_ele != rh_ele ) {
int ele_fix_lh = tsd->indexed_bonus.subele[lh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[lh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL];
for (const auto &it : tsd->subele2) {
if (it.ele != ELE_ALL && it.ele != lh_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix_lh += it.rate;
}
cardfix = cardfix * (100 - ele_fix_lh) / 100;
}
cardfix = cardfix * (100 - tsd->indexed_bonus.subdefele[s_defele] - tsd->indexed_bonus.subdefele[ELE_ALL]) / 100;
}
int32 race_fix = 0;
cardfix = cardfix * (100 - tsd->indexed_bonus.subsize[sstatus->size] - tsd->indexed_bonus.subsize[SZ_ALL]) / 100;
cardfix = cardfix * (100 - tsd->indexed_bonus.weapon_subsize[sstatus->size] - tsd->indexed_bonus.weapon_subsize[SZ_ALL]) / 100;
for (const auto &raceit : s_race2)
race_fix += tsd->indexed_bonus.subrace2[raceit];
cardfix = cardfix * (100 - race_fix) / 100;
race_fix = tsd->indexed_bonus.subrace[sstatus->race] + tsd->indexed_bonus.subrace[RC_ALL];
for (const auto &it : tsd->subrace3) {
if (it.race != RC_ALL && it.race != sstatus->race)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
race_fix += it.rate;
}
cardfix = cardfix * (100 - race_fix) / 100;
cardfix = cardfix * (100 - tsd->indexed_bonus.subclass[sstatus->class_] - tsd->indexed_bonus.subclass[CLASS_ALL]) / 100;
for (const auto &it : tsd->add_def) {
if (it.id == s_class) {
cardfix = cardfix * (100 - it.val) / 100;
break;
}
}
if( flag&BF_SHORT )
cardfix = cardfix * (100 - tsd->bonus.near_attack_def_rate) / 100;
else if (!nk[NK_IGNORELONGCARD]) // BF_LONG (there's no other choice)
cardfix = cardfix * (100 - tsd->bonus.long_attack_def_rate) / 100;
if( tsd->sc.data[SC_DEF_RATE] )
cardfix = cardfix * (100 - tsd->sc.data[SC_DEF_RATE]->val1) / 100;
APPLY_CARDFIX(damage, cardfix);
}
break;
case BF_MISC:
// Affected by target DEF bonuses
if( tsd && !nk[NK_IGNOREDEFCARD] ) {
if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses
int ele_fix = tsd->indexed_bonus.subele[rh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[rh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL];
for (const auto &it : tsd->subele2) {
if (it.ele != rh_ele)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
ele_fix += it.rate;
}
if (s_defele != ELE_NONE)
ele_fix += tsd->indexed_bonus.subdefele[s_defele] + tsd->indexed_bonus.subdefele[ELE_ALL];
cardfix = cardfix * (100 - ele_fix) / 100;
}
int race_fix = tsd->indexed_bonus.subrace[sstatus->race] + tsd->indexed_bonus.subrace[RC_ALL];
for (const auto &it : tsd->subrace3) {
if (it.race != RC_ALL && it.race != sstatus->race)
continue;
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
race_fix += it.rate;
}
cardfix = cardfix * (100 - race_fix) / 100;
cardfix = cardfix * (100 - tsd->indexed_bonus.subsize[sstatus->size] - tsd->indexed_bonus.subsize[SZ_ALL]) / 100;
race_fix = 0;
for (const auto &raceit : s_race2)
race_fix += tsd->indexed_bonus.subrace2[raceit];
cardfix = cardfix * (100 - race_fix) / 100;
cardfix = cardfix * (100 - tsd->indexed_bonus.subclass[sstatus->class_] - tsd->indexed_bonus.subclass[CLASS_ALL]) / 100;
cardfix = cardfix * (100 - tsd->bonus.misc_def_rate) / 100;
if( flag&BF_SHORT )
cardfix = cardfix * (100 - tsd->bonus.near_attack_def_rate) / 100;
else if (!nk[NK_IGNORELONGCARD]) // BF_LONG (there's no other choice)
cardfix = cardfix * (100 - tsd->bonus.long_attack_def_rate) / 100;
APPLY_CARDFIX(damage, cardfix);
}
break;
}
#undef APPLY_CARDFIX
return (int)cap_value(damage - original_damage, INT_MIN, INT_MAX);
}
/**
* Absorb damage based on criteria
* @param bl
* @param d Damage
**/
static void battle_absorb_damage(struct block_list *bl, struct Damage *d) {
int64 dmg_ori = 0, dmg_new = 0;
nullpo_retv(bl);
nullpo_retv(d);
if (!d->damage && !d->damage2)
return;
switch (bl->type) {
case BL_PC:
{
struct map_session_data *sd = BL_CAST(BL_PC, bl);
if (!sd)
return;
dmg_ori = dmg_new = d->damage + d->damage2;
if (sd->bonus.absorb_dmg_maxhp) {
int hp = sd->bonus.absorb_dmg_maxhp * status_get_max_hp(bl) / 100;
if (dmg_ori > hp)
dmg_new = dmg_ori - hp;
}
if (sd->bonus.absorb_dmg_maxhp2) {
int hp = sd->bonus.absorb_dmg_maxhp2 * status_get_max_hp(bl) / 100;
if (dmg_ori > hp) {
dmg_new = hp;
}
}
}
break;
}
if (dmg_ori == dmg_new)
return;
if (!d->damage2)
d->damage = dmg_new;
else if (!d->damage)
d->damage2 = dmg_new;
else {
d->damage = dmg_new;
d->damage2 = dmg_new * d->damage2 / dmg_ori / 100;
if (d->damage2 < 1)
d->damage2 = 1;
d->damage = d->damage - d->damage2;
}
}
/**
* Check for active statuses that block damage
* @param src: Attacker
* @param target: Target of attack
* @param sc: Status Change data
* @param d: Damage data
* @param damage: Damage received as a reference
* @param skill_id: Skill ID
* @param skill_lv: Skill level
* @return True: Damage inflicted, False: Missed
**/
bool battle_status_block_damage(struct block_list *src, struct block_list *target, struct status_change *sc, struct Damage *d, int64 &damage, uint16 skill_id, uint16 skill_lv) {
if (!src || !target || !sc || !d)
return true;
status_change_entry *sce;
int flag = d->flag;
// SC Types that must be first because they may or may not block damage
if ((sce = sc->data[SC_KYRIE]) && damage > 0) {
sce->val2 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
if (flag & BF_WEAPON || skill_id == TF_THROWSTONE) {
if (sce->val2 >= 0)
damage = 0;
else
damage = -sce->val2;
}
if ((--sce->val3) <= 0 || (sce->val2 <= 0) || skill_id == AL_HOLYLIGHT)
status_change_end(target, SC_KYRIE, INVALID_TIMER);
}
if ((sce = sc->data[SC_P_ALTER]) && damage > 0) {
clif_specialeffect(target, EF_GUARD, AREA);
sce->val3 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
if (sce->val3 >= 0)
damage = 0;
else
damage = -sce->val3;
if (sce->val3 <= 0)
status_change_end(target, SC_P_ALTER, INVALID_TIMER);
}
if ((sce = sc->data[SC_TUNAPARTY]) && damage > 0) {
sce->val2 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
if (sce->val2 >= 0)
damage = 0;
else
damage = -sce->val2;
if (sce->val2 <= 0)
status_change_end(target, SC_TUNAPARTY, INVALID_TIMER);
}
if ((sce = sc->data[SC_DIMENSION1]) && damage > 0) {
sce->val2 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
if (sce->val2 <= 0)
status_change_end(target, SC_DIMENSION1, INVALID_TIMER);
return false;
}
if ((sce = sc->data[SC_DIMENSION2]) && damage > 0) {
sce->val2 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
if (sce->val2 <= 0)
status_change_end(target, SC_DIMENSION2, INVALID_TIMER);
return false;
}
if ((sce = sc->data[SC_GUARDIAN_S]) && damage > 0) {
clif_specialeffect(target, EF_GUARD3, AREA);// Not official but we gotta show some way the barrier is working. [Rytech]
sce->val2 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
if (flag & BF_WEAPON) {
if (sce->val2 >= 0)
damage = 0;
else
damage = -sce->val2;
}
if (sce->val2 <= 0)
status_change_end(target, SC_GUARDIAN_S, INVALID_TIMER);
}
if (damage == 0)
return false;
// ATK_BLOCK Type
if ((sce = sc->data[SC_SAFETYWALL]) && (flag&(BF_SHORT | BF_MAGIC)) == BF_SHORT) {
std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val3);
if (group) {
d->dmg_lv = ATK_BLOCK;
switch (sce->val2) {
case MG_SAFETYWALL:
if (--group->val2 <= 0) {
skill_delunitgroup(group);
break;
}
#ifdef RENEWAL
if (group->val3 - damage > 0)
group->val3 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
else
skill_delunitgroup(group);
#endif
break;
case MH_STEINWAND:
if (--group->val2 <= 0) {
skill_delunitgroup(group);
break;
}
if (group->val3 - damage > 0)
group->val3 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX));
else
skill_delunitgroup(group);
break;
}
return false;
}
status_change_end(target, SC_SAFETYWALL, INVALID_TIMER);
}
if ((sc->data[SC_PNEUMA] && (flag&(BF_MAGIC | BF_LONG)) == BF_LONG) ||
#ifdef RENEWAL
(sc->data[SC_BASILICA_CELL]
#else
(sc->data[SC_BASILICA]
#endif
&& !status_bl_has_mode(src, MD_STATUSIMMUNE) && skill_id != SP_SOULEXPLOSION) ||
(sc->data[SC_ZEPHYR] && !(flag&BF_MAGIC && skill_id) && !(skill_get_inf(skill_id)&(INF_GROUND_SKILL | INF_SELF_SKILL))) ||
sc->data[SC__MANHOLE] ||
sc->data[SC_KINGS_GRACE] ||
sc->data[SC_GRAVITYCONTROL]
)
{
d->dmg_lv = ATK_BLOCK;
return false;
}
if (sc->data[SC_WHITEIMPRISON]) { // Gravitation and Pressure do damage without removing the effect
if (skill_id == MG_NAPALMBEAT ||
skill_id == MG_SOULSTRIKE ||
skill_id == WL_SOULEXPANSION ||
skill_id == AG_SOUL_VC_STRIKE ||
(skill_id && skill_get_ele(skill_id, skill_lv) == ELE_GHOST) ||
(skill_id == 0 && (status_get_status_data(src))->rhw.ele == ELE_GHOST))
{
if (skill_id == WL_SOULEXPANSION)
damage <<= 1; // If used against a player in White Imprison, the skill deals double damage.
status_change_end(target, SC_WHITEIMPRISON, INVALID_TIMER); // Those skills do damage and removes effect
} else {
d->dmg_lv = ATK_BLOCK;
return false;
}
}
if ((sce = sc->data[SC_WEAPONBLOCKING]) && flag&(BF_SHORT | BF_WEAPON) && rnd() % 100 < sce->val2) {
clif_skill_nodamage(target, src, GC_WEAPONBLOCKING, sce->val1, 1);
sc_start(src, target, SC_WEAPONBLOCK_ON, 100, src->id, skill_get_time2(GC_WEAPONBLOCKING, sce->val1));
d->dmg_lv = ATK_BLOCK;
return false;
}
if ((sce = sc->data[SC_MILLENNIUMSHIELD]) && sce->val2 > 0 && damage > 0) {
sce->val3 -= static_cast<int>(cap_value(damage, INT_MIN, INT_MAX)); // absorb damage
d->dmg_lv = ATK_BLOCK;
if (sce->val3 <= 0) { // Shield Down
sce->val2--;
if (sce->val2 > 0) {
clif_millenniumshield(target, sce->val2);
sce->val3 = 1000; // Next shield
} else
status_change_end(target, SC_MILLENNIUMSHIELD, INVALID_TIMER); // All shields down
status_change_start(src, target, SC_STUN, 10000, 0, 0, 0, 0, 1000, SCSTART_NOTICKDEF);
}
return false;
}
// ATK_MISS Type
if ((sce = sc->data[SC_AUTOGUARD]) && flag&BF_WEAPON && rnd() % 100 < sce->val2 && !skill_get_inf2(skill_id, INF2_IGNOREAUTOGUARD)) {
status_change_entry *sce_d = sc->data[SC_DEVOTION];
block_list *d_bl;
int delay;
// different delay depending on skill level [celest]
if (sce->val1 <= 5)
delay = 300;
else if (sce->val1 > 5 && sce->val1 <= 9)
delay = 200;
else
delay = 100;
map_session_data *sd = map_id2sd(target->id);
if (sd && pc_issit(sd))
pc_setstand(sd, true);
if (sce_d && (d_bl = map_id2bl(sce_d->val1)) &&
((d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == target->id) ||
(d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce_d->val2] == target->id)) &&
check_distance_bl(target, d_bl, sce_d->val3))
{ //If player is target of devotion, show guard effect on the devotion caster rather than the target
clif_skill_nodamage(d_bl, d_bl, CR_AUTOGUARD, sce->val1, 1);
unit_set_walkdelay(d_bl, gettick(), delay, 1);
d->dmg_lv = ATK_MISS;
return false;
} else {
clif_skill_nodamage(target, target, CR_AUTOGUARD, sce->val1, 1);
unit_set_walkdelay(target, gettick(), delay, 1);
#ifdef RENEWAL
if (sc->data[SC_SHRINK])
sc_start(src, target, SC_STUN, 50, skill_lv, skill_get_time2(skill_id, skill_lv));
#else
if (sc->data[SC_SHRINK] && rnd() % 100 < 5 * sce->val1)
skill_blown(target, src, skill_get_blewcount(CR_SHRINK, 1), -1, BLOWN_NONE);
#endif
d->dmg_lv = ATK_MISS;
return false;
}
}
if (sc->data[SC_NEUTRALBARRIER] && ((flag&(BF_LONG|BF_MAGIC)) == BF_LONG
#ifndef RENEWAL
|| skill_id == CR_ACIDDEMONSTRATION
#endif
)) {
d->dmg_lv = ATK_MISS;
return false;
}
// ATK_DEF Type
if ((sce = sc->data[SC_LIGHTNINGWALK]) && !(flag & BF_MAGIC) && flag&BF_LONG && rnd() % 100 < sce->val1) {
const int dx[8] = { 0,-1,-1,-1,0,1,1,1 };
const int dy[8] = { 1,1,0,-1,-1,-1,0,1 };
uint8 dir = map_calc_dir(target, src->x, src->y);
if (unit_movepos(target, src->x - dx[dir], src->y - dy[dir], 1, 1)) {
clif_blown(target);
unit_setdir(target, dir);
}
d->dmg_lv = ATK_DEF;
status_change_end(target, SC_LIGHTNINGWALK, INVALID_TIMER);
return false;
}
// Other
if ((sc->data[SC_HERMODE] && flag&BF_MAGIC) ||
(sc->data[SC_TATAMIGAESHI] && (flag&(BF_MAGIC | BF_LONG)) == BF_LONG) ||
(sc->data[SC_MEIKYOUSISUI] && rnd() % 100 < 40)) // custom value
return false;
if ((sce = sc->data[SC_PARRYING]) && flag&BF_WEAPON && skill_id != WS_CARTTERMINATION && rnd() % 100 < sce->val2) {
clif_skill_nodamage(target, target, LK_PARRYING, sce->val1, 1);
if (skill_id == LK_PARRYING) {
unit_data *ud = unit_bl2ud(target);
if (ud != nullptr) // Delay the next attack
ud->attackabletime = gettick() + status_get_adelay(target);
}
return false;
}
if (sc->data[SC_DODGE] && (flag&BF_LONG || sc->data[SC_SPURT]) && (skill_id != NPC_EARTHQUAKE || (skill_id == NPC_EARTHQUAKE && flag & NPC_EARTHQUAKE_FLAG)) && rnd() % 100 < 20) {
map_session_data *sd = map_id2sd(target->id);
if (sd && pc_issit(sd))
pc_setstand(sd, true); //Stand it to dodge.
clif_skill_nodamage(target, target, TK_DODGE, 1, 1);
sc_start4(src, target, SC_COMBO, 100, TK_JUMPKICK, src->id, 1, 0, 2000);
return false;
}
if ((sce = sc->data[SC_KAUPE]) && (skill_id != NPC_EARTHQUAKE || (skill_id == NPC_EARTHQUAKE && flag & NPC_EARTHQUAKE_FLAG)) && rnd() % 100 < sce->val2) { //Kaupe blocks damage (skill or otherwise) from players, mobs, homuns, mercenaries.
clif_specialeffect(target, EF_STORMKICK4, AREA);
//Shouldn't end until Breaker's non-weapon part connects.
#ifndef RENEWAL
if (skill_id != ASC_BREAKER || !(flag&BF_WEAPON))
#endif
if (--sce->val3 <= 0) //We make it work like Safety Wall, even though it only blocks 1 time.
status_change_end(target, SC_KAUPE, INVALID_TIMER);
return false;
}
if (flag&BF_MAGIC && (sce = sc->data[SC_PRESTIGE]) && rnd() % 100 < sce->val2) {
clif_specialeffect(target, EF_STORMKICK4, AREA); // Still need confirm it.
return false;
}
if (((sce = sc->data[SC_UTSUSEMI]) || sc->data[SC_BUNSINJYUTSU]) && flag&BF_WEAPON && !skill_get_inf2(skill_id, INF2_IGNORECICADA)) {
skill_additional_effect(src, target, skill_id, skill_lv, flag, ATK_BLOCK, gettick());
if (!status_isdead(src))
skill_counter_additional_effect(src, target, skill_id, skill_lv, flag, gettick());
if (sce) {
clif_specialeffect(target, EF_STORMKICK4, AREA);
skill_blown(src, target, sce->val3, -1, BLOWN_NONE);
}
//Both need to be consumed if they are active.
if (sce && --sce->val2 <= 0)
status_change_end(target, SC_UTSUSEMI, INVALID_TIMER);
if ((sce = sc->data[SC_BUNSINJYUTSU]) && --sce->val2 <= 0)
status_change_end(target, SC_BUNSINJYUTSU, INVALID_TIMER);
return false;
}
return true;
}
/**
* Check damage through status.
* ATK may be MISS, BLOCKED FAIL, reduc, increase, end status.
* After this we apply bg/gvg reduction
* @param src
* @param bl
* @param d
* @param damage
* @param skill_id
* @param skill_lv
* @return damage
*/
int64 battle_calc_damage(struct block_list *src,struct block_list *bl,struct Damage *d,int64 damage,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = NULL, *tsd = BL_CAST(BL_PC, src);
struct status_change *sc;
struct status_change_entry *sce;
int div_ = d->div_, flag = d->flag;
nullpo_ret(bl);
if( !damage )
return 0;
if( battle_config.ksprotection && mob_ksprotected(src, bl) )
return 0;
if( map_getcell(bl->m, bl->x, bl->y, CELL_CHKMAELSTROM) && skill_id && skill_get_type(skill_id) != BF_MISC
&& skill_get_casttype(skill_id) == CAST_GROUND )
return 0;
if (bl->type == BL_PC) {
sd=(struct map_session_data *)bl;
//Special no damage states
if(flag&BF_WEAPON && sd->special_state.no_weapon_damage)
damage -= damage * sd->special_state.no_weapon_damage / 100;
if(flag&BF_MAGIC && sd->special_state.no_magic_damage)
damage -= damage * sd->special_state.no_magic_damage / 100;
if(flag&BF_MISC && sd->special_state.no_misc_damage)
damage -= damage * sd->special_state.no_misc_damage / 100;
if(!damage)
return 0;
}
sc = status_get_sc(bl); //check target status
if( sc && sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] )
return 1;
if (sc && sc->data[SC_MAXPAIN])
return 0;
switch (skill_id) {
#ifndef RENEWAL
case PA_PRESSURE:
case HW_GRAVITATION:
#endif
case SP_SOULEXPLOSION:
return damage; //These skills bypass everything else.
}
// Nothing can reduce the damage, but Safety Wall and Millennium Shield can block it completely.
// So can defense sphere's but what the heck is that??? [Rytech]
if (skill_id == SJ_NOVAEXPLOSING && !(sc && (sc->data[SC_SAFETYWALL] || sc->data[SC_MILLENNIUMSHIELD])))
return damage;
if( sc && sc->count ) {
if (!battle_status_block_damage(src, bl, sc, d, damage, skill_id, skill_lv)) // Statuses that reduce damage to 0.
return 0;
// Damage increasing effects
#ifdef RENEWAL // Flat +400% damage from melee
if (sc->data[SC_KAITE] && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT)
damage <<= 2;
#endif
if (sc->data[SC_AETERNA] && skill_id != PF_SOULBURN) {
if (src->type != BL_MER || !skill_id)
damage <<= 1; // Lex Aeterna only doubles damage of regular attacks from mercenaries
#ifndef RENEWAL
if( skill_id != ASC_BREAKER || !(flag&BF_WEAPON) )
#endif
status_change_end(bl, SC_AETERNA, INVALID_TIMER); //Shouldn't end until Breaker's non-weapon part connects.
}
#ifdef RENEWAL
if( sc->data[SC_RAID] ) {
if (status_get_class_(bl) == CLASS_BOSS)
damage += damage * 15 / 100;
else
damage += damage * 30 / 100;
}
#endif
if( damage ) {
if( sc->data[SC_DEEPSLEEP] ) {
damage += damage / 2; // 1.5 times more damage while in Deep Sleep.
status_change_end(bl,SC_DEEPSLEEP,INVALID_TIMER);
}
if( tsd && sd && sc->data[SC_CRYSTALIZE] && flag&BF_WEAPON ) {
switch(tsd->status.weapon) {
case W_MACE:
case W_2HMACE:
case W_1HAXE:
case W_2HAXE:
damage += damage / 2;
break;
case W_MUSICAL:
case W_WHIP:
if(!tsd->state.arrow_atk)
break;
case W_BOW:
case W_REVOLVER:
case W_RIFLE:
case W_GATLING:
case W_SHOTGUN:
case W_GRENADE:
case W_DAGGER:
case W_1HSWORD:
case W_2HSWORD:
damage -= damage / 2;
break;
}
}
if( sc->data[SC_VOICEOFSIREN] )
status_change_end(bl,SC_VOICEOFSIREN,INVALID_TIMER);
}
if (sc->data[SC_SOUNDOFDESTRUCTION])
damage <<= 1;
if (sc->data[SC_DARKCROW] && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT) {
int bonus = sc->data[SC_DARKCROW]->val2;
if( sc->data[SC_BURNT] && status_get_element(src) == ELE_FIRE )
damage += damage * 666 / 100; //Custom value
if (status_get_class_(bl) == CLASS_BOSS)
bonus /= 2;
damage += damage * bonus / 100;
}
if (sc->data[SC_HOLY_OIL] && (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON))
damage += damage * 50 / 100;// Need official adjustment. [Rytech]
if (sc->data[SC_SHADOW_SCAR])// Need official adjustment for this too.
damage += damage * (10 * sc->data[SC_SHADOW_SCAR]->val1) / 100;
// Damage reductions
// Assumptio increases DEF on RE mode, otherwise gives a reduction on the final damage. [Igniz]
#ifndef RENEWAL
if( sc->data[SC_ASSUMPTIO] ) {
if( map_flag_vs(bl->m) )
damage = (int64)damage*2/3; //Receive 66% damage
else
damage >>= 1; //Receive 50% damage
}
#endif
if (sc->data[SC_DEFENDER] &&
skill_id != NJ_ZENYNAGE && skill_id != KO_MUCHANAGE &&
#ifdef RENEWAL
((flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON) || skill_id == GN_FIRE_EXPANSION_ACID))
#else
(flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON))
#endif
damage -= damage * sc->data[SC_DEFENDER]->val2 / 100;
if(sc->data[SC_ADJUSTMENT] && (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON))
damage -= damage * 20 / 100;
if(sc->data[SC_FOGWALL] && skill_id != RK_DRAGONBREATH && skill_id != RK_DRAGONBREATH_WATER && skill_id != NPC_DRAGONBREATH) {
if(flag&BF_SKILL) //25% reduction
damage -= damage * 25 / 100;
else if ((flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON))
damage >>= 2; //75% reduction
}
if (sc->data[SC_SPORE_EXPLOSION] && (flag & BF_LONG) == BF_LONG)
damage += damage * (status_get_class(bl) == CLASS_BOSS ? 5 : 10) / 100;
if(sc->data[SC_ARMORCHANGE]) {
//On official servers, SC_ARMORCHANGE does not change DEF/MDEF but rather increases/decreases the damage
if(flag&BF_WEAPON)
damage -= damage * sc->data[SC_ARMORCHANGE]->val2 / 100;
else if(flag&BF_MAGIC)
damage -= damage * sc->data[SC_ARMORCHANGE]->val3 / 100;
}
if(sc->data[SC_SMOKEPOWDER]) {
if( (flag&(BF_SHORT|BF_WEAPON)) == (BF_SHORT|BF_WEAPON) )
damage -= damage * 15 / 100; // 15% reduction to physical melee attacks
else if( (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON) )
damage -= damage * 50 / 100; // 50% reduction to physical ranged attacks
}
if (sc->data[SC_WATER_BARRIER])
damage = damage * 80 / 100; // 20% reduction to all type attacks
if (sc->data[SC_SU_STOOP])
damage -= damage * 90 / 100;
// Compressed code, fixed by map.hpp [Epoque]
if (src->type == BL_MOB) {
std::vector<e_race2> race2 = status_get_race2(src);
for (const auto &raceit : race2) {
switch (raceit) {
case RC2_MANUK:
if (sce = sc->data[SC_MANU_DEF])
damage -= damage * sce->val1 / 100;
break;
case RC2_SPLENDIDE:
if (sce = sc->data[SC_SPL_DEF])
damage -= damage * sce->val1 / 100;
break;
case RC2_OGH_ATK_DEF:
if (sc->data[SC_GLASTHEIM_DEF])
return 0;
break;
case RC2_OGH_HIDDEN:
if (sce = sc->data[SC_GLASTHEIM_HIDDEN])
damage -= damage * sce->val1 / 100;
break;
case RC2_BIO5_ACOLYTE_MERCHANT:
if (sce = sc->data[SC_LHZ_DUN_N1])
damage -= damage * sce->val2 / 100;
break;
case RC2_BIO5_MAGE_ARCHER:
if (sce = sc->data[SC_LHZ_DUN_N2])
damage -= damage * sce->val2 / 100;
break;
case RC2_BIO5_SWORDMAN_THIEF:
if (sce = sc->data[SC_LHZ_DUN_N3])
damage -= damage * sce->val2 / 100;
break;
case RC2_BIO5_MVP:
if (sce = sc->data[SC_LHZ_DUN_N4])
damage -= damage * sce->val2 / 100;
break;
}
}
}
if((sce=sc->data[SC_ARMOR]) && //NPC_DEFENDER
sce->val3&flag && sce->val4&flag)
damage -= damage * sc->data[SC_ARMOR]->val2 / 100;
if( sc->data[SC_ENERGYCOAT] && (skill_id == GN_HELLS_PLANT_ATK ||
#ifdef RENEWAL
((flag&BF_WEAPON || flag&BF_MAGIC) && skill_id != WS_CARTTERMINATION)
#else
(flag&BF_WEAPON && skill_id != WS_CARTTERMINATION)
#endif
) )
{
struct status_data *status = status_get_status_data(bl);
int per = 100*status->sp / status->max_sp -1; //100% should be counted as the 80~99% interval
per /=20; //Uses 20% SP intervals.
//SP Cost: 1% + 0.5% per every 20% SP
if (!status_charge(bl, 0, (10+5*per)*status->max_sp/1000))
status_change_end(bl, SC_ENERGYCOAT, INVALID_TIMER);
damage -= damage * 6 * (1 + per) / 100; //Reduction: 6% + 6% every 20%
}
if(sc->data[SC_GRANITIC_ARMOR])
damage -= damage * sc->data[SC_GRANITIC_ARMOR]->val2 / 100;
if(sc->data[SC_PAIN_KILLER]) {
damage -= sc->data[SC_PAIN_KILLER]->val2;
damage = i64max(damage, 1);
}
if( (sce=sc->data[SC_MAGMA_FLOW]) && (rnd()%100 <= sce->val2) )
skill_castend_damage_id(bl,src,MH_MAGMA_FLOW,sce->val1,gettick(),0);
if( damage > 0 && (sce = sc->data[SC_STONEHARDSKIN]) ) {
if( src->type == BL_MOB ) //using explicit call instead break_equip for duration
sc_start(src,src, SC_STRIPWEAPON, 30, 0, skill_get_time2(RK_STONEHARDSKIN, sce->val1));
else if (flag&(BF_WEAPON|BF_SHORT))
skill_break_equip(src,src, EQP_WEAPON, 3000, BCT_SELF);
}
if (src->type == BL_PC && sc->data[SC_GVG_GOLEM]) {
if (flag&BF_WEAPON)
damage -= damage * sc->data[SC_GVG_GOLEM]->val3 / 100;
if (flag&BF_MAGIC)
damage -= damage * sc->data[SC_GVG_GOLEM]->val4 / 100;
}
#ifdef RENEWAL
// Renewal: steel body reduces all incoming damage to 1/10 [helvetica]
if( sc->data[SC_STEELBODY] )
damage = damage > 10 ? damage / 10 : 1;
#endif
//Finally added to remove the status of immobile when Aimed Bolt is used. [Jobbie]
if( skill_id == RA_AIMEDBOLT && (sc->data[SC_BITE] || sc->data[SC_ANKLE] || sc->data[SC_ELECTRICSHOCKER]) ) {
status_change_end(bl, SC_BITE, INVALID_TIMER);
status_change_end(bl, SC_ANKLE, INVALID_TIMER);
status_change_end(bl, SC_ELECTRICSHOCKER, INVALID_TIMER);
}
if (!damage)
return 0;
if( sd && (sce = sc->data[SC_FORCEOFVANGUARD]) && flag&BF_WEAPON && rnd()%100 < sce->val2 )
pc_addspiritball(sd,skill_get_time(LG_FORCEOFVANGUARD,sce->val1),sce->val3);
if( sd && (sce = sc->data[SC_GT_ENERGYGAIN]) && flag&BF_WEAPON && rnd()%100 < sce->val2 ) {
int spheres = 5;
if( sc->data[SC_RAISINGDRAGON] )
spheres += sc->data[SC_RAISINGDRAGON]->val1;
pc_addspiritball(sd, skill_get_time2(SR_GENTLETOUCH_ENERGYGAIN, sce->val1), spheres);
}
if (sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val1 == MH_MD_GRAPPLING) {
TBL_HOM *hd = BL_CAST(BL_HOM,bl); // We add a sphere for when the Homunculus is being hit
if (hd && (rnd()%100<50) ) // According to WarpPortal, this is a flat 50% chance
hom_addspiritball(hd, 10);
}
if( sc->data[SC__DEADLYINFECT] && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT && damage > 0 && rnd()%100 < 30 + 10 * sc->data[SC__DEADLYINFECT]->val1 )
status_change_spread(bl, src);
} //End of target SC_ check
//SC effects from caster side.
sc = status_get_sc(src);
if (sc && sc->count) {
if( sc->data[SC_INVINCIBLE] && !sc->data[SC_INVINCIBLEOFF] )
damage += damage * 75 / 100;
if ((sce = sc->data[SC_BLOODLUST]) && flag&BF_WEAPON && damage > 0 && rnd()%100 < sce->val3)
status_heal(src, damage * sce->val4 / 100, 0, 3);
if ((sce = sc->data[SC_BLOODSUCKER]) && flag & BF_WEAPON && damage > 0 && rnd() % 100 < (2 * sce->val1 - 1))
status_heal(src, damage * sce->val1 / 100, 0, 3);
if (flag&BF_MAGIC && bl->type == BL_PC && sc->data[SC_GVG_GIANT] && sc->data[SC_GVG_GIANT]->val4)
damage += damage * sc->data[SC_GVG_GIANT]->val4 / 100;
// [Epoque]
if (bl->type == BL_MOB) {
if ((flag&BF_WEAPON) || (flag&BF_MAGIC)) {
std::vector<e_race2> race2 = status_get_race2(bl);
for (const auto &raceit : race2) {
switch (raceit) {
case RC2_MANUK:
if (sce = sc->data[SC_MANU_ATK])
damage += damage * sce->val1 / 100;
break;
case RC2_SPLENDIDE:
if (sce = sc->data[SC_SPL_ATK])
damage += damage * sce->val1 / 100;
break;
case RC2_OGH_ATK_DEF:
if (sc->data[SC_GLASTHEIM_ATK])
damage <<= 1;
break;
case RC2_BIO5_SWORDMAN_THIEF:
if (sce = sc->data[SC_LHZ_DUN_N1])
damage += damage * sce->val1 / 100;
break;
case RC2_BIO5_ACOLYTE_MERCHANT:
if (sce = sc->data[SC_LHZ_DUN_N2])
damage += damage * sce->val1 / 100;
break;
case RC2_BIO5_MAGE_ARCHER:
if (sce = sc->data[SC_LHZ_DUN_N3])
damage += damage * sce->val1 / 100;
break;
case RC2_BIO5_MVP:
if (sce = sc->data[SC_LHZ_DUN_N4])
damage += damage * sce->val1 / 100;
break;
}
}
}
}
if (sc->data[SC_POISONINGWEAPON] && flag&BF_SHORT && (skill_id == 0 || skill_id == GC_VENOMPRESSURE) && damage > 0) {
damage += damage * 10 / 100;
if (rnd() % 100 < sc->data[SC_POISONINGWEAPON]->val3)
sc_start4(src, bl, (sc_type)sc->data[SC_POISONINGWEAPON]->val2, 100, sc->data[SC_POISONINGWEAPON]->val1, 0, 1, 0, skill_get_time2(GC_POISONINGWEAPON, 1));
}
if( sc->data[SC__DEADLYINFECT] && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT && damage > 0 && rnd()%100 < 30 + 10 * sc->data[SC__DEADLYINFECT]->val1 )
status_change_spread(src, bl);
if (sc->data[SC_STYLE_CHANGE] && sc->data[SC_STYLE_CHANGE]->val1 == MH_MD_FIGHTING) {
TBL_HOM *hd = BL_CAST(BL_HOM,src); //when attacking
if (hd && (rnd()%100<50) ) hom_addspiritball(hd, 10); // According to WarpPortal, this is a flat 50% chance
}
if (flag & BF_WEAPON && (sce = sc->data[SC_ADD_ATK_DAMAGE]))
damage += damage * sce->val1 / 100;
if (flag & BF_MAGIC && (sce = sc->data[SC_ADD_MATK_DAMAGE]))
damage += damage * sce->val1 / 100;
if (sc->data[SC_DANCEWITHWUG] && (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON))
damage += damage * sc->data[SC_DANCEWITHWUG]->val1 / 100;
if (sc->data[SC_UNLIMITEDHUMMINGVOICE] && flag&BF_MAGIC)
damage += damage * sc->data[SC_UNLIMITEDHUMMINGVOICE]->val3 / 100;
if (tsd && (sce = sc->data[SC_SOULREAPER])) {
if (rnd()%100 < sce->val2 && tsd->soulball < MAX_SOUL_BALL) {
clif_specialeffect(src, 1208, AREA);
pc_addsoulball(tsd, 5 + 3 * pc_checkskill(tsd, SP_SOULENERGY));
}
}
} //End of caster SC_ check
//PK damage rates
if (battle_config.pk_mode && sd && bl->type == BL_PC && damage && map_getmapflag(bl->m, MF_PVP)) {
if (flag & BF_SKILL) { //Skills get a different reduction than non-skills. [Skotlex]
if (flag&BF_WEAPON)
damage = damage * battle_config.pk_weapon_damage_rate / 100;
if (flag&BF_MAGIC)
damage = damage * battle_config.pk_magic_damage_rate / 100;
if (flag&BF_MISC)
damage = damage * battle_config.pk_misc_damage_rate / 100;
} else { //Normal attacks get reductions based on range.
if (flag & BF_SHORT)
damage = damage * battle_config.pk_short_damage_rate / 100;
if (flag & BF_LONG)
damage = damage * battle_config.pk_long_damage_rate / 100;
}
damage = i64max(damage,1);
}
if(battle_config.skill_min_damage && damage > 0 && damage < div_) {
if ((flag&BF_WEAPON && battle_config.skill_min_damage&1)
|| (flag&BF_MAGIC && battle_config.skill_min_damage&2)
|| (flag&BF_MISC && battle_config.skill_min_damage&4)
)
damage = div_;
}
if (tsd && pc_ismadogear(tsd)) {
short element = skill_get_ele(skill_id, skill_lv);
if( !skill_id || element == ELE_WEAPON ) { //Take weapon's element
struct status_data *sstatus = NULL;
if( src->type == BL_PC && ((TBL_PC*)src)->bonus.arrow_ele )
element = ((TBL_PC*)src)->bonus.arrow_ele;
else if( (sstatus = status_get_status_data(src)) ) {
element = sstatus->rhw.ele;
}
} else if( element == ELE_ENDOWED ) //Use enchantment's element
element = status_get_attack_sc_element(src,status_get_sc(src));
else if( element == ELE_RANDOM ) //Use random element
element = rnd()%ELE_ALL;
pc_overheat(tsd, (element == ELE_FIRE ? 3 : 1));
}
if (bl->type == BL_MOB) { // Reduces damage received for Green Aura MVP
mob_data *md = BL_CAST(BL_MOB, bl);
if (md && md->db->damagetaken != 100)
damage = i64max(damage * md->db->damagetaken / 100, 1);
}
return damage;
}
/**
* Determines whether battleground target can be hit
* @param src: Source of attack
* @param bl: Target of attack
* @param skill_id: Skill ID used
* @param flag: Special flags
* @return Can be hit (true) or can't be hit (false)
*/
bool battle_can_hit_bg_target(struct block_list *src, struct block_list *bl, uint16 skill_id, int flag)
{
struct mob_data* md = BL_CAST(BL_MOB, bl);
struct unit_data *ud = unit_bl2ud(bl);
if (ud && ud->immune_attack)
return false;
if (md && md->bg_id) {
if (status_bl_has_mode(bl, MD_SKILLIMMUNE) && flag&BF_SKILL) //Skill immunity.
return false;
if (src->type == BL_PC) {
struct map_session_data *sd = map_id2sd(src->id);
if (sd && sd->bg_id == md->bg_id)
return false;
}
}
return true;
}
/**
* Calculates BG related damage adjustments.
* @param src
* @param bl
* @param damage
* @param skill_id
* @param flag
* @return damage
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
int64 battle_calc_bg_damage(struct block_list *src, struct block_list *bl, int64 damage, uint16 skill_id, int flag)
{
if( !damage )
return 0;
if (!battle_can_hit_bg_target(src, bl, skill_id, flag))
return 0;
if(skill_get_inf2(skill_id, INF2_IGNOREBGREDUCTION))
return damage; //skill that ignore bg map reduction
if( flag&BF_SKILL ) { //Skills get a different reduction than non-skills. [Skotlex]
if( flag&BF_WEAPON )
damage = damage * battle_config.bg_weapon_damage_rate / 100;
if( flag&BF_MAGIC )
damage = damage * battle_config.bg_magic_damage_rate / 100;
if( flag&BF_MISC )
damage = damage * battle_config.bg_misc_damage_rate / 100;
} else { //Normal attacks get reductions based on range.
if( flag&BF_SHORT )
damage = damage * battle_config.bg_short_damage_rate / 100;
if( flag&BF_LONG )
damage = damage * battle_config.bg_long_damage_rate / 100;
}
damage = i64max(damage,1); //min 1 damage
return damage;
}
/**
* Determines whether target can be hit
* @param src
* @param bl
* @param skill_id
* @param flag
* @return Can be hit (true) or can't be hit (false)
*/
bool battle_can_hit_gvg_target(struct block_list *src,struct block_list *bl,uint16 skill_id,int flag)
{
struct mob_data* md = BL_CAST(BL_MOB, bl);
struct unit_data *ud = unit_bl2ud(bl);
int class_ = status_get_class(bl);
if (ud && ud->immune_attack)
return false;
if(md && (md->guardian_data || md->special_state.ai == AI_GUILD)) {
if ((status_bl_has_mode(bl,MD_SKILLIMMUNE) || (class_ == MOBID_EMPERIUM && !skill_get_inf2(skill_id, INF2_TARGETEMPERIUM))) && flag&BF_SKILL) //Skill immunity.
return false;
if( src->type != BL_MOB || mob_is_clone( ((struct mob_data*)src)->mob_id ) ){
struct guild *g = src->type == BL_PC ? ((TBL_PC *)src)->guild : guild_search(status_get_guild_id(src));
if (class_ == MOBID_EMPERIUM && (!g || guild_checkskill(g,GD_APPROVAL) <= 0 ))
return false;
if (g != nullptr) {
if (battle_config.guild_max_castles && guild_checkcastles(g)>=battle_config.guild_max_castles)
return false; // [MouseJstr]
if (md->special_state.ai == AI_GUILD && g->guild_id == md->master_id)
return false;
}
}
}
return true;
}
/**
* Calculates GVG related damage adjustments.
* @param src
* @param bl
* @param damage
* @param skill_id
* @param flag
* @return damage
*/
int64 battle_calc_gvg_damage(struct block_list *src,struct block_list *bl,int64 damage,uint16 skill_id,int flag)
{
if (!damage) //No reductions to make.
return 0;
if (!battle_can_hit_gvg_target(src,bl,skill_id,flag))
return 0;
if (skill_get_inf2(skill_id, INF2_IGNOREGVGREDUCTION)) //Skills with no gvg damage reduction.
return damage;
if (flag & BF_SKILL) { //Skills get a different reduction than non-skills. [Skotlex]
if (flag&BF_WEAPON)
damage = damage * battle_config.gvg_weapon_damage_rate / 100;
if (flag&BF_MAGIC)
damage = damage * battle_config.gvg_magic_damage_rate / 100;
if (flag&BF_MISC)
damage = damage * battle_config.gvg_misc_damage_rate / 100;
} else { //Normal attacks get reductions based on range.
if (flag & BF_SHORT)
damage = damage * battle_config.gvg_short_damage_rate / 100;
if (flag & BF_LONG)
damage = damage * battle_config.gvg_long_damage_rate / 100;
}
damage = i64max(damage,1);
return damage;
}
/**
* HP/SP drain calculation
* @param damage Damage inflicted to the enemy
* @param rate Success chance 1000 = 100%
* @param per HP/SP drained
* @return diff
*/
static int battle_calc_drain(int64 damage, int rate, int per)
{
int64 diff = 0;
if (per && (rate > 1000 || rnd()%1000 < rate)) {
diff = (damage * per) / 100;
if (diff == 0) {
if (per > 0)
diff = 1;
else
diff = -1;
}
}
return (int)cap_value(diff, INT_MIN, INT_MAX);
}
/**
* Passive skill damage increases
* @param sd
* @param target
* @param dmg
* @param type
* @return damage
*/
int64 battle_addmastery(struct map_session_data *sd,struct block_list *target,int64 dmg,int type)
{
int64 damage;
struct status_data *status = status_get_status_data(target);
int weapon, skill;
#ifdef RENEWAL
damage = 0;
#else
damage = dmg;
#endif
nullpo_ret(sd);
if((skill = pc_checkskill(sd,AL_DEMONBANE)) > 0 &&
target->type == BL_MOB && //This bonus doesn't work against players.
(battle_check_undead(status->race,status->def_ele) || status->race == RC_DEMON) )
damage += (skill*(int)(3+(sd->status.base_level+1)*0.05)); // submitted by orn
if( (skill = pc_checkskill(sd, RA_RANGERMAIN)) > 0 && (status->race == RC_BRUTE || status->race == RC_PLAYER_DORAM || status->race == RC_PLANT || status->race == RC_FISH) )
damage += (skill * 5);
if( (skill = pc_checkskill(sd,NC_RESEARCHFE)) > 0 && (status->def_ele == ELE_FIRE || status->def_ele == ELE_EARTH) )
damage += (skill * 10);
damage += (15 * pc_checkskill(sd, NC_MADOLICENCE)); // Attack bonus is granted even without the Madogear
if((skill = pc_checkskill(sd,HT_BEASTBANE)) > 0 && (status->race == RC_INSECT || status->race == RC_BRUTE || status->race == RC_PLAYER_DORAM) ) {
damage += (skill * 4);
if (sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_HUNTER)
damage += sd->status.str;
}
#ifdef RENEWAL
//Weapon Research bonus applies to all weapons
if((skill = pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0)
damage += (skill * 2);
#endif
// Kagerou/Oboro Spirit Charm bonus
if (sd->spiritcharm >= MAX_SPIRITCHARM) {
if ((sd->spiritcharm_type == CHARM_TYPE_FIRE && status->def_ele == ELE_EARTH) ||
(sd->spiritcharm_type == CHARM_TYPE_WATER && status->def_ele == ELE_FIRE) ||
(sd->spiritcharm_type == CHARM_TYPE_LAND && status->def_ele == ELE_WIND) ||
(sd->spiritcharm_type == CHARM_TYPE_WIND && status->def_ele == ELE_WATER))
damage += damage * 30 / 100;
}
if(type == 0)
weapon = sd->weapontype1;
else
weapon = sd->weapontype2;
switch(weapon) {
case W_1HSWORD:
#ifdef RENEWAL
if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0)
damage += (skill * 3);
#endif
case W_DAGGER:
if((skill = pc_checkskill(sd,SM_SWORD)) > 0)
damage += (skill * 4);
if((skill = pc_checkskill(sd,GN_TRAINING_SWORD)) > 0)
damage += skill * 10;
break;
case W_2HSWORD:
if((skill = pc_checkskill(sd,SM_TWOHAND)) > 0)
damage += (skill * 4);
break;
case W_1HSPEAR:
case W_2HSPEAR:
if((skill = pc_checkskill(sd,KN_SPEARMASTERY)) > 0) {
if(!pc_isriding(sd) && !pc_isridingdragon(sd))
damage += (skill * 4);
else
damage += (skill * 5);
// Increase damage by level of KN_SPEARMASTERY * 10
if(pc_checkskill(sd,RK_DRAGONTRAINING) > 0)
damage += (skill * 10);
}
break;
case W_1HAXE:
case W_2HAXE:
if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0)
damage += (skill * 3);
if((skill = pc_checkskill(sd,NC_TRAININGAXE)) > 0)
damage += (skill * 5);
break;
case W_MACE:
case W_2HMACE:
if((skill = pc_checkskill(sd,PR_MACEMASTERY)) > 0)
damage += (skill * 3);
if((skill = pc_checkskill(sd,NC_TRAININGAXE)) > 0)
damage += (skill * 4);
break;
case W_FIST:
if((skill = pc_checkskill(sd,TK_RUN)) > 0)
damage += (skill * 10);
// No break, fallthrough to Knuckles
case W_KNUCKLE:
if((skill = pc_checkskill(sd,MO_IRONHAND)) > 0)
damage += (skill * 3);
break;
case W_MUSICAL:
if((skill = pc_checkskill(sd,BA_MUSICALLESSON)) > 0)
damage += (skill * 3);
break;
case W_WHIP:
if((skill = pc_checkskill(sd,DC_DANCINGLESSON)) > 0)
damage += (skill * 3);
break;
case W_BOOK:
if((skill = pc_checkskill(sd,SA_ADVANCEDBOOK)) > 0)
damage += (skill * 3);
break;
case W_KATAR:
if((skill = pc_checkskill(sd,AS_KATAR)) > 0)
damage += (skill * 3);
break;
}
return damage;
}
/** Calculates overrefine damage bonus and weapon related bonuses (unofficial)
* @param sd Player
* @param damage Current damage
* @param lr_type EQI_HAND_L:left-hand weapon, EQI_HAND_R:right-hand weapon
*/
static void battle_add_weapon_damage(struct map_session_data *sd, int64 *damage, int lr_type) {
if (!sd)
return;
//rodatazone says that Overrefine bonuses are part of baseatk
//Here we also apply the weapon_damage_rate bonus so it is correctly applied on left/right hands.
if (lr_type == EQI_HAND_L) {
if (sd->left_weapon.overrefine)
(*damage) = (*damage) + rnd() % sd->left_weapon.overrefine + 1;
if (sd->indexed_bonus.weapon_damage_rate[sd->weapontype2])
(*damage) += (*damage) * sd->indexed_bonus.weapon_damage_rate[sd->weapontype2] / 100;
}
else if (lr_type == EQI_HAND_R) {
if (sd->right_weapon.overrefine)
(*damage) = (*damage) + rnd() % sd->right_weapon.overrefine + 1;
if (sd->indexed_bonus.weapon_damage_rate[sd->weapontype1])
(*damage) += (*damage) * sd->indexed_bonus.weapon_damage_rate[sd->weapontype1] / 100;
}
}
#ifdef RENEWAL
static int battle_calc_sizefix(int64 damage, struct map_session_data *sd, unsigned char t_size, unsigned char weapon_type, short flag)
{
if (sd && !sd->special_state.no_sizefix && !flag) // Size fix only for players
damage = damage * (weapon_type == EQI_HAND_L ? sd->left_weapon.atkmods[t_size] : sd->right_weapon.atkmods[t_size]) / 100;
return (int)cap_value(damage, INT_MIN, INT_MAX);
}
static int battle_calc_status_attack(struct status_data *status, short hand)
{
//left-hand penalty on sATK is always 50% [Baalberith]
if (hand == EQI_HAND_L)
return status->batk;
else
return 2 * status->batk;
}
/**
* Calculates renewal Variance, OverUpgradeBonus, and SizePenaltyMultiplier of weapon damage parts for player
* @param src Block list of attacker
* @param tstatus Target's status data
* @param wa Weapon attack data
* @param sd Player
* @return Base weapon damage
*/
static int battle_calc_base_weapon_attack(struct block_list *src, struct status_data *tstatus, struct weapon_atk *wa, struct map_session_data *sd)
{
struct status_data *status = status_get_status_data(src);
uint8 type = (wa == &status->lhw)?EQI_HAND_L:EQI_HAND_R;
uint16 atkmin = (type == EQI_HAND_L)?status->watk2:status->watk;
uint16 atkmax = atkmin;
int64 damage = atkmin;
bool weapon_perfection = false;
struct status_change *sc = status_get_sc(src);
if (sd && sd->equip_index[type] >= 0 && sd->inventory_data[sd->equip_index[type]]) {
short base_stat;
switch (sd->status.weapon) {
case W_BOW:
case W_MUSICAL:
case W_WHIP:
case W_REVOLVER:
case W_RIFLE:
case W_GATLING:
case W_SHOTGUN:
case W_GRENADE:
if (pc_checkskill(sd, SU_SOULATTACK) > 0)
base_stat = status->str;
else
base_stat = status->dex;
break;
default:
base_stat = status->str;
break;
}
float variance = 5.0f * wa->atk * wa->wlv / 100.0f;
float base_stat_bonus = wa->atk * base_stat / 200.0f;
atkmin = max(0, (int)(atkmin - variance + base_stat_bonus));
atkmax = min(UINT16_MAX, (int)(atkmax + variance + base_stat_bonus));
if (sc && sc->data[SC_MAXIMIZEPOWER])
damage = atkmax;
else
damage = rnd_value(atkmin, atkmax);
}
if (sc && sc->data[SC_WEAPONPERFECTION])
weapon_perfection = true;
battle_add_weapon_damage(sd, &damage, type);
damage = battle_calc_sizefix(damage, sd, tstatus->size, type, weapon_perfection);
return (int)cap_value(damage, INT_MIN, INT_MAX);
}
#endif
/*==========================================
* Calculates the standard damage of a normal attack assuming it hits,
* it calculates nothing extra fancy, is needed for magnum break's WATK_ELEMENT bonus. [Skotlex]
* This applies to pre-renewal and non-sd in renewal
*------------------------------------------
* Pass damage2 as NULL to not calc it.
* Flag values:
* &1 : Critical hit
* &2 : Arrow attack
* &4 : Skill is Magic Crasher
* &8 : Skip target size adjustment (Extremity Fist?)
* &16: Arrow attack but BOW, REVOLVER, RIFLE, SHOTGUN, GATLING or GRENADE type weapon not equipped (i.e. shuriken, kunai and venom knives not affected by DEX)
*
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static int64 battle_calc_base_damage(struct block_list *src, struct status_data *status, struct weapon_atk *wa, struct status_change *sc, unsigned short t_size, int flag)
{
unsigned int atkmin = 0, atkmax = 0;
short type = 0;
int64 damage = 0;
struct map_session_data *sd = NULL;
nullpo_retr(damage, src);
sd = BL_CAST(BL_PC, src);
if (!sd) { //Mobs/Pets
#ifndef RENEWAL
if (sc != nullptr && sc->data[SC_CHANGE] != nullptr)
return status->matk_max; // [Aegis] simply uses raw max matk for base damage when Mental Charge active
#endif
if(flag&4) {
atkmin = status->matk_min;
atkmax = status->matk_max;
} else {
atkmin = wa->atk;
atkmax = wa->atk2;
}
if (atkmin > atkmax)
atkmin = atkmax;
} else { //PCs
atkmax = wa->atk;
type = (wa == &status->lhw)?EQI_HAND_L:EQI_HAND_R;
if (!(flag&1) || (flag&2)) { //Normal attacks
atkmin = status->dex;
if (sd->equip_index[type] >= 0 && sd->inventory_data[sd->equip_index[type]] && sd->inventory_data[sd->equip_index[type]]->type == IT_WEAPON)
atkmin = atkmin*(80 + sd->inventory_data[sd->equip_index[type]]->weapon_level*20)/100;
if (atkmin > atkmax)
atkmin = atkmax;
if(flag&2 && !(flag&16)) { //Bows
atkmin = atkmin*atkmax/100;
if (atkmin > atkmax)
atkmax = atkmin;
}
}
}
if (sc && sc->data[SC_MAXIMIZEPOWER])
atkmin = atkmax;
//Weapon Damage calculation
if (!(flag&1))
damage = (atkmax>atkmin? rnd()%(atkmax-atkmin):0)+atkmin;
else
damage = atkmax;
if (sd) {
//rodatazone says the range is 0~arrow_atk-1 for non crit
if (flag&2 && sd->bonus.arrow_atk)
damage += ( (flag&1) ? sd->bonus.arrow_atk : rnd()%sd->bonus.arrow_atk );
// Size fix only for players
if (!(sd->special_state.no_sizefix || (flag&8)))
damage = damage * (type == EQI_HAND_L ? sd->left_weapon.atkmods[t_size] : sd->right_weapon.atkmods[t_size]) / 100;
} else if (src->type == BL_ELEM) {
struct status_change *ele_sc = status_get_sc(src);
int ele_class = status_get_class(src);
if (ele_sc) {
switch (ele_class) {
case ELEMENTALID_AGNI_S:
case ELEMENTALID_AGNI_M:
case ELEMENTALID_AGNI_L:
case ELEMENTALID_ARDOR:
if (ele_sc->data[SC_FIRE_INSIGNIA] && ele_sc->data[SC_FIRE_INSIGNIA]->val1 == 1)
damage += damage * 20 / 100;
break;
case ELEMENTALID_AQUA_S:
case ELEMENTALID_AQUA_M:
case ELEMENTALID_AQUA_L:
case ELEMENTALID_DILUVIO:
if (ele_sc->data[SC_WATER_INSIGNIA] && ele_sc->data[SC_WATER_INSIGNIA]->val1 == 1)
damage += damage * 20 / 100;
break;
case ELEMENTALID_VENTUS_S:
case ELEMENTALID_VENTUS_M:
case ELEMENTALID_VENTUS_L:
case ELEMENTALID_PROCELLA:
if (ele_sc->data[SC_WIND_INSIGNIA] && ele_sc->data[SC_WIND_INSIGNIA]->val1 == 1)
damage += damage * 20 / 100;
break;
case ELEMENTALID_TERA_S:
case ELEMENTALID_TERA_M:
case ELEMENTALID_TERA_L:
case ELEMENTALID_TERREMOTUS:
case ELEMENTALID_SERPENS:
if (ele_sc->data[SC_EARTH_INSIGNIA] && ele_sc->data[SC_EARTH_INSIGNIA]->val1 == 1)
damage += damage * 20 / 100;
break;
}
}
}
//Finally, add baseatk
if(flag&4)
damage += status->matk_min;
else
damage += status->batk;
if (sd)
battle_add_weapon_damage(sd, &damage, type);
#ifdef RENEWAL
if (flag&1)
damage = (damage * 14) / 10;
#endif
return damage;
}
/*==========================================
* Consumes ammo for the given skill.
*------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
void battle_consume_ammo(struct map_session_data*sd, int skill, int lv)
{
int qty = 1;
if (!battle_config.arrow_decrement)
return;
if (skill) {
qty = skill_get_ammo_qty(skill, lv);
if (!qty) qty = 1;
}
if (sd->equip_index[EQI_AMMO] >= 0) //Qty check should have been done in skill_check_condition
pc_delitem(sd,sd->equip_index[EQI_AMMO],qty,0,1,LOG_TYPE_CONSUME);
sd->state.arrow_atk = 0;
}
static int battle_range_type(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv)
{
// [Akinari] , [Xynvaroth]: Traps are always short range.
if (skill_get_inf2(skill_id, INF2_ISTRAP))
return BF_SHORT;
switch (skill_id) {
case AC_SHOWER:
case AM_DEMONSTRATION:
// When monsters use Arrow Shower or Bomb, it is always short range
if (src->type == BL_MOB)
return BF_SHORT;
break;
#ifdef RENEWAL
case KN_BRANDISHSPEAR:
// Renewal changes to ranged physical damage
#endif
case SR_RAMPAGEBLASTER:
case DK_HACKANDSLASHER_ATK: // 2 cell cast range.
return BF_LONG;
case NJ_KIRIKAGE: // Cast range mimics NJ_SHADOWJUMP but damage is considered melee
case GC_CROSSIMPACT: // Cast range is 7 cells and player jumps to target but skill is considered melee
case DK_SERVANT_W_PHANTOM: // 9 cell cast range.
case SHC_SAVAGE_IMPACT: // 7 cell cast range.
case SHC_FATAL_SHADOW_CROW: // 9 cell cast range.
case MT_RUSH_QUAKE: // 9 cell cast range.
case ABC_UNLUCKY_RUSH: // 7 cell cast range.
//case ABC_DEFT_STAB: // 2 cell cast range???
return BF_SHORT;
case CD_PETITIO: { // Skill range is 2 but damage is melee with books and ranged with mace.
map_session_data *sd = BL_CAST(BL_PC, src);
if (sd && (sd->status.weapon == W_MACE || sd->status.weapon == W_2HMACE))
return BF_LONG;
}
}
//Skill Range Criteria
if (battle_config.skillrange_by_distance &&
(src->type&battle_config.skillrange_by_distance)
) { //based on distance between src/target [Skotlex]
if (check_distance_bl(src, target, 3))
return BF_SHORT;
return BF_LONG;
}
//based on used skill's range
if (skill_get_range2(src, skill_id, skill_lv, true) < 4)
return BF_SHORT;
return BF_LONG;
}
static int battle_blewcount_bonus(struct map_session_data *sd, uint16 skill_id)
{
if (sd->skillblown.empty())
return 0;
//Apply the bonus blewcount. [Skotlex]
for (const auto &it : sd->skillblown) {
if (it.id == skill_id)
return it.val;
}
return 0;
}
static enum e_skill_damage_type battle_skill_damage_type( struct block_list* bl ){
switch( bl->type ){
case BL_PC:
return SKILLDMG_PC;
case BL_MOB:
if( status_get_class_(bl) == CLASS_BOSS ){
return SKILLDMG_BOSS;
}else{
return SKILLDMG_MOB;
}
default:
return SKILLDMG_OTHER;
}
}
/**
* Gets skill damage rate from a skill (based on skill_damage_db.txt)
* @param src
* @param target
* @param skill_id
* @return Skill damage rate
*/
static int battle_skill_damage_skill(struct block_list *src, struct block_list *target, uint16 skill_id) {
std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id);
if (!skill || !skill->damage.map)
return 0;
s_skill_damage *damage = &skill->damage;
//check the adjustment works for specified type
if (!(damage->caster&src->type))
return 0;
map_data *mapdata = map_getmapdata(src->m);
if ((damage->map&1 && (!mapdata->flag[MF_PVP] && !mapdata_flag_gvg2(mapdata) && !mapdata->flag[MF_BATTLEGROUND] && !mapdata->flag[MF_SKILL_DAMAGE] && !mapdata->flag[MF_RESTRICTED])) ||
(damage->map&2 && mapdata->flag[MF_PVP]) ||
(damage->map&4 && mapdata_flag_gvg2(mapdata)) ||
(damage->map&8 && mapdata->flag[MF_BATTLEGROUND]) ||
(damage->map&16 && mapdata->flag[MF_SKILL_DAMAGE]) ||
(damage->map&mapdata->zone && mapdata->flag[MF_RESTRICTED]))
{
return damage->rate[battle_skill_damage_type(target)];
}
return 0;
}
/**
* Gets skill damage rate from a skill (based on 'skill_damage' mapflag)
* @param src
* @param target
* @param skill_id
* @return Skill damage rate
*/
static int battle_skill_damage_map(struct block_list *src, struct block_list *target, uint16 skill_id) {
map_data *mapdata = map_getmapdata(src->m);
if (!mapdata || !mapdata->flag[MF_SKILL_DAMAGE])
return 0;
int rate = 0;
// Damage rate for all skills at this map
if (mapdata->damage_adjust.caster&src->type)
rate = mapdata->damage_adjust.rate[battle_skill_damage_type(target)];
if (mapdata->skill_damage.empty())
return rate;
// Damage rate for specified skill at this map
if (mapdata->skill_damage.find(skill_id) != mapdata->skill_damage.end() && mapdata->skill_damage[skill_id].caster&src->type) {
rate += mapdata->skill_damage[skill_id].rate[battle_skill_damage_type(target)];
}
return rate;
}
/**
* Check skill damage adjustment based on mapflags and skill_damage_db.txt for specified skill
* @param src
* @param target
* @param skill_id
* @return Total damage rate
*/
static int battle_skill_damage(struct block_list *src, struct block_list *target, uint16 skill_id) {
nullpo_ret(src);
if (!target || !skill_id)
return 0;
skill_id = skill_dummy2skill_id(skill_id);
return battle_skill_damage_skill(src, target, skill_id) + battle_skill_damage_map(src, target, skill_id);
}
/**
* Calculates Minstrel/Wanderer bonus for Chorus skills.
* @param sd: Player who has Chorus skill active
* @return Bonus value based on party count
*/
int battle_calc_chorusbonus(struct map_session_data *sd) {
#ifdef RENEWAL // No bonus in renewal
return 0;
#endif
int members = 0;
if (!sd || !sd->status.party_id)
return 0;
members = party_foreachsamemap(party_sub_count_class, sd, 0, MAPID_THIRDMASK, MAPID_MINSTRELWANDERER);
if (members < 3)
return 0; // Bonus remains 0 unless 3 or more Minstrels/Wanderers are in the party.
if (members > 7)
return 5; // Maximum effect possible from 7 or more Minstrels/Wanderers.
return members - 2; // Effect bonus from additional Minstrels/Wanderers if not above the max possible.
}
struct Damage battle_calc_magic_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag);
struct Damage battle_calc_misc_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag);
/*=======================================================
* Should infinite defense be applied on target? (plant)
*-------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
* flag - see e_battle_flag
*/
bool is_infinite_defense(struct block_list *target, int flag)
{
struct status_data *tstatus = status_get_status_data(target);
if(target->type == BL_SKILL) {
TBL_SKILL *su = ((TBL_SKILL*)target);
if (su && su->group && (su->group->skill_id == NPC_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD))
return true;
}
if(status_has_mode(tstatus,MD_IGNOREMELEE) && (flag&(BF_WEAPON|BF_SHORT)) == (BF_WEAPON|BF_SHORT) )
return true;
if(status_has_mode(tstatus,MD_IGNOREMAGIC) && flag&(BF_MAGIC) )
return true;
if(status_has_mode(tstatus,MD_IGNORERANGED) && (flag&(BF_WEAPON|BF_LONG)) == (BF_WEAPON|BF_LONG) )
return true;
if(status_has_mode(tstatus,MD_IGNOREMISC) && flag&(BF_MISC) )
return true;
return false;
}
/*========================
* Is attack arrow based?
*------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool is_skill_using_arrow(struct block_list *src, int skill_id)
{
if(src != NULL) {
struct status_data *sstatus = status_get_status_data(src);
struct map_session_data *sd = BL_CAST(BL_PC, src);
return ((sd && sd->state.arrow_atk) || (!sd && ((skill_id && skill_get_ammotype(skill_id)) || sstatus->rhw.range>3)) || (skill_id == HT_PHANTASMIC) || (skill_id == GS_GROUNDDRIFT));
} else
return false;
}
/*=========================================
* Is attack right handed? By default yes.
*-----------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool is_attack_right_handed(struct block_list *src, int skill_id)
{
if(src != NULL) {
struct map_session_data *sd = BL_CAST(BL_PC, src);
//Skills ALWAYS use ONLY your right-hand weapon (tested on Aegis 10.2)
if(!skill_id && sd && sd->weapontype1 == W_FIST && sd->weapontype2 != W_FIST)
return false;
}
return true;
}
/*=======================================
* Is attack left handed? By default no.
*---------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool is_attack_left_handed(struct block_list *src, int skill_id)
{
if(src != NULL) {
//Skills ALWAYS use ONLY your right-hand weapon (tested on Aegis 10.2)
if(!skill_id) {
struct map_session_data *sd = BL_CAST(BL_PC, src);
if (sd) {
if (sd->weapontype1 == W_FIST && sd->weapontype2 != W_FIST)
return true;
if (sd->status.weapon == W_KATAR)
return true;
}
struct status_data *sstatus = status_get_status_data(src);
if (sstatus->lhw.atk)
return true;
}
}
return false;
}
/*=============================
* Do we score a critical hit?
*-----------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool is_attack_critical(struct Damage* wd, struct block_list *src, struct block_list *target, int skill_id, int skill_lv, bool first_call)
{
if (!first_call)
return (wd->type == DMG_CRITICAL || wd->type == DMG_MULTI_HIT_CRITICAL);
#ifdef RENEWAL
if (skill_id == NPC_CRITICALSLASH || skill_id == LG_PINPOINTATTACK) //Always critical skills
return true;
#endif
if( skill_id && !skill_get_nk(skill_id,NK_CRITICAL) )
return false;
struct status_data *sstatus = status_get_status_data(src);
if( sstatus->cri )
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
if(wd->type == DMG_MULTI_HIT){ //Multiple Hit Attack Skills.
if(pc_checkskill(sd,GS_CHAINACTION) && !skill_get_nk(GS_CHAINACTION,NK_CRITICAL)) //Chain Action
return false;
if(pc_checkskill(sd,TF_DOUBLE) && !skill_get_nk(TF_DOUBLE,NK_CRITICAL)) //Double Attack
return false;
}
struct status_data *tstatus = status_get_status_data(target);
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct map_session_data *tsd = BL_CAST(BL_PC, target);
short cri = sstatus->cri;
if (sd) {
cri += sd->indexed_bonus.critaddrace[tstatus->race] + sd->indexed_bonus.critaddrace[RC_ALL];
if(!skill_id && is_skill_using_arrow(src, skill_id)) {
cri += sd->bonus.arrow_cri;
cri += sd->bonus.critical_rangeatk;
}
}
if(sc && sc->data[SC_CAMOUFLAGE])
cri += 100 * min(10,sc->data[SC_CAMOUFLAGE]->val3); //max 100% (1K)
//The official equation is *2, but that only applies when sd's do critical.
//Therefore, we use the old value 3 on cases when an sd gets attacked by a mob
cri -= tstatus->luk * ((!sd && tsd) ? 3 : 2);
if( tsc && tsc->data[SC_SLEEP] )
cri <<= 1;
switch(skill_id) {
case 0:
if(sc && !sc->data[SC_AUTOCOUNTER])
break;
clif_specialeffect(src, EF_AUTOCOUNTER, AREA);
status_change_end(src, SC_AUTOCOUNTER, INVALID_TIMER);
case KN_AUTOCOUNTER:
if(battle_config.auto_counter_type &&
(battle_config.auto_counter_type&src->type))
return true;
else
cri <<= 1;
break;
case SN_SHARPSHOOTING:
case MA_SHARPSHOOTING:
#ifdef RENEWAL
cri += 300; // !TODO: Confirm new bonus
#else
cri += 200;
#endif
break;
case NJ_KIRIKAGE:
cri += 250 + 50*skill_lv;
break;
#ifdef RENEWAL
case ASC_BREAKER:
#endif
case LG_CANNONSPEAR:
case GC_CROSSIMPACT:
case SHC_SAVAGE_IMPACT:
case SHC_ETERNAL_SLASH:
case SHC_IMPACT_CRATER:
cri /= 2;
break;
case WH_GALESTORM:
if (sc && !sc->data[SC_CALAMITYGALE])
return false;
}
if(tsd && tsd->bonus.critical_def)
cri = cri * ( 100 - tsd->bonus.critical_def ) / 100;
return (rnd()%1000 < cri);
}
return false;
}
/*==========================================================
* Is the attack piercing? (Investigate/Ice Pick in pre-re)
*----------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static int is_attack_piercing(struct Damage* wd, struct block_list *src, struct block_list *target, int skill_id, int skill_lv, short weapon_position)
{
if (skill_id == MO_INVESTIGATE || skill_id == RL_MASS_SPIRAL)
return 2;
if(src != NULL) {
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_data *tstatus = status_get_status_data(target);
if( skill_id != PA_SACRIFICE && skill_id != CR_GRANDCROSS && skill_id != NPC_GRANDDARKNESS && skill_id != PA_SHIELDCHAIN && skill_id != KO_HAPPOKUNAI
#ifndef RENEWAL
&& !is_attack_critical(wd, src, target, skill_id, skill_lv, false)
#endif
)
{ //Elemental/Racial adjustments
if( sd && (sd->right_weapon.def_ratio_atk_ele & (1<<tstatus->def_ele) || sd->right_weapon.def_ratio_atk_ele & (1<<ELE_ALL) ||
sd->right_weapon.def_ratio_atk_race & (1<<tstatus->race) || sd->right_weapon.def_ratio_atk_race & (1<<RC_ALL) ||
sd->right_weapon.def_ratio_atk_class & (1<<tstatus->class_) || sd->right_weapon.def_ratio_atk_class & (1<<CLASS_ALL))
)
if (weapon_position == EQI_HAND_R)
return 1;
if(sd && (sd->left_weapon.def_ratio_atk_ele & (1<<tstatus->def_ele) || sd->left_weapon.def_ratio_atk_ele & (1<<ELE_ALL) ||
sd->left_weapon.def_ratio_atk_race & (1<<tstatus->race) || sd->left_weapon.def_ratio_atk_race & (1<<RC_ALL) ||
sd->left_weapon.def_ratio_atk_class & (1<<tstatus->class_) || sd->left_weapon.def_ratio_atk_class & (1<<CLASS_ALL))
)
{ //Pass effect onto right hand if configured so. [Skotlex]
if (battle_config.left_cardfix_to_right && is_attack_right_handed(src, skill_id)){
if (weapon_position == EQI_HAND_R)
return 1;
}
else if (weapon_position == EQI_HAND_L)
return 1;
}
}
}
return 0;
}
static std::bitset<NK_MAX> battle_skill_get_damage_properties(uint16 skill_id, int is_splash)
{
if (skill_id == 0) {
if (is_splash) {
std::bitset<NK_MAX> tmp_nk;
tmp_nk.set(NK_IGNOREATKCARD);
tmp_nk.set(NK_IGNOREFLEE);
return tmp_nk;
} else
return 0;
} else
return skill_db.find(skill_id)->nk;
}
/*=============================
* Checks if attack is hitting
*-----------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool is_attack_hitting(struct Damage* wd, struct block_list *src, struct block_list *target, int skill_id, int skill_lv, bool first_call)
{
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct map_session_data *sd = BL_CAST(BL_PC, src);
std::bitset<NK_MAX> nk = battle_skill_get_damage_properties(skill_id, wd->miscflag);
short flee, hitrate;
if (!first_call)
return (wd->dmg_lv != ATK_FLEE);
if (is_attack_critical(wd, src, target, skill_id, skill_lv, false))
return true;
else if(sd && sd->bonus.perfect_hit > 0 && rnd()%100 < sd->bonus.perfect_hit)
return true;
else if (sc && sc->data[SC_FUSION])
return true;
else if ((skill_id == AS_SPLASHER || skill_id == GN_SPORE_EXPLOSION) && !wd->miscflag)
return true;
else if (skill_id == CR_SHIELDBOOMERANG && sc && sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_CRUSADER )
return true;
else if (tsc && tsc->opt1 && tsc->opt1 != OPT1_STONEWAIT && tsc->opt1 != OPT1_BURNING)
return true;
else if (nk[NK_IGNOREFLEE])
return true;
if( tsc && tsc->data[SC_NEUTRALBARRIER] && (wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG )
return false;
flee = tstatus->flee;
#ifdef RENEWAL
hitrate = 0; //Default hitrate
#else
hitrate = 80; //Default hitrate
#endif
if(battle_config.agi_penalty_type && battle_config.agi_penalty_target&target->type) {
unsigned char attacker_count = unit_counttargeted(target); //256 max targets should be a sane max
if(attacker_count >= battle_config.agi_penalty_count) {
if (battle_config.agi_penalty_type == 1)
flee = (flee * (100 - (attacker_count - (battle_config.agi_penalty_count - 1)) * battle_config.agi_penalty_num)) / 100;
else //assume type 2: absolute reduction
flee -= (attacker_count - (battle_config.agi_penalty_count - 1)) * battle_config.agi_penalty_num;
if(flee < 1)
flee = 1;
}
}
hitrate += sstatus->hit - flee;
//Fogwall's hit penalty is only for normal ranged attacks.
if ((wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG && !skill_id && tsc && tsc->data[SC_FOGWALL])
hitrate -= 50;
if(sd && is_skill_using_arrow(src, skill_id))
hitrate += sd->bonus.arrow_hit;
#ifdef RENEWAL
if (sd) //in Renewal hit bonus from Vultures Eye is not anymore shown in status window
hitrate += pc_checkskill(sd,AC_VULTURE);
#endif
if(skill_id) {
switch(skill_id) { //Hit skill modifiers
//It is proven that bonus is applied on final hitrate, not hit.
case SM_BASH:
case MS_BASH:
hitrate += hitrate * 5 * skill_lv / 100;
break;
case MS_MAGNUM:
case SM_MAGNUM:
hitrate += hitrate * 10 * skill_lv / 100;
break;
case KN_AUTOCOUNTER:
case PA_SHIELDCHAIN:
case NPC_WATERATTACK:
case NPC_GROUNDATTACK:
case NPC_FIREATTACK:
case NPC_WINDATTACK:
case NPC_POISONATTACK:
case NPC_HOLYATTACK:
case NPC_DARKNESSATTACK:
case NPC_UNDEADATTACK:
case NPC_TELEKINESISATTACK:
case NPC_EARTHQUAKE:
case NPC_BLEEDING:
hitrate += hitrate * 20 / 100;
break;
case NPC_FIREBREATH:
case NPC_ICEBREATH:
case NPC_THUNDERBREATH:
case NPC_ACIDBREATH:
case NPC_DARKNESSBREATH:
hitrate *= 2;
break;
case KN_PIERCE:
case ML_PIERCE:
hitrate += hitrate * 5 * skill_lv / 100;
break;
case AS_SONICBLOW:
if(sd && pc_checkskill(sd,AS_SONICACCEL) > 0)
#ifdef RENEWAL
hitrate += hitrate * 90 / 100;
#else
hitrate += hitrate * 50 / 100;
#endif
break;
#ifdef RENEWAL
case RG_BACKSTAP:
hitrate += skill_lv; // !TODO: What's the rate increase?
break;
#endif
case RK_SONICWAVE:
hitrate += hitrate * 3 * skill_lv / 100; // !TODO: Confirm the hitrate bonus
break;
case MC_CARTREVOLUTION:
case GN_CART_TORNADO:
case GN_CARTCANNON:
if (sd && pc_checkskill(sd, GN_REMODELING_CART))
hitrate += pc_checkskill(sd, GN_REMODELING_CART) * 4;
break;
case LG_BANISHINGPOINT:
hitrate += 3 * skill_lv;
break;
case GC_VENOMPRESSURE:
hitrate += 10 + 4 * skill_lv;
break;
case SC_FATALMENACE:
if (skill_lv < 6)
hitrate -= 35 - 5 * skill_lv;
else if (skill_lv > 6)
hitrate += 5 * skill_lv - 30;
break;
case RL_SLUGSHOT:
{
int8 dist = distance_bl(src, target);
if (dist > 3) {
// Reduce n hitrate for each cell after initial 3 cells. Different each level
// -10:-9:-8:-7:-6
dist -= 3;
hitrate -= ((11 - skill_lv) * dist);
}
}
break;
}
} else if (sd && wd->type&DMG_MULTI_HIT && wd->div_ == 2) // +1 hit per level of Double Attack on a successful double attack (making sure other multi attack skills do not trigger this) [helvetica]
hitrate += pc_checkskill(sd,TF_DOUBLE);
if (sd) {
int skill = 0;
// Weaponry Research hidden bonus
if ((skill = pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0)
hitrate += hitrate * ( 2 * skill ) / 100;
if( (sd->status.weapon == W_1HSWORD || sd->status.weapon == W_DAGGER) &&
(skill = pc_checkskill(sd, GN_TRAINING_SWORD))>0 )
hitrate += 3 * skill;
}
if (sc) {
if (sc->data[SC_MTF_ASPD])
hitrate += sc->data[SC_MTF_ASPD]->val2;
if (sc->data[SC_MTF_ASPD2])
hitrate += sc->data[SC_MTF_ASPD2]->val2;
}
hitrate = cap_value(hitrate, battle_config.min_hitrate, battle_config.max_hitrate);
return (rnd()%100 < hitrate);
}
/*==========================================
* If attack ignores def.
*------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool attack_ignores_def(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, short weapon_position)
{
struct status_data *tstatus = status_get_status_data(target);
struct status_change *sc = status_get_sc(src);
struct map_session_data *sd = BL_CAST(BL_PC, src);
std::bitset<NK_MAX> nk = battle_skill_get_damage_properties(skill_id, wd->miscflag);
#ifndef RENEWAL
if (is_attack_critical(wd, src, target, skill_id, skill_lv, false))
return true;
else
#endif
if (sc && sc->data[SC_FUSION])
return true;
else if (skill_id != CR_GRANDCROSS && skill_id != NPC_GRANDDARKNESS)
{ //Ignore Defense?
if (sd && (sd->right_weapon.ignore_def_ele & (1<<tstatus->def_ele) || sd->right_weapon.ignore_def_ele & (1<<ELE_ALL) ||
sd->right_weapon.ignore_def_race & (1<<tstatus->race) || sd->right_weapon.ignore_def_race & (1<<RC_ALL) ||
sd->right_weapon.ignore_def_class & (1<<tstatus->class_) || sd->right_weapon.ignore_def_class & (1<<CLASS_ALL)))
if (weapon_position == EQI_HAND_R)
return true;
if (sd && (sd->left_weapon.ignore_def_ele & (1<<tstatus->def_ele) || sd->left_weapon.ignore_def_ele & (1<<ELE_ALL) ||
sd->left_weapon.ignore_def_race & (1<<tstatus->race) || sd->left_weapon.ignore_def_race & (1<<RC_ALL) ||
sd->left_weapon.ignore_def_class & (1<<tstatus->class_) || sd->left_weapon.ignore_def_class & (1<<CLASS_ALL)))
{
if(battle_config.left_cardfix_to_right && is_attack_right_handed(src, skill_id)) {//Move effect to right hand. [Skotlex]
if (weapon_position == EQI_HAND_R)
return true;
} else if (weapon_position == EQI_HAND_L)
return true;
}
} else if (skill_id == RK_WINDCUTTER && sd && sd->status.weapon == W_2HSWORD)
return true;
return nk[NK_IGNOREDEFENSE] != 0;
}
/*================================================
* Should skill attack consider VVS and masteries?
*------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static bool battle_skill_stacks_masteries_vvs(uint16 skill_id)
{
if (
#ifndef RENEWAL
skill_id == PA_SHIELDCHAIN || skill_id == CR_SHIELDBOOMERANG ||
#endif
skill_id == RK_DRAGONBREATH || skill_id == RK_DRAGONBREATH_WATER || skill_id == NC_SELFDESTRUCTION ||
skill_id == LG_SHIELDPRESS || skill_id == LG_EARTHDRIVE)
return false;
return true;
}
#ifdef RENEWAL
/*========================================
* Calculate equipment ATK for renewal ATK
*----------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static int battle_calc_equip_attack(struct block_list *src, int skill_id)
{
if(src != NULL) {
int eatk = 0;
struct status_data *status = status_get_status_data(src);
struct map_session_data *sd = BL_CAST(BL_PC, src);
if (sd) // add arrow atk if using an applicable skill
eatk += (is_skill_using_arrow(src, skill_id) ? sd->bonus.arrow_atk : 0);
return eatk + status->eatk;
}
return 0; // shouldn't happen but just in case
}
#endif
/*========================================
* Returns the element type of attack
*----------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static int battle_get_weapon_element(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, short weapon_position, bool calc_for_damage_only)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_change *sc = status_get_sc(src);
struct status_data *sstatus = status_get_status_data(src);
int element = skill_get_ele(skill_id, skill_lv);
//Take weapon's element
if( !skill_id || element == ELE_WEAPON ) {
if (weapon_position == EQI_HAND_R)
element = sstatus->rhw.ele;
else
element = sstatus->lhw.ele;
if(is_skill_using_arrow(src, skill_id) && sd && sd->bonus.arrow_ele && weapon_position == EQI_HAND_R)
element = sd->bonus.arrow_ele;
if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm >= MAX_SPIRITCHARM)
element = sd->spiritcharm_type; // Summoning 10 spiritcharm will endow your weapon
// on official endows override all other elements [helvetica]
if(sc && sc->data[SC_ENCHANTARMS]) // Check for endows
element = sc->data[SC_ENCHANTARMS]->val1;
} else if( element == ELE_ENDOWED ) //Use enchantment's element
element = status_get_attack_sc_element(src,sc);
else if( element == ELE_RANDOM ) //Use random element
element = rnd()%ELE_ALL;
switch( skill_id ) {
case GS_GROUNDDRIFT:
element = wd->miscflag; //element comes in flag.
break;
case LK_SPIRALPIERCE:
if (!sd)
element = ELE_NEUTRAL; //forced neutral for monsters
break;
case RK_DRAGONBREATH:
if (sc) {
if (sc->data[SC_LUXANIMA]) // Lux Anima has priority over Giant Growth
element = ELE_DARK;
else if (sc->data[SC_GIANTGROWTH])
element = ELE_HOLY;
}
break;
case RK_DRAGONBREATH_WATER:
if (sc) {
if (sc->data[SC_LUXANIMA]) // Lux Anima has priority over Fighting Spirit
element = ELE_NEUTRAL;
else if (sc->data[SC_FIGHTINGSPIRIT])
element = ELE_GHOST;
}
break;
case LG_HESPERUSLIT:
if (sc && sc->data[SC_BANDING] && sc->data[SC_BANDING]->val2 > 4)
element = ELE_HOLY;
break;
case GN_CARTCANNON:
case NC_ARMSCANNON:
if (sd && sd->state.arrow_atk > 0)
element = sd->bonus.arrow_ele;
break;
case SJ_PROMINENCEKICK:
element = ELE_FIRE;
break;
case RL_H_MINE:
if (sd && sd->flicker) //Force RL_H_MINE deals fire damage if activated by RL_FLICKER
element = ELE_FIRE;
break;
}
if (sc && sc->data[SC_GOLDENE_FERSE] && ((!skill_id && (rnd() % 100 < sc->data[SC_GOLDENE_FERSE]->val4)) || skill_id == MH_STAHL_HORN))
element = ELE_HOLY;
// calc_flag means the element should be calculated for damage only
if (calc_for_damage_only)
return element;
#ifdef RENEWAL
if (skill_id == CR_SHIELDBOOMERANG)
element = ELE_NEUTRAL;
#endif
return element;
}
/*========================================
* Do element damage modifier calculation
*----------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_element_damage(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv)
{
std::bitset<NK_MAX> nk = battle_skill_get_damage_properties(skill_id, wd->miscflag);
// Elemental attribute fix
if(!nk[NK_IGNOREELEMENT] && (wd->damage > 0 || wd->damage2 > 0)) {
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_change *sc = status_get_sc(src);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
int left_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_L, true);
int right_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_R, true);
switch (skill_id) {
case PA_SACRIFICE:
case RK_DRAGONBREATH:
case RK_DRAGONBREATH_WATER:
case NC_SELFDESTRUCTION:
case HFLI_SBR44:
wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv);
if (is_attack_left_handed(src, skill_id))
wd->damage2 = battle_attr_fix(src, target, wd->damage2, left_element, tstatus->def_ele, tstatus->ele_lv);
break;
default:
if (skill_id == 0 && (battle_config.attack_attr_none & src->type))
return; // Non-player basic attacks are non-elemental, they deal 100% against all defense elements
#ifdef RENEWAL
if (sd == nullptr) { // Renewal player's elemental damage calculation is already done before this point, only calculate for everything else
#endif
wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv);
if (is_attack_left_handed(src, skill_id))
wd->damage2 = battle_attr_fix(src, target, wd->damage2, left_element, tstatus->def_ele, tstatus->ele_lv);
#ifdef RENEWAL
}
#endif
break;
}
// Forced to neutral skills [helvetica]
// Skills forced to neutral gain benefits from weapon element but final damage is considered "neutral" and resistances are applied again
switch (skill_id) {
#ifdef RENEWAL
case MO_INVESTIGATE:
case CR_SHIELDBOOMERANG:
case PA_SHIELDCHAIN:
#endif
case MC_CARTREVOLUTION:
case HW_MAGICCRASHER:
case SR_FALLENEMPIRE:
case SR_CRESCENTELBOW_AUTOSPELL:
case SR_GATEOFHELL:
case GN_FIRE_EXPANSION_ACID:
wd->damage = battle_attr_fix(src, target, wd->damage, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv);
if (is_attack_left_handed(src, skill_id))
wd->damage2 = battle_attr_fix(src, target, wd->damage2, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv);
break;
}
#ifdef RENEWAL
if (sd == nullptr) { // Only monsters have a single ATK for element, in pre-renewal we also apply element to entire ATK on players [helvetica]
#endif
if (sc && sc->data[SC_WATK_ELEMENT]) { // Descriptions indicate this means adding a percent of a normal attack in another element [Skotlex]
int64 damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, (is_skill_using_arrow(src, skill_id) ? 2 : 0)) * sc->data[SC_WATK_ELEMENT]->val2 / 100;
wd->damage += battle_attr_fix(src, target, damage, sc->data[SC_WATK_ELEMENT]->val1, tstatus->def_ele, tstatus->ele_lv);
if (is_attack_left_handed(src, skill_id)) {
damage = battle_calc_base_damage(src, sstatus, &sstatus->lhw, sc, tstatus->size, (is_skill_using_arrow(src, skill_id) ? 2 : 0)) * sc->data[SC_WATK_ELEMENT]->val2 / 100;
wd->damage2 += battle_attr_fix(src, target, damage, sc->data[SC_WATK_ELEMENT]->val1, tstatus->def_ele, tstatus->ele_lv);
}
}
#ifdef RENEWAL
}
#endif
}
}
#define ATK_RATE(damage, damage2, a) do { int64 rate_ = (a); (damage) = (damage) * rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) = (damage2) * rate_ / 100; } while(0);
#define ATK_RATE2(damage, damage2, a , b) do { int64 rate_ = (a), rate2_ = (b); (damage) = (damage) *rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) = (damage2) * rate2_ / 100; } while(0);
#define ATK_RATER(damage, a) { (damage) = (damage) * (a) / 100; }
#define ATK_RATEL(damage2, a) { (damage2) = (damage2) * (a) / 100; }
//Adds dmg%. 100 = +100% (double) damage. 10 = +10% damage
#define ATK_ADDRATE(damage, damage2, a) do { int64 rate_ = (a); (damage) += (damage) * rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) += (damage2) *rate_ / 100; } while(0);
#define ATK_ADDRATE2(damage, damage2, a , b) do { int64 rate_ = (a), rate2_ = (b); (damage) += (damage) * rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) += (damage2) * rate2_ / 100; } while(0);
//Adds an absolute value to damage. 100 = +100 damage
#define ATK_ADD(damage, damage2, a) do { int64 rate_ = (a); (damage) += rate_; if(is_attack_left_handed(src, skill_id)) (damage2) += rate_; } while(0);
#define ATK_ADD2(damage, damage2, a , b) do { int64 rate_ = (a), rate2_ = (b); (damage) += rate_; if(is_attack_left_handed(src, skill_id)) (damage2) += rate2_; } while(0);
#ifdef RENEWAL
#define RE_ALLATK_ADD(wd, a) do { int64 a_ = (a); ATK_ADD((wd)->statusAtk, (wd)->statusAtk2, a_); ATK_ADD((wd)->weaponAtk, (wd)->weaponAtk2, a_); ATK_ADD((wd)->equipAtk, (wd)->equipAtk2, a_); ATK_ADD((wd)->masteryAtk, (wd)->masteryAtk2, a_); } while(0);
#define RE_ALLATK_RATE(wd, a) do { int64 a_ = (a); ATK_RATE((wd)->statusAtk, (wd)->statusAtk2, a_); ATK_RATE((wd)->weaponAtk, (wd)->weaponAtk2, a_); ATK_RATE((wd)->equipAtk, (wd)->equipAtk2, a_); ATK_RATE((wd)->masteryAtk, (wd)->masteryAtk2, a_); } while(0);
#define RE_ALLATK_ADDRATE(wd, a) do { int64 a_ = (a); ATK_ADDRATE((wd)->statusAtk, (wd)->statusAtk2, a_); ATK_ADDRATE((wd)->weaponAtk, (wd)->weaponAtk2, a_); ATK_ADDRATE((wd)->equipAtk, (wd)->equipAtk2, a_); ATK_ADDRATE((wd)->masteryAtk, (wd)->masteryAtk2, a_); } while(0);
#else
#define RE_ALLATK_ADD(wd, a) {;}
#define RE_ALLATK_RATE(wd, a) {;}
#define RE_ALLATK_ADDRATE(wd, a) {;}
#endif
/*==================================
* Calculate weapon mastery damages
*----------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_attack_masteries(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_change *sc = status_get_sc(src);
struct status_data *sstatus = status_get_status_data(src);
int t_class = status_get_class(target);
if (sd && battle_skill_stacks_masteries_vvs(skill_id) &&
skill_id != MO_INVESTIGATE &&
skill_id != MO_EXTREMITYFIST &&
skill_id != CR_GRANDCROSS)
{ //Add mastery damage
uint16 skill;
wd->damage = battle_addmastery(sd,target,wd->damage,0);
#ifdef RENEWAL
wd->masteryAtk = battle_addmastery(sd,target,wd->weaponAtk,0);
#endif
if (is_attack_left_handed(src, skill_id)) {
wd->damage2 = battle_addmastery(sd,target,wd->damage2,1);
#ifdef RENEWAL
wd->masteryAtk2 = battle_addmastery(sd,target,wd->weaponAtk2,1);
#endif
}
#ifdef RENEWAL
//General skill masteries
if(skill_id == TF_POISON) //Additional ATK from Envenom is treated as mastery type damage [helvetica]
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 15 * skill_lv);
if (skill_id != MC_CARTREVOLUTION && pc_checkskill(sd, BS_HILTBINDING) > 0)
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 4);
if (skill_id != CR_SHIELDBOOMERANG)
ATK_ADD2(wd->masteryAtk, wd->masteryAtk2, ((wd->div_ < 1) ? 1 : wd->div_) * sd->right_weapon.star, ((wd->div_ < 1) ? 1 : wd->div_) * sd->left_weapon.star);
if (skill_id == MO_FINGEROFFENSIVE) {
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, ((wd->div_ < 1) ? 1 : wd->div_) * sd->spiritball_old * 3);
} else
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, ((wd->div_ < 1) ? 1 : wd->div_) * sd->spiritball * 3);
#endif
if (skill_id == NJ_SYURIKEN && (skill = pc_checkskill(sd,NJ_TOBIDOUGU)) > 0) { // !TODO: Confirm new mastery formula
ATK_ADD(wd->damage, wd->damage2, 3 * skill);
#ifdef RENEWAL
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 3 * skill);
#endif
}
if (skill_id == NV_BREAKTHROUGH) {
ATK_ADD(wd->damage, wd->damage2, 15 * skill_lv + (skill_lv > 4 ? 25 : 0));
#ifdef RENEWAL
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 15 * skill_lv + (skill_lv > 4 ? 25 : 0));
#endif
}
switch(skill_id) {
case RA_WUGDASH:
case RA_WUGSTRIKE:
case RA_WUGBITE:
if (sd) {
skill = pc_checkskill(sd, RA_TOOTHOFWUG);
ATK_ADD(wd->damage, wd->damage2, 30 * skill);
#ifdef RENEWAL
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 30 * skill);
#endif
}
break;
}
if (sc) { // Status change considered as masteries
#ifdef RENEWAL
if (sc->data[SC_NIBELUNGEN]) // With renewal, the level 4 weapon limitation has been removed
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, sc->data[SC_NIBELUNGEN]->val2);
#endif
if(sc->data[SC_CAMOUFLAGE]) {
ATK_ADD(wd->damage, wd->damage2, 30 * min(10, sc->data[SC_CAMOUFLAGE]->val3));
#ifdef RENEWAL
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 30 * min(10, sc->data[SC_CAMOUFLAGE]->val3));
#endif
}
if(sc->data[SC_GN_CARTBOOST]) {
ATK_ADD(wd->damage, wd->damage2, 10 * sc->data[SC_GN_CARTBOOST]->val1);
#ifdef RENEWAL
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 10 * sc->data[SC_GN_CARTBOOST]->val1);
#endif
}
if (sc->data[SC_P_ALTER]) {
ATK_ADD(wd->damage, wd->damage2, sc->data[SC_P_ALTER]->val2);
#ifdef RENEWAL
ATK_ADD(wd->masteryAtk, wd->masteryAtk2, sc->data[SC_P_ALTER]->val2);
#endif
}
}
}
}
#ifdef RENEWAL
/*=========================================
* Calculate the various Renewal ATK parts
*-----------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_damage_parts(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
struct map_session_data *sd = BL_CAST(BL_PC, src);
int right_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_R, false);
int left_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_L, false);
wd->statusAtk += battle_calc_status_attack(sstatus, EQI_HAND_R);
wd->statusAtk2 += battle_calc_status_attack(sstatus, EQI_HAND_L);
if (sd && sd->sc.data[SC_SEVENWIND]) { // Mild Wind applies element to status ATK as well as weapon ATK [helvetica]
wd->statusAtk = battle_attr_fix(src, target, wd->statusAtk, right_element, tstatus->def_ele, tstatus->ele_lv);
wd->statusAtk2 = battle_attr_fix(src, target, wd->statusAtk, left_element, tstatus->def_ele, tstatus->ele_lv);
} else { // status atk is considered neutral on normal attacks [helvetica]
wd->statusAtk = battle_attr_fix(src, target, wd->statusAtk, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv);
wd->statusAtk2 = battle_attr_fix(src, target, wd->statusAtk, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv);
}
wd->weaponAtk += battle_calc_base_weapon_attack(src, tstatus, &sstatus->rhw, sd);
wd->weaponAtk = battle_attr_fix(src, target, wd->weaponAtk, right_element, tstatus->def_ele, tstatus->ele_lv);
wd->weaponAtk2 += battle_calc_base_weapon_attack(src, tstatus, &sstatus->lhw, sd);
wd->weaponAtk2 = battle_attr_fix(src, target, wd->weaponAtk2, left_element, tstatus->def_ele, tstatus->ele_lv);
wd->equipAtk += battle_calc_equip_attack(src, skill_id);
wd->equipAtk = battle_attr_fix(src, target, wd->equipAtk, right_element, tstatus->def_ele, tstatus->ele_lv);
wd->equipAtk2 += battle_calc_equip_attack(src, skill_id);
wd->equipAtk2 = battle_attr_fix(src, target, wd->equipAtk2, left_element, tstatus->def_ele, tstatus->ele_lv);
//Mastery ATK is a special kind of ATK that has no elemental properties
//Because masteries are not elemental, they are unaffected by Ghost armors or Raydric Card
battle_calc_attack_masteries(wd, src, target, skill_id, skill_lv);
wd->damage = 0;
wd->damage2 = 0;
}
#endif
/*==========================================================
* Calculate basic ATK that goes into the skill ATK formula
*----------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_skill_base_damage(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct status_change *sc = status_get_sc(src);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct map_session_data *tsd = BL_CAST(BL_PC, target);
uint16 i;
std::bitset<NK_MAX> nk = battle_skill_get_damage_properties(skill_id, wd->miscflag);
switch (skill_id) { //Calc base damage according to skill
case PA_SACRIFICE:
wd->damage = sstatus->max_hp* 9/100;
wd->damage2 = 0;
#ifdef RENEWAL
wd->weaponAtk = wd->damage;
wd->weaponAtk2 = wd->damage2;
#endif
break;
#ifdef RENEWAL
case LK_SPIRALPIERCE:
case ML_SPIRALPIERCE:
if (sd) {
short index = sd->equip_index[EQI_HAND_R];
if (index >= 0 &&
sd->inventory_data[index] &&
sd->inventory_data[index]->type == IT_WEAPON)
wd->equipAtk += sd->inventory_data[index]->weight*7/100; // weight from spear is treated as equipment ATK on official [helvetica]
battle_calc_damage_parts(wd, src, target, skill_id, skill_lv);
wd->masteryAtk = 0; // weapon mastery is ignored for spiral
switch (tstatus->size) { //Size-fix. Is this modified by weapon perfection?
case SZ_SMALL: //Small: 115%
ATK_RATE(wd->damage, wd->damage2, 115);
RE_ALLATK_RATE(wd, 115);
break;
//case SZ_MEDIUM: //Medium: 100%
case SZ_BIG: //Large: 85%
ATK_RATE(wd->damage, wd->damage2, 85);
RE_ALLATK_RATE(wd, 85);
break;
}
} else {
wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, 0); //Monsters have no weight and use ATK instead
}
#else
case NJ_ISSEN:
wd->damage = 40 * sstatus->str + sstatus->hp * 8 * skill_lv / 100;
wd->damage2 = 0;
break;
case LK_SPIRALPIERCE:
case ML_SPIRALPIERCE:
if (sd) {
short index = sd->equip_index[EQI_HAND_R];
if (index >= 0 &&
sd->inventory_data[index] &&
sd->inventory_data[index]->type == IT_WEAPON)
wd->damage = sd->inventory_data[index]->weight*8/100; //80% of weight
ATK_ADDRATE(wd->damage, wd->damage2, 50*skill_lv); //Skill modifier applies to weight only.
} else {
wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, 0); //Monsters have no weight and use ATK instead
}
i = sstatus->str/10;
i*=i;
ATK_ADD(wd->damage, wd->damage2, i); //Add str bonus.
switch (tstatus->size) { //Size-fix. Is this modified by weapon perfection?
case SZ_SMALL: //Small: 125%
ATK_RATE(wd->damage, wd->damage2, 125);
break;
//case SZ_MEDIUM: //Medium: 100%
case SZ_BIG: //Large: 75%
ATK_RATE(wd->damage, wd->damage2, 75);
break;
}
#endif
break;
case CR_SHIELDBOOMERANG:
case PA_SHIELDCHAIN:
wd->damage = sstatus->batk;
if (sd) {
short index = sd->equip_index[EQI_HAND_L];
if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR) {
ATK_ADD(wd->damage, wd->damage2, sd->inventory_data[index]->weight / 10);
#ifdef RENEWAL
ATK_ADD(wd->weaponAtk, wd->weaponAtk2, sd->inventory_data[index]->weight / 10);
#endif
}
} else
ATK_ADD(wd->damage, wd->damage2, sstatus->rhw.atk2); //Else use Atk2
break;
case RK_DRAGONBREATH:
case RK_DRAGONBREATH_WATER:
{
int damagevalue = (sstatus->hp / 50 + status_get_max_sp(src) / 4) * skill_lv;
if(status_get_lv(src) > 100)
damagevalue = damagevalue * status_get_lv(src) / 100;
if(sd)
damagevalue = damagevalue * (90 + 10 * pc_checkskill(sd, RK_DRAGONTRAINING)) / 100;
if (sc && sc->data[SC_DRAGONIC_AURA])// Need official damage increase. [Rytech]
damagevalue += damagevalue * 50 / 100;
ATK_ADD(wd->damage, wd->damage2, damagevalue);
#ifdef RENEWAL
ATK_ADD(wd->weaponAtk, wd->weaponAtk2, damagevalue);
#endif
wd->flag |= BF_LONG;
}
break;
case NC_SELFDESTRUCTION: {
int damagevalue = (skill_lv + 1) * ((sd ? pc_checkskill(sd,NC_MAINFRAME) : 0) + 8) * (status_get_sp(src) + sstatus->vit);
if(status_get_lv(src) > 100)
damagevalue = damagevalue * status_get_lv(src) / 100;
damagevalue = damagevalue + sstatus->hp;
ATK_ADD(wd->damage, wd->damage2, damagevalue);
#ifdef RENEWAL
ATK_ADD(wd->weaponAtk, wd->weaponAtk2, damagevalue);
#endif
}
break;
case KO_HAPPOKUNAI:
if(sd) {
short index = sd->equip_index[EQI_AMMO];
int damagevalue = 3 * (
#ifdef RENEWAL
2 *
#endif
sstatus->batk + sstatus->rhw.atk + (index >= 0 && sd->inventory_data[index] ?
sd->inventory_data[index]->atk : 0)) * (skill_lv + 5) / 5;
if (sc && sc->data[SC_KAGEMUSYA])
damagevalue += damagevalue * sc->data[SC_KAGEMUSYA]->val2 / 100;
ATK_ADD(wd->damage, wd->damage2, damagevalue);
#ifdef RENEWAL
ATK_ADD(wd->weaponAtk, wd->weaponAtk2, damagevalue);
#endif
} else
ATK_ADD(wd->damage, wd->damage2, 5000);
break;
case HFLI_SBR44: //[orn]
if(src->type == BL_HOM)
wd->damage = ((TBL_HOM*)src)->homunculus.intimacy ;
break;
default:
#ifdef RENEWAL
if (sd)
battle_calc_damage_parts(wd, src, target, skill_id, skill_lv);
else {
i = (is_attack_critical(wd, src, target, skill_id, skill_lv, false)?1:0)|
(!skill_id && sc && sc->data[SC_CHANGE]?4:0);
wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, i);
if (is_attack_left_handed(src, skill_id))
wd->damage2 = battle_calc_base_damage(src, sstatus, &sstatus->lhw, sc, tstatus->size, i);
}
#else
i = (is_attack_critical(wd, src, target, skill_id, skill_lv, false)?1:0)|
(is_skill_using_arrow(src, skill_id)?2:0)|
(skill_id == HW_MAGICCRASHER?4:0)|
(!skill_id && sc && sc->data[SC_CHANGE]?4:0)|
(skill_id == MO_EXTREMITYFIST?8:0)|
(sc && sc->data[SC_WEAPONPERFECTION]?8:0);
if (is_skill_using_arrow(src, skill_id) && sd) {
switch(sd->status.weapon) {
case W_BOW:
case W_REVOLVER:
case W_RIFLE:
case W_GATLING:
case W_SHOTGUN:
case W_GRENADE:
break;
default:
i |= 16; // for ex. shuriken must not be influenced by DEX
break;
}
}
wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, i);
if (is_attack_left_handed(src, skill_id))
wd->damage2 = battle_calc_base_damage(src, sstatus, &sstatus->lhw, sc, tstatus->size, i);
#endif
if (nk[NK_SPLASHSPLIT]){ // Divide ATK among targets
if(wd->miscflag > 0) {
wd->damage /= wd->miscflag;
#ifdef RENEWAL
wd->statusAtk /= wd->miscflag;
wd->weaponAtk /= wd->miscflag;
wd->equipAtk /= wd->miscflag;
wd->masteryAtk /= wd->miscflag;
#endif
} else
ShowError("0 enemies targeted by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id));
}
//Add any bonuses that modify the base atk (pre-skills)
if(sd) {
int skill;
if (sd->bonus.atk_rate) {
ATK_ADDRATE(wd->damage, wd->damage2, sd->bonus.atk_rate);
RE_ALLATK_ADDRATE(wd, sd->bonus.atk_rate);
}
#ifndef RENEWAL
if(sd->bonus.crit_atk_rate && is_attack_critical(wd, src, target, skill_id, skill_lv, false)) { // add +crit damage bonuses here in pre-renewal mode [helvetica]
ATK_ADDRATE(wd->damage, wd->damage2, sd->bonus.crit_atk_rate);
}
#endif
if(sd->status.party_id && (skill=pc_checkskill(sd,TK_POWER)) > 0) {
if( (i = party_foreachsamemap(party_sub_count, sd, 0)) > 1 ) { // exclude the player himself [Inkfish]
// Reduce count by one (self) [Tydus1]
i -= 1;
ATK_ADDRATE(wd->damage, wd->damage2, 2*skill*i);
RE_ALLATK_ADDRATE(wd, 2*skill*i);
}
}
}
#ifndef RENEWAL
if(tsd != nullptr && tsd->bonus.crit_def_rate != 0 && !skill_id && is_attack_critical(wd, src, target, skill_id, skill_lv, false)) {
ATK_ADDRATE(wd->damage, wd->damage2, -tsd->bonus.crit_def_rate);
}
#endif
break;
} //End switch(skill_id)
}
//For quick div adjustment.
#define DAMAGE_DIV_FIX(dmg, div) { if ((div) < 0) { (div) *= -1; (dmg) /= (div); } (dmg) *= (div); }
#define DAMAGE_DIV_FIX2(dmg, div) { if ((div) > 1) (dmg) *= div; }
/*================================================= [Playtester]
* Applies DAMAGE_DIV_FIX and checks for min damage
* @param d: Damage struct to apply DAMAGE_DIV_FIX to
* @param skill_id: ID of the skill that deals damage
* @return Modified damage struct
*------------------------------------------------*/
static void battle_apply_div_fix(struct Damage* d, uint16 skill_id)
{
if(d->damage) {
DAMAGE_DIV_FIX(d->damage, d->div_);
//Min damage
if(d->damage < d->div_ && (skill_id == SU_LUNATICCARROTBEAT || skill_id == SU_LUNATICCARROTBEAT2 || skill_id == SU_CN_METEOR || skill_id == SU_CN_METEOR2 || (battle_config.skill_min_damage&d->flag)))
d->damage = d->div_;
} else if (d->div_ < 0) {
d->div_ *= -1;
}
}
/*=======================================
* Check for and calculate multi attacks
*---------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_multi_attack(struct Damage* wd, struct block_list *src,struct block_list *target, uint16 skill_id, uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct status_data *tstatus = status_get_status_data(target);
if( sd && !skill_id ) { // if no skill_id passed, check for double attack [helvetica]
short i;
if( ( ( skill_lv = pc_checkskill(sd,TF_DOUBLE) ) > 0 && sd->weapontype1 == W_DAGGER )
|| ( sd->bonus.double_rate > 0 && sd->weapontype1 != W_FIST ) // Will fail bare-handed
|| ( sc && sc->data[SC_KAGEMUSYA] && sd->weapontype1 != W_FIST )) // Will fail bare-handed
{ //Success chance is not added, the higher one is used [Skotlex]
int max_rate = 0;
if (sc && sc->data[SC_KAGEMUSYA])
max_rate = sc->data[SC_KAGEMUSYA]->val1 * 10; // Same rate as even levels of TF_DOUBLE
else
#ifdef RENEWAL
max_rate = max(7 * skill_lv, sd->bonus.double_rate);
#else
max_rate = max(5 * skill_lv, sd->bonus.double_rate);
#endif
if( rnd()%100 < max_rate ) {
wd->div_ = skill_get_num(TF_DOUBLE,skill_lv?skill_lv:1);
wd->type = DMG_MULTI_HIT;
}
}
else if( ((sd->weapontype1 == W_REVOLVER && (skill_lv = pc_checkskill(sd,GS_CHAINACTION)) > 0) //Normal Chain Action effect
|| (sc && sc->count && sc->data[SC_E_CHAIN] && (skill_lv = sc->data[SC_E_CHAIN]->val1) > 0)) //Chain Action of ETERNAL_CHAIN
&& rnd()%100 < 5*skill_lv ) //Success rate
{
wd->div_ = skill_get_num(GS_CHAINACTION,skill_lv);
wd->type = DMG_MULTI_HIT;
sc_start(src,src,SC_QD_SHOT_READY,100,target->id,skill_get_time(RL_QD_SHOT,1));
}
else if(sc && sc->data[SC_FEARBREEZE] && sd->weapontype1==W_BOW
&& (i = sd->equip_index[EQI_AMMO]) >= 0 && sd->inventory_data[i] && sd->inventory.u.items_inventory[i].amount > 1)
{
int chance = rnd()%100;
switch(sc->data[SC_FEARBREEZE]->val1) {
case 5: if( chance < 4) { wd->div_ = 5; break; } // 3 % chance to attack 5 times.
case 4: if( chance < 7) { wd->div_ = 4; break; } // 6 % chance to attack 4 times.
case 3: if( chance < 10) { wd->div_ = 3; break; } // 9 % chance to attack 3 times.
case 2:
case 1: if( chance < 13) { wd->div_ = 2; break; } // 12 % chance to attack 2 times.
}
wd->div_ = min(wd->div_,sd->inventory.u.items_inventory[i].amount);
sc->data[SC_FEARBREEZE]->val4 = wd->div_-1;
if (wd->div_ > 1)
wd->type = DMG_MULTI_HIT;
}
}
switch (skill_id) {
case RK_WINDCUTTER:
if (sd && sd->weapontype1 == W_2HSWORD)
wd->div_ = 2;
break;
case SC_FATALMENACE:
if (sd && sd->weapontype1 == W_DAGGER)
wd->div_++;
break;
case SR_RIDEINLIGHTNING:
wd->div_ = (sd ? max(1, skill_lv) : 1);
break;
case RL_QD_SHOT:
wd->div_ = 1 + (sd ? sd->status.job_level : 1) / 20 + (tsc && tsc->data[SC_C_MARKER] ? 2 : 0);
break;
case KO_JYUMONJIKIRI:
if( tsc && tsc->data[SC_JYUMONJIKIRI] )
wd->div_ = wd->div_ * -1;// needs more info
break;
#ifdef RENEWAL
case AS_POISONREACT:
skill_lv = pc_checkskill(sd, TF_DOUBLE);
if (skill_lv > 0) {
if(rnd()%100 < (7 * skill_lv)) {
wd->div_++;
}
}
break;
#endif
}
}
/*======================================================
* Calculate skill level ratios for weapon-based skills
*------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static int battle_calc_attack_skill_ratio(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct map_session_data *tsd = BL_CAST(BL_PC, target);
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
int skillratio = 100;
int i;
//Skill damage modifiers that stack linearly
if(sc && skill_id != PA_SACRIFICE) {
if(sc->data[SC_OVERTHRUST])
skillratio += sc->data[SC_OVERTHRUST]->val3;
if(sc->data[SC_MAXOVERTHRUST])
skillratio += sc->data[SC_MAXOVERTHRUST]->val2;
if(sc->data[SC_BERSERK])
#ifndef RENEWAL
skillratio += 100;
#else
skillratio += 200;
if (sc && sc->data[SC_TRUESIGHT])
skillratio += 2 * sc->data[SC_TRUESIGHT]->val1;
if (sc->data[SC_CONCENTRATION] && (skill_id != RK_DRAGONBREATH && skill_id != RK_DRAGONBREATH_WATER && skill_id != NPC_DRAGONBREATH))
skillratio += sc->data[SC_CONCENTRATION]->val2;
if (sc && sc->data[SC_VIGOR])// Lacking info on how damage is increased. Guessing for now. [Rytech]
skillratio += skillratio * 50 / 100;
#endif
if (!skill_id || skill_id == KN_AUTOCOUNTER) {
if (sc->data[SC_CRUSHSTRIKE]) {
if (sd) { //ATK [{Weapon Level * (Weapon Upgrade Level + 6) * 100} + (Weapon ATK) + (Weapon Weight)]%
short index = sd->equip_index[EQI_HAND_R];
if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_WEAPON)
skillratio += -100 + sd->inventory_data[index]->weight / 10 + sd->inventory_data[index]->atk +
100 * sd->inventory_data[index]->weapon_level * (sd->inventory.u.items_inventory[index].refine + 6);
}
status_change_end(src,SC_CRUSHSTRIKE,INVALID_TIMER);
skill_break_equip(src,src,EQP_WEAPON,2000,BCT_SELF);
} else {
if (sc->data[SC_GIANTGROWTH] && (sd->class_&MAPID_THIRDMASK) == MAPID_RUNE_KNIGHT) { // Increase damage again if Crush Strike is not active
if (map_flag_vs(src->m)) // Only half of the 2.5x increase on versus-type maps
skillratio += 125;
else
skillratio += 250;
}
}
}
}
switch(skill_id) {
case SM_BASH:
case MS_BASH:
skillratio += 30 * skill_lv;
break;
case SM_MAGNUM:
case MS_MAGNUM:
if(wd->miscflag == 1)
skillratio += 20 * skill_lv; //Inner 3x3 circle takes 100%+20%*level damage [Playtester]
else
skillratio += 10 * skill_lv; //Outer 5x5 circle takes 100%+10%*level damage [Playtester]
break;
case MC_MAMMONITE:
skillratio += 50 * skill_lv;
break;
case HT_POWER:
skillratio += -50 + 8 * sstatus->str;
break;
case AC_DOUBLE:
case MA_DOUBLE:
skillratio += 10 * (skill_lv - 1);
break;
case AC_SHOWER:
case MA_SHOWER:
#ifdef RENEWAL
skillratio += 50 + 10 * skill_lv;
#else
skillratio += -25 + 5 * skill_lv;
#endif
break;
case AC_CHARGEARROW:
case MA_CHARGEARROW:
skillratio += 50;
break;
#ifndef RENEWAL
case HT_FREEZINGTRAP:
case MA_FREEZINGTRAP:
skillratio += -50 + 10 * skill_lv;
break;
#endif
case KN_PIERCE:
skillratio += 10 * skill_lv;
if (sc && sc->data[SC_CHARGINGPIERCE_COUNT] && sc->data[SC_CHARGINGPIERCE_COUNT]->val1 >= 10)
skillratio *= 2;
break;
case ML_PIERCE:
skillratio += 10 * skill_lv;
break;
case MER_CRASH:
skillratio += 10 * skill_lv;
break;
case KN_SPEARSTAB:
skillratio += 20 * skill_lv;
break;
case KN_SPEARBOOMERANG:
skillratio += 50 * skill_lv;
break;
#ifdef RENEWAL
case KN_BRANDISHSPEAR:
skillratio += -100 + 400 + 100 * skill_lv + sstatus->str * 3;
break;
#else
case KN_BRANDISHSPEAR:
#endif
case ML_BRANDISH:
{
int ratio = 100 + 20 * skill_lv;
skillratio += -100 + ratio;
if(skill_lv > 3 && wd->miscflag == 0)
skillratio += ratio / 2;
if(skill_lv > 6 && wd->miscflag == 0)
skillratio += ratio / 4;
if(skill_lv > 9 && wd->miscflag == 0)
skillratio += ratio / 8;
if(skill_lv > 6 && wd->miscflag == 1)
skillratio += ratio / 2;
if(skill_lv > 9 && wd->miscflag == 1)
skillratio += ratio / 4;
if(skill_lv > 9 && wd->miscflag == 2)
skillratio += ratio / 2;
}
break;
case KN_BOWLINGBASH:
case MS_BOWLINGBASH:
skillratio += 40 * skill_lv;
break;
case AS_GRIMTOOTH:
skillratio += 20 * skill_lv;
break;
case AS_POISONREACT:
skillratio += 30 * skill_lv;
break;
case AS_SONICBLOW:
#ifdef RENEWAL
skillratio += 100 + 100 * skill_lv;
if (tstatus->hp < tstatus->max_hp >> 1)
skillratio += skillratio / 2;
#else
skillratio += 300 + 40 * skill_lv;
#endif
break;
case TF_SPRINKLESAND:
skillratio += 30;
break;
case MC_CARTREVOLUTION:
skillratio += 50;
if(sd && sd->cart_weight)
skillratio += 100 * sd->cart_weight / sd->cart_weight_max; // +1% every 1% weight
else if (!sd)
skillratio += 100; //Max damage for non players.
break;
case NPC_PIERCINGATT:
skillratio += -25; //75% base damage
break;
case NPC_COMBOATTACK:
skillratio += 25 * skill_lv;
break;
case NPC_RANDOMATTACK:
case NPC_WATERATTACK:
case NPC_GROUNDATTACK:
case NPC_FIREATTACK:
case NPC_WINDATTACK:
case NPC_POISONATTACK:
case NPC_HOLYATTACK:
case NPC_DARKNESSATTACK:
case NPC_UNDEADATTACK:
case NPC_TELEKINESISATTACK:
case NPC_BLOODDRAIN:
case NPC_ACIDBREATH:
case NPC_DARKNESSBREATH:
case NPC_FIREBREATH:
case NPC_ICEBREATH:
case NPC_THUNDERBREATH:
case NPC_HELLJUDGEMENT:
case NPC_PULSESTRIKE:
skillratio += 100 * (skill_lv - 1);
break;
case NPC_REVERBERATION_ATK:
skillratio += 400 + 200 * skill_lv;
break;
case RG_BACKSTAP:
if(sd && sd->status.weapon == W_BOW && battle_config.backstab_bow_penalty)
skillratio += (200 + 40 * skill_lv) / 2;
else
skillratio += 200 + 40 * skill_lv;
break;
case RG_RAID:
#ifdef RENEWAL
skillratio += -100 + 50 + skill_lv * 150;
#else
skillratio += 40 * skill_lv;
#endif
break;
case RG_INTIMIDATE:
skillratio += 30 * skill_lv;
break;
case CR_SHIELDCHARGE:
skillratio += 20 * skill_lv;
break;
case CR_SHIELDBOOMERANG:
#ifdef RENEWAL
skillratio += -100 + skill_lv * 80;
#else
skillratio += 30 * skill_lv;
#endif
break;
case NPC_DARKCROSS:
case CR_HOLYCROSS:
#ifdef RENEWAL
if(sd && sd->status.weapon == W_2HSPEAR)
skillratio += 70 * skill_lv;
else
#endif
skillratio += 35 * skill_lv;
break;
case AM_DEMONSTRATION:
skillratio += 20 * skill_lv;
break;
case AM_ACIDTERROR:
#ifdef RENEWAL
skillratio += -100 + 200 * skill_lv;
if (sd && pc_checkskill(sd, AM_LEARNINGPOTION))
skillratio += 100; // !TODO: What's this bonus increase?
#else
skillratio += 40 * skill_lv;
#endif
break;
case MO_FINGEROFFENSIVE:
#ifdef RENEWAL
skillratio += 500 + skill_lv * 200;
if (tsc && tsc->data[SC_BLADESTOP])
skillratio += skillratio / 2;
#else
skillratio += 50 * skill_lv;
#endif
break;
case MO_INVESTIGATE:
#ifdef RENEWAL
skillratio += -100 + 100 * skill_lv;
if (tsc && tsc->data[SC_BLADESTOP])
skillratio += skillratio / 2;
#else
skillratio += 75 * skill_lv;
#endif
break;
case MO_EXTREMITYFIST:
skillratio += 100 * (7 + sstatus->sp / 10);
#ifdef RENEWAL
if (wd->miscflag&1)
skillratio *= 2; // More than 5 spirit balls active
#endif
skillratio = min(500000,skillratio); //We stop at roughly 50k SP for overflow protection
break;
case MO_TRIPLEATTACK:
skillratio += 20 * skill_lv;
break;
case MO_CHAINCOMBO:
#ifdef RENEWAL
skillratio += 150 + 50 * skill_lv;
if (sd && sd->status.weapon == W_KNUCKLE)
skillratio *= 2;
#else
skillratio += 50 + 50 * skill_lv;
#endif
break;
case MO_COMBOFINISH:
#ifdef RENEWAL
skillratio += 450 + 50 * skill_lv + sstatus->str; // !TODO: How does STR play a role?
#else
skillratio += 140 + 60 * skill_lv;
#endif
if (sc->data[SC_GT_ENERGYGAIN])
skillratio += skillratio * 50 / 100;
break;
case BA_MUSICALSTRIKE:
case DC_THROWARROW:
#ifdef RENEWAL
skillratio += 10 + 40 * skill_lv;
#else
skillratio += 25 + 25 * skill_lv;
#endif
break;
case CH_TIGERFIST:
#ifdef RENEWAL
skillratio += 400 + 150 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += -60 + 100 * skill_lv;
#endif
if (sc->data[SC_GT_ENERGYGAIN])
skillratio += skillratio * 50 / 100;
break;
case CH_CHAINCRUSH:
#ifdef RENEWAL
skillratio += -100 + 200 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += 300 + 100 * skill_lv;
#endif
if (sc->data[SC_GT_ENERGYGAIN])
skillratio += skillratio * 50 / 100;
break;
case CH_PALMSTRIKE:
#ifdef RENEWAL
skillratio += 100 + 100 * skill_lv + sstatus->str; // !TODO: How does STR play a role?
RE_LVL_DMOD(100);
#else
skillratio += 100 + 100 * skill_lv;
#endif
break;
case LK_HEADCRUSH:
skillratio += 40 * skill_lv;
break;
case LK_JOINTBEAT:
skillratio += 10 * skill_lv - 50;
if (wd->miscflag & BREAK_NECK || (tsc && tsc->data[SC_JOINTBEAT] && tsc->data[SC_JOINTBEAT]->val2 & BREAK_NECK)) // The 2x damage is only for the BREAK_NECK ailment.
skillratio <<= 1;
break;
#ifdef RENEWAL
// Renewal: skill ratio applies to entire damage [helvetica]
case LK_SPIRALPIERCE:
skillratio += 50 + 50 * skill_lv;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CHARGINGPIERCE_COUNT] && sc->data[SC_CHARGINGPIERCE_COUNT]->val1 >= 10)
skillratio *= 2;
break;
case ML_SPIRALPIERCE:
skillratio += 50 + 50 * skill_lv;
RE_LVL_DMOD(100);
break;
#endif
case ASC_METEORASSAULT:
#ifdef RENEWAL
skillratio += 100 + 120 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += -60 + 40 * skill_lv;
#endif
break;
case SN_SHARPSHOOTING:
if (src->type == BL_MOB) { // TODO: Did these formulas change in the renewal balancing?
if (wd->miscflag & 2) // Splash damage bonus
skillratio += -100 + 140 * skill_lv;
else
skillratio += 100 + 50 * skill_lv;
break;
}
// Fall through
case MA_SHARPSHOOTING:
#ifdef RENEWAL
skillratio += -100 + 300 + 300 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += 100 + 50 * skill_lv;
#endif
break;
#ifdef RENEWAL
case CR_ACIDDEMONSTRATION:
skillratio += -100 + 200 * skill_lv + sstatus->int_ + tstatus->vit; // !TODO: Confirm status bonus
if (target->type == BL_PC)
skillratio /= 2;
break;
#endif
case CG_ARROWVULCAN:
#ifdef RENEWAL
skillratio += 400 + 100 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += 100 + 100 * skill_lv;
#endif
break;
case AS_SPLASHER:
#ifdef RENEWAL
skillratio += -100 + 400 + 100 * skill_lv;
#else
skillratio += 400 + 50 * skill_lv;
#endif
if(sd)
skillratio += 20 * pc_checkskill(sd,AS_POISONREACT);
break;
case ASC_BREAKER:
#ifdef RENEWAL
skillratio += -100 + 150 * skill_lv + sstatus->str + sstatus->int_; // !TODO: Confirm stat modifier
RE_LVL_DMOD(100);
#else
// Pre-Renewal: skill ratio for weapon part of damage [helvetica]
skillratio += -100 + 100 * skill_lv;
#endif
break;
case PA_SACRIFICE:
skillratio += -10 + 10 * skill_lv;
break;
case PA_SHIELDCHAIN:
#ifdef RENEWAL
skillratio = 60 + 40 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += 30 * skill_lv;
#endif
if (sc && sc->data[SC_SHIELD_POWER])// Whats the official increase? [Rytech]
skillratio += skillratio * 50 / 100;
break;
case WS_CARTTERMINATION:
i = 10 * (16 - skill_lv);
if (i < 1) i = 1;
//Preserve damage ratio when max cart weight is changed.
if (sd && sd->cart_weight)
skillratio += sd->cart_weight / i * 80000 / battle_config.max_cart_weight - 100;
else if (!sd)
skillratio += 80000 / i - 100;
break;
case TK_DOWNKICK:
case TK_STORMKICK:
skillratio += 60 + 20 * skill_lv;
break;
case TK_TURNKICK:
case TK_COUNTER:
skillratio += 90 + 30 * skill_lv;
break;
case TK_JUMPKICK:
//Different damage formulas depending on damage trigger
if (sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == skill_id)
skillratio += -100 + 4 * status_get_lv(src); //Tumble formula [4%*baselevel]
else if (wd->miscflag) {
skillratio += -100 + 4 * status_get_lv(src); //Running formula [4%*baselevel]
if (sc && sc->data[SC_SPURT]) //Spurt formula [8%*baselevel]
skillratio *= 2;
}
else
skillratio += -70 + 10 * skill_lv;
break;
case GS_TRIPLEACTION:
skillratio += 50 * skill_lv;
break;
case GS_BULLSEYE:
//Only works well against brute/demihumans non bosses.
if((tstatus->race == RC_BRUTE || tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_PLAYER_HUMAN || tstatus->race == RC_PLAYER_DORAM) && !status_has_mode(tstatus,MD_STATUSIMMUNE))
skillratio += 400;
break;
case GS_TRACKING:
skillratio += 100 * (skill_lv + 1);
break;
case GS_PIERCINGSHOT:
#ifdef RENEWAL
if (sd && sd->weapontype1 == W_RIFLE)
skillratio += 150 + 30 * skill_lv;
else
skillratio += 100 + 20 * skill_lv;
#else
skillratio += 20 * skill_lv;
#endif
break;
case GS_RAPIDSHOWER:
skillratio += 400 + 50 * skill_lv;
break;
case GS_DESPERADO:
skillratio += 50 * (skill_lv - 1);
if (sc && sc->data[SC_FALLEN_ANGEL])
skillratio *= 2;
break;
case GS_DUST:
skillratio += 50 * skill_lv;
break;
case GS_FULLBUSTER:
skillratio += 100 * (skill_lv + 2);
break;
case GS_SPREADATTACK:
#ifdef RENEWAL
skillratio += 30 * skill_lv;
#else
skillratio += 20 * (skill_lv - 1);
#endif
break;
#ifdef RENEWAL
case GS_GROUNDDRIFT:
skillratio += 100 + 20 * skill_lv;
break;
#endif
case NJ_HUUMA:
#ifdef RENEWAL
skillratio += -150 + 250 * skill_lv;
#else
skillratio += 50 + 150 * skill_lv;
#endif
break;
case NJ_TATAMIGAESHI:
skillratio += 10 * skill_lv;
#ifdef RENEWAL
skillratio *= 2;
#endif
break;
case NJ_KASUMIKIRI:
#ifdef RENEWAL
skillratio += 20 * skill_lv;
#else
skillratio += 10 * skill_lv;
#endif
break;
case NJ_KIRIKAGE:
#ifdef RENEWAL
skillratio += -50 + 150 * skill_lv;
#else
skillratio += 100 * (skill_lv - 1);
#endif
break;
#ifdef RENEWAL
case NJ_SYURIKEN:
skillratio += 5 * skill_lv;
break;
case NJ_KUNAI:
skillratio += -100 + 100 * skill_lv;
break;
case KN_CHARGEATK:
skillratio += 600;
break;
case AS_VENOMKNIFE:
skillratio += 400;
break;
#else
case KN_CHARGEATK: { // +100% every 3 cells of distance but hard-limited to 500%
int k = (wd->miscflag-1)/3;
if (k < 0)
k = 0;
else if (k > 4)
k = 4;
skillratio += 100 * k;
}
break;
#endif
case HT_PHANTASMIC:
#ifdef RENEWAL
skillratio += 400;
#else
skillratio += 50;
#endif
break;
case MO_BALKYOUNG:
#ifdef RENEWAL
skillratio += 700;
#else
skillratio += 200;
#endif
break;
case HFLI_MOON: //[orn]
skillratio += 10 + 110 * skill_lv;
break;
case HFLI_SBR44: //[orn]
skillratio += 100 * (skill_lv - 1);
break;
case NPC_VAMPIRE_GIFT:
skillratio += ((skill_lv - 1) % 5 + 1) * 100;
break;
case RK_SONICWAVE:
skillratio += -100 + 1050 + 150 * skill_lv;
RE_LVL_DMOD(100);
break;
case RK_HUNDREDSPEAR:
skillratio += -100 + 600 + 200 * skill_lv;
if (sd)
skillratio += 50 * pc_checkskill(sd,LK_SPIRALPIERCE);
RE_LVL_DMOD(100);
if (sc) {
if (sc->data[SC_CHARGINGPIERCE_COUNT] && sc->data[SC_CHARGINGPIERCE_COUNT]->val1 >= 10)
skillratio *= 2;
if (sc->data[SC_DRAGONIC_AURA])// Need official damage increase. [Rytech]
skillratio += skillratio * 50 / 100;
}
break;
case RK_WINDCUTTER:
if (sd) {
if (sd->weapontype1 == W_2HSWORD)
skillratio += -100 + 250 * skill_lv;
else if (sd->weapontype1 == W_1HSPEAR || sd->weapontype1 == W_2HSPEAR)
skillratio += -100 + 400 * skill_lv;
else
skillratio += -100 + 300 * skill_lv;
} else
skillratio += -100 + 300 * skill_lv;
RE_LVL_DMOD(100);
break;
case RK_IGNITIONBREAK:
skillratio += -100 + 450 * skill_lv;
RE_LVL_DMOD(100);
break;
case NPC_IGNITIONBREAK:
// 3x3 cell Damage = 1000 1500 2000 2500 3000 %
// 7x7 cell Damage = 750 1250 1750 2250 2750 %
// 11x11 cell Damage = 500 1000 1500 2000 2500 %
i = distance_bl(src,target);
if (i < 2)
skillratio += -100 + 500 * (skill_lv + 1);
else if (i < 4)
skillratio += -100 + 250 + 500 * skill_lv;
else
skillratio += -100 + 500 * skill_lv;
break;
case RK_STORMBLAST:
skillratio += -100 + (((sd) ? pc_checkskill(sd,RK_RUNEMASTERY) : 0) + sstatus->str / 6) * 100; // ATK = [{Rune Mastery Skill Level + (Caster's STR / 6)} x 100] %
RE_LVL_DMOD(100);
break;
case RK_PHANTOMTHRUST: // ATK = [{(Skill Level x 50) + (Spear Master Level x 10)} x Caster's Base Level / 150] %
skillratio += -100 + 50 * skill_lv + 10 * (sd ? pc_checkskill(sd,KN_SPEARMASTERY) : 5);
RE_LVL_DMOD(150); // Base level bonus.
break;
// case NPC_PHANTOMTHRUST: // ATK = 100% for all level
case GC_CROSSIMPACT:
skillratio += -100 + 1400 + 150 * skill_lv;
RE_LVL_DMOD(100);
break;
case GC_COUNTERSLASH:
//ATK [{(Skill Level x 150) + 300} x Caster's Base Level / 120]% + ATK [(AGI x 2) + (Caster's Job Level x 4)]%
skillratio += -100 + 300 + 150 * skill_lv;
RE_LVL_DMOD(120);
break;
case GC_VENOMPRESSURE:
skillratio += 900;
break;
case GC_PHANTOMMENACE:
skillratio += 200;
break;
case GC_ROLLINGCUTTER:
skillratio += -100 + 50 + 80 * skill_lv;
RE_LVL_DMOD(100);
break;
case GC_CROSSRIPPERSLASHER:
skillratio += -100 + 80 * skill_lv + (sstatus->agi * 3);
RE_LVL_DMOD(100);
if (sc && sc->data[SC_ROLLINGCUTTER])
skillratio += sc->data[SC_ROLLINGCUTTER]->val1 * 200;
break;
case GC_DARKCROW:
skillratio += 100 * (skill_lv - 1);
break;
case AB_DUPLELIGHT_MELEE:
skillratio += 50 + 15 * skill_lv;
break;
case NPC_ARROWSTORM:
if (skill_lv > 4)
skillratio += 1900;
else
skillratio += 900;
break;
case NPC_DRAGONBREATH:
if (skill_lv > 5)
skillratio += 500 + 500 * (skill_lv - 5); // Level 6-10 is using water element, like RK_DRAGONBREATH_WATER
else
skillratio += 500 + 500 * skill_lv; // Level 1-5 is using fire element, like RK_DRAGONBREATH
break;
case RA_ARROWSTORM:
if (sc && sc->data[SC_FEARBREEZE])
skillratio += -100 + 200 + 250 * skill_lv;
else
skillratio += -100 + 200 + 180 * skill_lv;
RE_LVL_DMOD(100);
break;
case RA_AIMEDBOLT:
if (sc && sc->data[SC_FEARBREEZE])
skillratio += -100 + 800 + 35 * skill_lv;
else
skillratio += -100 + 500 + 20 * skill_lv;
RE_LVL_DMOD(100);
break;
case RA_CLUSTERBOMB:
skillratio += 100 + 100 * skill_lv;
break;
case RA_WUGDASH:// ATK 300%
skillratio += 200;
break;
case RA_WUGSTRIKE:
skillratio += -100 + 200 * skill_lv;
break;
case RA_WUGBITE:
skillratio += 300 + 200 * skill_lv;
if (skill_lv == 5)
skillratio += 100;
break;
case RA_SENSITIVEKEEN:
skillratio += 50 * skill_lv;
break;
case NC_BOOSTKNUCKLE:
skillratio += 200 * skill_lv + sstatus->dex / 6; // !TODO: What's the DEX bonus?
RE_LVL_DMOD(100);
break;
case NC_PILEBUNKER:
skillratio += 200 + 100 * skill_lv + status_get_str(src);
RE_LVL_DMOD(100);
break;
case NC_VULCANARM:
skillratio += -100 + 140 * skill_lv + sstatus->dex / 6; // !TODO: What's the DEX bonus?
RE_LVL_DMOD(100);
break;
case NC_FLAMELAUNCHER:
case NC_COLDSLOWER:
skillratio += 200 + 300 * skill_lv;
RE_LVL_DMOD(150);
break;
case NC_ARMSCANNON:
skillratio += -100 + 400 + 300 * skill_lv;
RE_LVL_DMOD(100);
break;
case NC_AXEBOOMERANG:
skillratio += 150 + 50 * skill_lv;
if (sd) {
short index = sd->equip_index[EQI_HAND_R];
if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_WEAPON)
skillratio += sd->inventory_data[index]->weight / 10;// Weight is divided by 10 since 10 weight in coding make 1 whole actual weight. [Rytech]
}
RE_LVL_DMOD(100);
break;
case NC_POWERSWING: // According to current sources, only the str + dex gets modified by level [Akinari]
skillratio += -100 + ((sstatus->str + sstatus->dex)/ 2) + 300 + 100 * skill_lv;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_ABR_BATTLE_WARIOR])
skillratio *= 2;
break;
case NC_MAGMA_ERUPTION: // 'Slam' damage
skillratio += 350 + 50 * skill_lv;
break;
case NC_AXETORNADO:
skillratio += -100 + 200 + 180 * skill_lv + sstatus->vit / 6; // !TODO: What's the VIT bonus?
RE_LVL_DMOD(100);
if (sc && sc->data[SC_AXE_STOMP])// Whats the official increase? [Rytech]
skillratio += skillratio * 50 / 100;
break;
case SC_FATALMENACE:
skillratio += 120 * skill_lv + sstatus->agi / 6; // !TODO: What's the AGI bonus?
RE_LVL_DMOD(100);
if (sc && sc->data[SC_ABYSS_DAGGER])
skillratio += skillratio * 50 / 100;
break;
case SC_TRIANGLESHOT:
skillratio += -100 + 230 * skill_lv + 3 * sstatus->agi;
RE_LVL_DMOD(100);
break;
case SC_FEINTBOMB:
skillratio += -100 + (skill_lv + 1) * sstatus->dex / 2 * ((sd) ? sd->status.job_level / 10 : 1);
RE_LVL_DMOD(120);
break;
case LG_CANNONSPEAR:
skillratio += -100 + skill_lv * (50 + sstatus->str);
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SPEAR_SCAR])// Whats the official increase? [Rytech]
skillratio += skillratio * 50 / 100;
break;
case LG_BANISHINGPOINT:
skillratio += -100 + (80 * skill_lv) + ((sd) ? pc_checkskill(sd,SM_BASH) * 30 : 0);
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SPEAR_SCAR])// Whats the official increase? [Rytech]
skillratio += skillratio * 50 / 100;
break;
case LG_SHIELDPRESS:
skillratio += -100 + 200 * skill_lv + sstatus->str;
if (sd) {
short index = sd->equip_index[EQI_HAND_L];
if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR)
skillratio += sd->inventory_data[index]->weight / 10;
}
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SHIELD_POWER])// Whats the official increase? [Rytech]
skillratio += skillratio * 50 / 100;
break;
case LG_PINPOINTATTACK:
skillratio += -100 + 100 * skill_lv + 5 * status_get_agi(src);
RE_LVL_DMOD(120);
break;
case LG_RAGEBURST:
if (sd && sd->spiritball_old)
skillratio += -100 + 200 * sd->spiritball_old + (status_get_max_hp(src) - status_get_hp(src)) / 100;
else
skillratio += 2900 + (status_get_max_hp(src) - status_get_hp(src));
RE_LVL_DMOD(100);
break;
case LG_MOONSLASHER:
skillratio += -100 + 120 * skill_lv + ((sd) ? pc_checkskill(sd,LG_OVERBRAND) * 80 : 0);
RE_LVL_DMOD(100);
break;
case LG_OVERBRAND:
if(sc && sc->data[SC_OVERBRANDREADY])
skillratio += -100 + 450 * skill_lv;
else
skillratio += -100 + 300 * skill_lv;
skillratio += ((sd) ? pc_checkskill(sd, CR_SPEARQUICKEN) * 50 : 0);
RE_LVL_DMOD(100);
break;
case LG_EARTHDRIVE:
skillratio += -100 + 380 * skill_lv + ((sstatus->str + sstatus->vit) / 6); // !TODO: What's the STR/VIT bonus?
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SHIELD_POWER])// Whats the official increase? [Rytech]
skillratio += skillratio * 50 / 100;
break;
case LG_HESPERUSLIT:
if (sc && sc->data[SC_INSPIRATION])
skillratio += -100 + 450 * skill_lv;
else
skillratio += -100 + 300 * skill_lv;
skillratio += sstatus->vit / 6; // !TODO: What's the VIT bonus?
RE_LVL_DMOD(100);
break;
case SR_EARTHSHAKER:
if (tsc && ((tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK)) || tsc->data[SC_CAMOUFLAGE] || tsc->data[SC_STEALTHFIELD] || tsc->data[SC__SHADOWFORM])) {
//[(Skill Level x 300) x (Caster Base Level / 100) + (Caster STR x 3)] %
skillratio += -100 + 300 * skill_lv;
RE_LVL_DMOD(100);
skillratio += status_get_str(src) * 3;
} else { //[(Skill Level x 400) x (Caster Base Level / 100) + (Caster STR x 2)] %
skillratio += -100 + 400 * skill_lv;
RE_LVL_DMOD(100);
skillratio += status_get_str(src) * 2;
}
break;
case SR_DRAGONCOMBO:
skillratio += 100 + 80 * skill_lv;
RE_LVL_DMOD(100);
break;
case SR_FALLENEMPIRE:
// ATK [(Skill Level x 300 + 100) x Caster Base Level / 150] %
skillratio += 300 * skill_lv;
RE_LVL_DMOD(150);
break;
case SR_TIGERCANNON:
{
unsigned int hp = sstatus->max_hp * (12 + (skill_lv * 2)) / 100,
sp = sstatus->max_sp * (5 + skill_lv) / 100;
if (wd->miscflag&8)
// Base_Damage = [((Caster consumed HP + SP) / 2) x Caster Base Level / 100] %
skillratio += -100 + (hp + sp) / 2;
else
// Base_Damage = [((Caster consumed HP + SP) / 4) x Caster Base Level / 100] %
skillratio += -100 + (hp + sp) / 4;
RE_LVL_DMOD(100);
}
if (sc->data[SC_GT_REVITALIZE])
skillratio += skillratio * 30 / 100;
break;
case SR_SKYNETBLOW:
//ATK [{(Skill Level x 200) + (Caster AGI)} x Caster Base Level / 100] %
skillratio += -100 + 200 * skill_lv + sstatus->agi / 6; // !TODO: Confirm AGI bonus
RE_LVL_DMOD(100);
break;
case SR_RAMPAGEBLASTER:
if (tsc && tsc->data[SC_EARTHSHAKER]) {
skillratio += 1400 + 550 * skill_lv;
RE_LVL_DMOD(120);
} else {
skillratio += 900 + 350 * skill_lv;
RE_LVL_DMOD(150);
}
if (sc->data[SC_GT_CHANGE])
skillratio += skillratio * 30 / 100;
break;
case SR_KNUCKLEARROW:
if (wd->miscflag&4) { // ATK [(Skill Level x 150) + (1000 x Target current weight / Maximum weight) + (Target Base Level x 5) x (Caster Base Level / 150)] %
skillratio += -100 + 150 * skill_lv + status_get_lv(target) * 5;
if (tsd && tsd->weight)
skillratio += 100 * tsd->weight / tsd->max_weight;
RE_LVL_DMOD(150);
} else {
if (status_get_class_(target) == CLASS_BOSS)
skillratio += 400 + 200 * skill_lv;
else // ATK [(Skill Level x 100 + 500) x Caster Base Level / 100] %
skillratio += 400 + 100 * skill_lv;
RE_LVL_DMOD(100);
}
if (sc->data[SC_GT_CHANGE])
skillratio += skillratio * 30 / 100;
break;
case SR_WINDMILL: // ATK [(Caster Base Level + Caster DEX) x Caster Base Level / 100] %
skillratio += -100 + status_get_lv(src) + sstatus->dex;
RE_LVL_DMOD(100);
break;
case SR_GATEOFHELL:
if (sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE)
skillratio += -100 + 800 * skill_lv;
else
skillratio += -100 + 500 * skill_lv;
RE_LVL_DMOD(100);
if (sc->data[SC_GT_REVITALIZE])
skillratio += skillratio * 30 / 100;
break;
case SR_GENTLETOUCH_QUIET:
skillratio += -100 + 100 * skill_lv + sstatus->dex;
RE_LVL_DMOD(100);
break;
case SR_HOWLINGOFLION:
skillratio += -100 + 500 * skill_lv;
RE_LVL_DMOD(100);
break;
case SR_RIDEINLIGHTNING:
skillratio += -100 + 40 * skill_lv;
if (sd && sd->status.weapon == W_KNUCKLE)
skillratio += 50 * skill_lv;
RE_LVL_DMOD(100);
break;
case WM_SEVERE_RAINSTORM_MELEE:
//ATK [{(Caster DEX / 300 + AGI / 200)} x Caster Base Level / 100] %
skillratio += -100 + 100 * skill_lv + (sstatus->dex / 300 + sstatus->agi / 200);
if (wd->miscflag&4) // Whip/Instrument equipped
skillratio += 20 * skill_lv;
RE_LVL_DMOD(100);
break;
case WM_GREAT_ECHO:
skillratio += -100 + 250 + 500 * skill_lv;
if (sd) {
skillratio += pc_checkskill(sd, WM_LESSON) * 50; // !TODO: Confirm bonus
if (skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 0) > 0)
skillratio <<= 1;
}
RE_LVL_DMOD(100);
break;
case GN_CART_TORNADO: { // ATK [( Skill Level x 200 ) + ( Cart Weight / ( 150 - Caster Base STR ))] + ( Cart Remodeling Skill Level x 50 )] %
skillratio += -100 + 200 * skill_lv;
if(sd && sd->cart_weight)
skillratio += sd->cart_weight / 10 / (150 - min(sd->status.str,120)) + pc_checkskill(sd,GN_REMODELING_CART) * 50;
if (sc && sc->data[SC_BIONIC_WOODENWARRIOR])
skillratio *= 2;
}
break;
case GN_CARTCANNON:
skillratio += -100 + (250 + 20 * pc_checkskill(sd, GN_REMODELING_CART)) * skill_lv + 2 * sstatus->int_ / (6 - pc_checkskill(sd, GN_REMODELING_CART));
RE_LVL_DMOD(100);
break;
case GN_SPORE_EXPLOSION:
skillratio += -100 + 400 + 200 * skill_lv;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_BIONIC_WOODEN_FAIRY])
skillratio *= 2;
break;
case GN_WALLOFTHORN:
skillratio += 10 * skill_lv;
break;
case GN_CRAZYWEED_ATK:
skillratio += -100 + 700 + 100 * skill_lv;
RE_LVL_DMOD(100);
break;
case GN_SLINGITEM_RANGEMELEEATK:
if( sd ) {
switch( sd->itemid ) {
case ITEMID_APPLE_BOMB:
skillratio += 200 + status_get_str(src) + status_get_dex(src);
break;
case ITEMID_COCONUT_BOMB:
case ITEMID_PINEAPPLE_BOMB:
skillratio += 700 + status_get_str(src) + status_get_dex(src);
break;
case ITEMID_MELON_BOMB:
skillratio += 400 + status_get_str(src) + status_get_dex(src);
break;
case ITEMID_BANANA_BOMB:
skillratio += 777 + status_get_str(src) + status_get_dex(src);
break;
case ITEMID_BLACK_LUMP:
skillratio += -100 + (status_get_str(src) + status_get_agi(src) + status_get_dex(src)) / 3;
break;
case ITEMID_BLACK_HARD_LUMP:
skillratio += -100 + (status_get_str(src) + status_get_agi(src) + status_get_dex(src)) / 2;
break;
case ITEMID_VERY_HARD_LUMP:
skillratio += -100 + status_get_str(src) + status_get_agi(src) + status_get_dex(src);
break;
}
RE_LVL_DMOD(100);
}
break;
case GN_HELLS_PLANT_ATK:
skillratio += -100 + 100 * skill_lv + sstatus->int_ * (sd ? pc_checkskill(sd, AM_CANNIBALIZE) : 5); // !TODO: Confirm INT and Cannibalize bonus
RE_LVL_DMOD(100);
break;
// Physical Elemantal Spirits Attack Skills
case EL_CIRCLE_OF_FIRE:
case EL_FIRE_BOMB_ATK:
case EL_STONE_RAIN:
skillratio += 200;
break;
case EL_FIRE_WAVE_ATK:
skillratio += 500;
break;
case EL_TIDAL_WEAPON:
skillratio += 1400;
break;
case EL_WIND_SLASH:
skillratio += 100;
break;
case EL_HURRICANE:
skillratio += 600;
break;
case EL_TYPOON_MIS:
case EL_WATER_SCREW_ATK:
skillratio += 900;
break;
case EL_STONE_HAMMER:
skillratio += 400;
break;
case EL_ROCK_CRUSHER:
skillratio += 700;
break;
case KO_JYUMONJIKIRI:
skillratio += -100 + 200 * skill_lv;
RE_LVL_DMOD(120);
if(tsc && tsc->data[SC_JYUMONJIKIRI])
skillratio += skill_lv * status_get_lv(src);
if (sc && sc->data[SC_KAGEMUSYA])
skillratio += skillratio * sc->data[SC_KAGEMUSYA]->val2 / 100;
break;
case KO_HUUMARANKA:
skillratio += -100 + 150 * skill_lv + sstatus->str + (sd ? pc_checkskill(sd,NJ_HUUMA) * 100 : 0);
RE_LVL_DMOD(100);
if (sc && sc->data[SC_KAGEMUSYA])
skillratio += skillratio * sc->data[SC_KAGEMUSYA]->val2 / 100;
break;
case KO_SETSUDAN:
skillratio += 100 * (skill_lv - 1);
RE_LVL_DMOD(100);
if (tsc) {
struct status_change_entry *sce;
if ((sce = tsc->data[SC_SPIRIT]) || (sce = tsc->data[SC_SOULGOLEM]) || (sce = tsc->data[SC_SOULSHADOW]) || (sce = tsc->data[SC_SOULFALCON]) || (sce = tsc->data[SC_SOULFAIRY])) // Bonus damage added when target is soul linked.
skillratio += 200 * sce->val1;
}
break;
case KO_BAKURETSU:
skillratio += -100 + (sd ? pc_checkskill(sd,NJ_TOBIDOUGU) : 1) * (50 + sstatus->dex / 4) * skill_lv * 4 / 10;
RE_LVL_DMOD(120);
skillratio += 10 * (sd ? sd->status.job_level : 1);
if (sc && sc->data[SC_KAGEMUSYA])
skillratio += skillratio * sc->data[SC_KAGEMUSYA]->val2 / 100;
break;
case KO_MAKIBISHI:
skillratio += -100 + 20 * skill_lv;
break;
case MH_NEEDLE_OF_PARALYZE:
skillratio += -100 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->dex / 6; // !TODO: Confirm Base Level and DEX bonus
break;
case MH_STAHL_HORN:
skillratio += -100 + 1000 + 300 * skill_lv * status_get_lv(src) / 150 + sstatus->vit / 6; // !TODO: Confirm VIT bonus
break;
case MH_LAVA_SLIDE:
skillratio += -100 + 50 * skill_lv;
break;
case MH_SONIC_CRAW:
skillratio += -100 + 60 * skill_lv * status_get_lv(src) / 150;
break;
case MH_SILVERVEIN_RUSH:
skillratio += -100 + 250 * skill_lv * status_get_lv(src) / 100 + sstatus->str / 6; // !TODO: Confirm STR bonus
break;
case MH_MIDNIGHT_FRENZY:
skillratio += -100 + 450 * skill_lv * status_get_lv(src) / 150 + sstatus->str / 6; // !TODO: Confirm STR bonus
break;
case MH_MAGMA_FLOW:
skillratio += -100 + (100 * skill_lv + 3 * status_get_lv(src)) * status_get_lv(src) / 120;
break;
case RL_MASS_SPIRAL:
skillratio += -100 + 200 * skill_lv;
break;
case RL_FIREDANCE:
skillratio += 100 + 100 * skill_lv;
skillratio += (sd ? pc_checkskill(sd, GS_DESPERADO) * 20 : 0);
RE_LVL_DMOD(100);
break;
case RL_BANISHING_BUSTER:
skillratio += -100 + 1000 + 200 * skill_lv;
RE_LVL_DMOD(100);
break;
case RL_S_STORM:
skillratio += -100 + 1700 + 200 * skill_lv;
break;
case RL_SLUGSHOT:
if (target->type == BL_MOB)
skillratio += -100 + 1200 * skill_lv;
else
skillratio += -100 + 2000 * skill_lv;
skillratio *= 2 + tstatus->size;
break;
case RL_D_TAIL:
skillratio += -100 + 500 + 200 * skill_lv;
if (sd && (wd->miscflag & 8))
skillratio *= 2;
RE_LVL_DMOD(100);
break;
case RL_R_TRIP:
skillratio += -100 + 350 * skill_lv;
RE_LVL_DMOD(100);
break;
case RL_R_TRIP_PLUSATK:
skillratio += -100 + 300 + 300 * skill_lv;
break;
case RL_H_MINE:
if (sd && sd->flicker) // Flicker explosion damage: 500 + 300 * SkillLv
skillratio += -100 + 500 + 300 * skill_lv;
else // 200 + 200 * SkillLv
skillratio += -100 + 200 + 200 * skill_lv;
break;
case RL_HAMMER_OF_GOD:
skillratio += -100 + 100 * skill_lv;
if (sd) {
if (wd->miscflag & 8)
skillratio += 400 * sd->spiritball_old;
else
skillratio += 150 * sd->spiritball_old;
}
RE_LVL_DMOD(100);
break;
case RL_FIRE_RAIN:
case RL_AM_BLAST:
skillratio += -100 + 3500 + 300 * skill_lv;
break;
case SU_BITE:
skillratio += 100;
break;
case SU_SCRATCH:
skillratio += -50 + 50 * skill_lv;
break;
case SU_SCAROFTAROU:
skillratio += -100 + 100 * skill_lv;
if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE))
skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src);
break;
case SU_PICKYPECK:
case SU_PICKYPECK_DOUBLE_ATK:
skillratio += 100 + 100 * skill_lv;
if (status_get_hp(target) < status_get_max_hp(target) >> 1)
skillratio *= 2;
if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE))
skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src);
break;
case SU_LUNATICCARROTBEAT:
case SU_LUNATICCARROTBEAT2:
skillratio += 100 + 100 * skill_lv;
if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE))
skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src);
break;
case SU_SVG_SPIRIT:
skillratio += 150 + 150 * skill_lv;
if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE))
skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src);
break;
case SJ_FULLMOONKICK:
skillratio += 1000 + 100 * skill_lv;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_LIGHTOFMOON])
skillratio += skillratio * sc->data[SC_LIGHTOFMOON]->val2 / 100;
break;
case SJ_NEWMOONKICK:
skillratio += 600 + 100 * skill_lv;
break;
case SJ_STAREMPEROR:
skillratio += 700 + 200 * skill_lv;
break;
case SJ_SOLARBURST:
skillratio += 900 + 220 * skill_lv;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_LIGHTOFSUN])
skillratio += skillratio * sc->data[SC_LIGHTOFSUN]->val2 / 100;
break;
case SJ_PROMINENCEKICK:
skillratio += 50 + 50 * skill_lv;
break;
case SJ_FALLINGSTAR_ATK:
case SJ_FALLINGSTAR_ATK2:
skillratio += 100 * skill_lv;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_LIGHTOFSTAR])
skillratio += skillratio * sc->data[SC_LIGHTOFSTAR]->val2 / 100;
break;
case DK_SERVANTWEAPON_ATK:
skillratio += 50 + 50 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case DK_SERVANT_W_PHANTOM:
skillratio += 100 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case DK_SERVANT_W_DEMOL:
skillratio += 600 + 120 * skill_lv;
RE_LVL_DMOD(100);
break;
case DK_HACKANDSLASHER:
skillratio += -100 + 500 + 250 * skill_lv;
if (sd && sd->status.weapon == W_2HSWORD) {
skillratio += 5 * sstatus->pow;
RE_LVL_DMOD(100); // Only takes place with 2h Sword
}
break;
case DK_HACKANDSLASHER_ATK:
skillratio += 600 + 120 * skill_lv;
if (sd && sd->status.weapon == W_2HSPEAR) {
skillratio += 5 * sstatus->pow;
RE_LVL_DMOD(100); // Only takes place with 2h Spear
}
break;
case DK_DRAGONIC_AURA:
skillratio += 950 * skill_lv + 10 * sstatus->pow;
if (tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_ANGEL)
skillratio += 450 * skill_lv;
RE_LVL_DMOD(100);
break;
case DK_MADNESS_CRUSHER:// How does weight affect the damage? [Rytech]
skillratio += -100 + 450 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CHARGINGPIERCE_COUNT] && sc->data[SC_CHARGINGPIERCE_COUNT]->val1 >= 10)
skillratio *= 2;
break;
case DK_STORMSLASH:
skillratio += -100 + 120 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_GIANTGROWTH])
skillratio *= 2;
break;
case IQ_OLEUM_SANCTUM:
skillratio += -100 + 400 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_MASSIVE_F_BLASTER:
skillratio += -100 + 800 * skill_lv + 10 * sstatus->pow;
if (tstatus->race == RC_BRUTE || tstatus->race == RC_DEMON)
skillratio += 300 * skill_lv;
RE_LVL_DMOD(100);
break;
case IQ_EXPOSION_BLASTER:
skillratio += -100 + 450 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
if (tsc && tsc->data[SC_HOLY_OIL])
skillratio += skillratio * 50 / 100;
break;
case IQ_FIRST_BRAND:
skillratio += -100 + 450 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_SECOND_FLAME:
skillratio += -100 + 550 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_SECOND_FAITH:
skillratio += -100 + 500 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_SECOND_JUDGEMENT:
skillratio += -100 + 500 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_THIRD_PUNISH:
skillratio += -100 + 550 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_THIRD_FLAME_BOMB:
skillratio += -100 + 650 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IQ_THIRD_CONSECRATION:
skillratio += -100 + 650 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case IG_GRAND_JUDGEMENT:
skillratio += -100 + 750 * skill_lv + 10 * sstatus->pow;
if (tstatus->race == RC_PLANT || tstatus->race == RC_INSECT)
skillratio += 350 * skill_lv;
RE_LVL_DMOD(100);
if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0)
skillratio += skillratio * i / 100;
break;
case IG_SHIELD_SHOOTING:
skillratio += -100 + 600 * skill_lv + 5 * sstatus->pow;
if (sd) { // Damage affected by the shield's weight and refine. Need official formula. [Rytech]
short index = sd->equip_index[EQI_HAND_L];
if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR)
skillratio += sd->inventory_data[index]->weight / 20 * sd->inventory.u.items_inventory[index].refine;
}
RE_LVL_DMOD(100);
if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0)
skillratio += skillratio * i / 100;
break;
case IG_OVERSLASH:
skillratio += -100 + 60 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0)
skillratio += skillratio * i / 100;
break;
case CD_EFFLIGO:
skillratio += -100 + 800 * skill_lv + 5 * sstatus->pow;
if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DEMON)
skillratio += 400 * skill_lv;
RE_LVL_DMOD(100);
break;
case CD_PETITIO:
skillratio += -100 + 270 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case SHC_DANCING_KNIFE:
skillratio += -100 + 200 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case SHC_SAVAGE_IMPACT:
skillratio += -100 + 350 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case SHC_ETERNAL_SLASH:
skillratio += -100 + 350 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case SHC_SHADOW_STAB:
skillratio += -100 + 750 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case SHC_IMPACT_CRATER:
skillratio += -100 + 65 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case SHC_FATAL_SHADOW_CROW:
skillratio += -100 + 650 * skill_lv + 10 * sstatus->pow;
if (tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_DRAGON)
skillratio += 300 * skill_lv;
RE_LVL_DMOD(100);
break;
case MT_AXE_STOMP:
skillratio += -100 + 350 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case MT_RUSH_QUAKE:
skillratio += -100 + 750 * skill_lv + 10 * sstatus->pow;
if (tstatus->race == RC_FORMLESS || tstatus->race == RC_INSECT)
skillratio += 350 * skill_lv;
RE_LVL_DMOD(100);
break;
case MT_A_MACHINE:// Formula unknown. Using Dancing Knife's formula for now. [Rytech]
skillratio += -100 + 200 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case ABC_ABYSS_DAGGER:
skillratio += -100 + 550 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case ABC_UNLUCKY_RUSH:
skillratio += -100 + 500 * skill_lv + 5 * sstatus->crt;
RE_LVL_DMOD(100);
break;
case ABC_CHAIN_REACTION_SHOT:
case ABC_CHAIN_REACTION_SHOT_ATK:// Same damage formula? [Rytech]
skillratio += -100 + 600 * skill_lv + 5 * sstatus->con;
RE_LVL_DMOD(100);
break;
case ABC_DEFT_STAB:
skillratio += -100 + 360 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
break;
case ABC_FRENZY_SHOT:
skillratio += -100 + 350 * skill_lv + 5 * sstatus->con;
RE_LVL_DMOD(100);
break;
case WH_HAWKRUSH:
skillratio += -100 + 100 * skill_lv + 5 * sstatus->con;
RE_LVL_DMOD(100);
break;
case WH_HAWKBOOMERANG:// Affected by trait stats??? CON for sure but the other one unknown. Likely POW. [Rytech]
skillratio += -100 + 500 * skill_lv + 10 * sstatus->pow + 10 * sstatus->con;
if (tstatus->race == RC_BRUTE || tstatus->race == RC_FISH)
skillratio += 250 * skill_lv;
RE_LVL_DMOD(100);
break;
case WH_GALESTORM:
skillratio += -100 + 250 * skill_lv + 5 * sstatus->con;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CALAMITYGALE] && (tstatus->race == RC_BRUTE || tstatus->race == RC_FISH))
skillratio += skillratio * 50 / 100;
break;
case WH_CRESCIVE_BOLT:
skillratio += -100 + 300 * skill_lv + 5 * sstatus->con;
RE_LVL_DMOD(100);
if (sc) { // At level 10 the SP usage of 100 increased by 20 on each count. So maybe damage increase is 20%??? [Rytech]
if (sc->data[SC_CRESCIVEBOLT])
skillratio += skillratio * (20 * sc->data[SC_CRESCIVEBOLT]->val1) / 100;
if (sc->data[SC_CALAMITYGALE]) {
skillratio += skillratio * 20 / 100;
if (tstatus->race == RC_BRUTE || tstatus->race == RC_FISH)
skillratio += skillratio * 50 / 100;
}
}
break;
case WH_DEEPBLINDTRAP:
case WH_SOLIDTRAP:
case WH_SWIFTTRAP:
case WH_FLAMETRAP:
skillratio += -100 + 250 * skill_lv + 5 * sstatus->con;
RE_LVL_DMOD(100);
skillratio += skillratio * (20 * (sd ? pc_checkskill(sd, WH_ADVANCED_TRAP) : 5)) / 100;
break;
case BO_ACIDIFIED_ZONE_WATER:
case BO_ACIDIFIED_ZONE_GROUND:
case BO_ACIDIFIED_ZONE_WIND:
case BO_ACIDIFIED_ZONE_FIRE:
case BO_ACIDIFIED_ZONE_WATER_ATK:// These deal the same damage? [Rytech]
case BO_ACIDIFIED_ZONE_GROUND_ATK:
case BO_ACIDIFIED_ZONE_WIND_ATK:
case BO_ACIDIFIED_ZONE_FIRE_ATK:
skillratio += -100 + 250 * skill_lv + 5 * sstatus->pow;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_RESEARCHREPORT]) { // Does this also affect skills like acid demo? [Rytech]
skillratio += skillratio * 50 / 100;
if (tstatus->race == RC_FORMLESS || tstatus->race == RC_PLANT)
skillratio += skillratio * 50 / 100;
// Skill description is sounding a bit too crazy.
// I need more info on this before allowing this part to work to avoid overpowered issues. [Rytech]
//skillratio += 5 * sstatus->pow;
//RE_LVL_DMOD(100);
}
break;
case TR_ROSEBLOSSOM:
case TR_ROSEBLOSSOM_ATK:// Same damage formula? [Rytech]
skillratio += -100 + 500 * skill_lv + (sd ? pc_checkskill(sd, TR_STAGE_MANNER) : 5) * sstatus->con;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_MYSTIC_SYMPHONY]) {
skillratio += skillratio * 40 / 100;
if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN)
skillratio += skillratio * 50 / 100;
}
if (tsc && tsc->data[SC_SOUNDBLEND])
skillratio += skillratio * 50 / 100;
break;
case TR_RHYTHMSHOOTING:
skillratio += -100 + 120 * skill_lv + (sd ? pc_checkskill(sd, TR_STAGE_MANNER) : 5) * sstatus->con;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_MYSTIC_SYMPHONY]) {
skillratio += skillratio * 40 / 100;
if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN)
skillratio += skillratio * 50 / 100;
}
if (tsc && tsc->data[SC_SOUNDBLEND])
skillratio += skillratio * 50 / 100;
break;
case ABR_BATTLE_BUSTER:// Need official formula.
case ABR_DUAL_CANNON_FIRE:// Need official formula.
skillratio += -100 + 8000;
break;
case ABR_INFINITY_BUSTER:// Need official formula.
skillratio += -100 + 50000;
break;
}
return skillratio;
}
/*==================================================================================================
* Constant skill damage additions are added before SC modifiers and after skill base ATK calculation
*--------------------------------------------------------------------------------------------------*
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static int64 battle_calc_skill_constant_addition(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct map_session_data *tsd = BL_CAST(BL_PC, target);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
int64 atk = 0;
//Constant/misc additions from skills
switch (skill_id) {
case MO_EXTREMITYFIST:
atk = 250 + 150 * skill_lv;
break;
#ifndef RENEWAL
case GS_MAGICALBULLET:
if (sstatus->matk_max > sstatus->matk_min)
atk = sstatus->matk_min + rnd()%(sstatus->matk_max - sstatus->matk_min);
else
atk = sstatus->matk_min;
break;
case NJ_SYURIKEN:
atk = 4 * skill_lv;
break;
#endif
#ifdef RENEWAL
case HT_FREEZINGTRAP:
if(sd)
atk = 40 * pc_checkskill(sd, RA_RESEARCHTRAP);
break;
#endif
case GC_COUNTERSLASH:
atk = sstatus->agi * 2 + (sd ? sd->status.job_level * 4 : 0);
break;
case LG_SHIELDPRESS:
if (sd) {
int damagevalue = 0;
short index = sd->equip_index[EQI_HAND_L];
if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR)
damagevalue = sstatus->vit * sd->inventory.u.items_inventory[index].refine;
atk = damagevalue;
}
break;
}
return atk;
}
/*==============================================================
* Stackable SC bonuses added on top of calculated skill damage
*--------------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_attack_sc_bonus(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_change *sc = status_get_sc(src);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
uint8 anger_id = 0; // SLS Anger
// Kagerou/Oboro Earth Charm effect +15% wATK
if(sd && sd->spiritcharm_type == CHARM_TYPE_LAND && sd->spiritcharm > 0) {
ATK_ADDRATE(wd->damage, wd->damage2, 15 * sd->spiritcharm);
#ifdef RENEWAL
ATK_ADDRATE(wd->weaponAtk, wd->weaponAtk2, 15 * sd->spiritcharm);
#endif
}
//The following are applied on top of current damage and are stackable.
if (sc) {
#ifdef RENEWAL
if (sc->data[SC_WATK_ELEMENT] && skill_id != ASC_METEORASSAULT)
ATK_ADDRATE(wd->weaponAtk, wd->weaponAtk2, sc->data[SC_WATK_ELEMENT]->val2);
if (sc->data[SC_DRUMBATTLE])
ATK_ADD(wd->equipAtk, wd->equipAtk2, sc->data[SC_DRUMBATTLE]->val2);
if (sc->data[SC_MADNESSCANCEL])
ATK_ADD(wd->equipAtk, wd->equipAtk2, 100);
if (sc->data[SC_MAGICALBULLET]) {
short tmdef = tstatus->mdef + tstatus->mdef2;
if (sstatus->matk_min > tmdef && sstatus->matk_max > sstatus->matk_min) {
ATK_ADD(wd->weaponAtk, wd->weaponAtk2, i64max((sstatus->matk_min + rnd() % (sstatus->matk_max - sstatus->matk_min)) - tmdef, 0));
} else {
ATK_ADD(wd->weaponAtk, wd->weaponAtk2, i64max(sstatus->matk_min - tmdef, 0));
}
}
if (sc->data[SC_GATLINGFEVER])
ATK_ADD(wd->equipAtk, wd->equipAtk2, sc->data[SC_GATLINGFEVER]->val3);
#else
if (sc->data[SC_TRUESIGHT])
ATK_ADDRATE(wd->damage, wd->damage2, 2 * sc->data[SC_TRUESIGHT]->val1);
#endif
if (sc->data[SC_SPIRIT]) {
if (skill_id == AS_SONICBLOW && sc->data[SC_SPIRIT]->val2 == SL_ASSASIN) {
ATK_ADDRATE(wd->damage, wd->damage2, map_flag_gvg2(src->m) ? 25 : 100); //+25% dmg on woe/+100% dmg on nonwoe
RE_ALLATK_ADDRATE(wd, map_flag_gvg2(src->m) ? 25 : 100); //+25% dmg on woe/+100% dmg on nonwoe
} else if (skill_id == CR_SHIELDBOOMERANG && sc->data[SC_SPIRIT]->val2 == SL_CRUSADER) {
ATK_ADDRATE(wd->damage, wd->damage2, 100);
RE_ALLATK_ADDRATE(wd, 100);
}
}
if (sc->data[SC_GT_CHANGE])
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_GT_CHANGE]->val1);
if (sc->data[SC_EDP]) {
switch(skill_id) {
case AS_SPLASHER:
case ASC_METEORASSAULT:
// Pre-Renewal only: Soul Breaker ignores EDP
// Renewal only: Grimtooth and Venom Knife ignore EDP
// Both: Venom Splasher and Meteor Assault ignore EDP [helvetica]
#ifndef RENEWAL
case ASC_BREAKER:
#else
case AS_GRIMTOOTH:
case AS_VENOMKNIFE:
#endif
break; // skills above have no effect with EDP
#ifdef RENEWAL
default: // fall through to apply EDP bonuses
// Renewal EDP formula [helvetica]
// weapon atk * (1 + (edp level * .8))
// equip atk * (1 + (edp level * .6))
ATK_RATE(wd->weaponAtk, wd->weaponAtk2, 100 + (sc->data[SC_EDP]->val1 * 80));
ATK_RATE(wd->equipAtk, wd->equipAtk2, 100 + (sc->data[SC_EDP]->val1 * 60));
break;
#else
default:
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_EDP]->val3);
#endif
}
}
if (sc->data[SC_DANCEWITHWUG]) {
if (skill_get_inf2(skill_id, INF2_INCREASEDANCEWITHWUGDAMAGE)) {
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_DANCEWITHWUG]->val1 * 10 * battle_calc_chorusbonus(sd));
RE_ALLATK_ADDRATE(wd, sc->data[SC_DANCEWITHWUG]->val1 * 10 * battle_calc_chorusbonus(sd));
}
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_DANCEWITHWUG]->val1 * 2 * battle_calc_chorusbonus(sd));
#ifdef RENEWAL
ATK_ADDRATE(wd->equipAtk, wd->equipAtk2, sc->data[SC_DANCEWITHWUG]->val1 * 2 * battle_calc_chorusbonus(sd));
#endif
}
if(sc->data[SC_ZENKAI] && sstatus->rhw.ele == sc->data[SC_ZENKAI]->val2) {
ATK_ADD(wd->damage, wd->damage2, 200);
#ifdef RENEWAL
ATK_ADD(wd->equipAtk, wd->equipAtk2, 200);
#endif
}
if (sc->data[SC_EQC]) {
ATK_ADDRATE(wd->damage, wd->damage2, -sc->data[SC_EQC]->val2);
#ifdef RENEWAL
ATK_ADDRATE(wd->equipAtk, wd->equipAtk2, -sc->data[SC_EQC]->val2);
#endif
}
if(sc->data[SC_STYLE_CHANGE]) {
TBL_HOM *hd = BL_CAST(BL_HOM,src);
if(hd) {
ATK_ADD(wd->damage, wd->damage2, hd->homunculus.spiritball * 3);
RE_ALLATK_ADD(wd, hd->homunculus.spiritball * 3);
}
}
if(sc->data[SC_UNLIMIT] && (wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG) {
switch(skill_id) {
case RA_WUGDASH:
case RA_WUGSTRIKE:
case RA_WUGBITE:
break;
default:
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_UNLIMIT]->val2);
RE_ALLATK_ADDRATE(wd, sc->data[SC_UNLIMIT]->val2);
break;
}
}
if (sc->data[SC_HEAT_BARREL]) {
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_HEAT_BARREL]->val3);
RE_ALLATK_ADDRATE(wd, sc->data[SC_HEAT_BARREL]->val3);
}
if((wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG) {
if (sc->data[SC_MTF_RANGEATK]) { // Monster Transformation bonus
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_MTF_RANGEATK]->val1);
RE_ALLATK_ADDRATE(wd, sc->data[SC_MTF_RANGEATK]->val1);
}
if (sc->data[SC_MTF_RANGEATK2]) { // Monster Transformation bonus
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_MTF_RANGEATK2]->val1);
RE_ALLATK_ADDRATE(wd, sc->data[SC_MTF_RANGEATK2]->val1);
}
if (sc->data[SC_ARCLOUSEDASH] && sc->data[SC_ARCLOUSEDASH]->val4) {
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_ARCLOUSEDASH]->val4);
RE_ALLATK_ADDRATE(wd, sc->data[SC_ARCLOUSEDASH]->val4);
}
}
if (sd && wd->flag&BF_WEAPON && sc->data[SC_GVG_GIANT] && sc->data[SC_GVG_GIANT]->val3) {
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_GVG_GIANT]->val3);
RE_ALLATK_ADDRATE(wd, sc->data[SC_GVG_GIANT]->val3);
}
if (skill_id == 0 && sc->data[SC_EXEEDBREAK]) {
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_EXEEDBREAK]->val2);
RE_ALLATK_ADDRATE(wd, sc->data[SC_EXEEDBREAK]->val2);
}
if (sc->data[SC_PYREXIA] && sc->data[SC_PYREXIA]->val3 == 0) {
ATK_ADDRATE(wd->damage, wd->damage2, sc->data[SC_PYREXIA]->val2);
RE_ALLATK_ADDRATE(wd, sc->data[SC_PYREXIA]->val2);
}
if (sc->data[SC_MIRACLE])
anger_id = 2; // Always treat all monsters as star flagged monster when in miracle state
}
if ((wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG) {
if (sd && pc_checkskill(sd, SU_POWEROFLIFE) > 0 && pc_checkskill_summoner(sd, SUMMONER_POWER_LIFE) >= 20) {
ATK_ADDRATE(wd->damage, wd->damage2, 20);
RE_ALLATK_ADDRATE(wd, 20);
}
}
if (sd != nullptr && !anger_id)
ARR_FIND(0, MAX_PC_FEELHATE, anger_id, status_get_class(target) == sd->hate_mob[anger_id]);
uint16 anger_level;
if (sd != nullptr && anger_id < MAX_PC_FEELHATE && (anger_level = pc_checkskill(sd, sg_info[anger_id].anger_id))) {
int skillratio = sd->status.base_level + sstatus->dex + sstatus->luk;
if (anger_id == 2)
skillratio += sstatus->str; // SG_STAR_ANGER additionally has STR added in its formula.
if (anger_level < 4)
skillratio /= 12 - 3 * anger_level;
ATK_ADDRATE(wd->damage, wd->damage2, skillratio);
#ifdef RENEWAL
RE_ALLATK_ADDRATE(wd, skillratio);
#endif
}
}
/*====================================
* Calc defense damage reduction
*------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_defense_reduction(struct Damage* wd, struct block_list *src,struct block_list *target, uint16 skill_id, uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct map_session_data *tsd = BL_CAST(BL_PC, target);
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
//Defense reduction
short vit_def;
defType def1 = status_get_def(target); //Don't use tstatus->def1 due to skill timer reductions.
short def2 = tstatus->def2;
if (sd) {
int i = sd->indexed_bonus.ignore_def_by_race[tstatus->race] + sd->indexed_bonus.ignore_def_by_race[RC_ALL];
i += sd->indexed_bonus.ignore_def_by_class[tstatus->class_] + sd->indexed_bonus.ignore_def_by_class[CLASS_ALL];
if (i) {
i = min(i,100); //cap it to 100 for 0 def min
def1 -= def1 * i / 100;
def2 -= def2 * i / 100;
}
//Kagerou/Oboro Earth Charm effect +10% eDEF
if(sd->spiritcharm_type == CHARM_TYPE_LAND && sd->spiritcharm > 0) {
short si = 10 * sd->spiritcharm;
def1 = (def1 * (100 + si)) / 100;
}
}
if (sc && sc->data[SC_EXPIATIO]) {
short i = 5 * sc->data[SC_EXPIATIO]->val1; // 5% per level
i = min(i,100); //cap it to 100 for 0 def min
def1 = (def1*(100-i))/100;
def2 = (def2*(100-i))/100;
}
if (tsc) {
if (tsc->data[SC_FORCEOFVANGUARD]) {
short i = 2 * tsc->data[SC_FORCEOFVANGUARD]->val1;
def1 = (def1 * (100 + i)) / 100;
}
if( tsc->data[SC_CAMOUFLAGE] ){
short i = 5 * tsc->data[SC_CAMOUFLAGE]->val3; //5% per second
i = min(i,100); //cap it to 100 for 0 def min
def1 = (def1*(100-i))/100;
def2 = (def2*(100-i))/100;
}
if (tsc->data[SC_GT_REVITALIZE])
def1 += tsc->data[SC_GT_REVITALIZE]->val4;
if (tsc->data[SC_OVERED_BOOST] && target->type == BL_PC)
def1 = (def1 * tsc->data[SC_OVERED_BOOST]->val4) / 100;
}
if( battle_config.vit_penalty_type && battle_config.vit_penalty_target&target->type ) {
unsigned char target_count; //256 max targets should be a sane max
//Official servers limit the count to 22 targets
target_count = min(unit_counttargeted(target), (100 / battle_config.vit_penalty_num) + (battle_config.vit_penalty_count - 1));
if(target_count >= battle_config.vit_penalty_count) {
if(battle_config.vit_penalty_type == 1) {
if( !tsc || !tsc->data[SC_STEELBODY] )
def1 = (def1 * (100 - (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num))/100;
def2 = (def2 * (100 - (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num))/100;
} else { //Assume type 2
if( !tsc || !tsc->data[SC_STEELBODY] )
def1 -= (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num;
def2 -= (target_count - (battle_config.vit_penalty_count - 1))*battle_config.vit_penalty_num;
}
}
if (skill_id == AM_ACIDTERROR)
#ifdef RENEWAL
def2 = 0; //Ignore only status defense. [FatalEror]
#else
def1 = 0; //Ignores only armor defense. [Skotlex]
#endif
if(def2 < 1)
def2 = 1;
}
//Damage reduction based on vitality
if (tsd) { //Sd vit-eq
int skill;
#ifndef RENEWAL
//Damage reduction: [VIT*0.3] + RND(0, [VIT^2/150] - [VIT*0.3] - 1) + [VIT*0.5]
vit_def = ((3 * def2) / 10);
vit_def += rnd_value(0, (def2 * def2) / 150 - ((3 * def2) / 10) - 1);
vit_def += (def2 / 2);
#else
vit_def = def2;
#endif
if( src->type == BL_MOB && (battle_check_undead(sstatus->race,sstatus->def_ele) || sstatus->race==RC_DEMON) && //This bonus already doesn't work vs players
(skill=pc_checkskill(tsd,AL_DP)) > 0 )
vit_def += skill*(int)(3 +(tsd->status.base_level+1)*0.04); // submitted by orn
if( src->type == BL_MOB && (skill=pc_checkskill(tsd,RA_RANGERMAIN))>0 &&
(sstatus->race == RC_BRUTE || sstatus->race == RC_PLAYER_DORAM || sstatus->race == RC_FISH || sstatus->race == RC_PLANT) )
vit_def += skill*5;
if( src->type == BL_MOB && (skill = pc_checkskill(tsd, NC_RESEARCHFE)) > 0 &&
(sstatus->def_ele == ELE_FIRE || sstatus->def_ele == ELE_EARTH) )
vit_def += skill * 10;
} else { //Mob-Pet vit-eq
#ifndef RENEWAL
//VIT + rnd(0,[VIT/20]^2-1)
vit_def = (def2/20)*(def2/20);
if (tsc && tsc->data[SC_SKA])
vit_def += 100; //Eska increases the random part of the formula by 100
vit_def = def2 + (vit_def>0?rnd()%vit_def:0);
#else
//SoftDEF of monsters is floor((BaseLevel+Vit)/2)
vit_def = def2;
#endif
}
if (battle_config.weapon_defense_type) {
vit_def += def1*battle_config.weapon_defense_type;
def1 = 0;
}
#ifdef RENEWAL
/**
* RE DEF Reduction
* Damage = Attack * (4000+eDEF)/(4000+eDEF*10) - sDEF
* Pierce defence gains 1 atk per def/2
*/
if( def1 == -400 ) /* -400 creates a division by 0 and subsequently crashes */
def1 = -399;
ATK_ADD2(wd->damage, wd->damage2,
is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ? (def1*battle_calc_attack_skill_ratio(wd, src, target, skill_id, skill_lv))/200 : 0,
is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ? (def1*battle_calc_attack_skill_ratio(wd, src, target, skill_id, skill_lv))/200 : 0
);
if( !attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_R) && !is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) )
wd->damage = wd->damage * (4000+def1) / (4000+10*def1) - vit_def;
if( is_attack_left_handed(src, skill_id) && !attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_L) && !is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) )
wd->damage2 = wd->damage2 * (4000+def1) / (4000+10*def1) - vit_def;
#else
if (def1 > 100) def1 = 100;
ATK_RATE2(wd->damage, wd->damage2,
attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ?100:(is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ? (int64)is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R)*(def1+vit_def) : (100-def1)),
attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ?100:(is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ? (int64)is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L)*(def1+vit_def) : (100-def1))
);
ATK_ADD2(wd->damage, wd->damage2,
attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_R) || is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ?0:-vit_def,
attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_L) || is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ?0:-vit_def
);
#endif
}
/*====================================
* Modifiers ignoring DEF
*------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_attack_post_defense(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct status_change *sc = status_get_sc(src);
struct status_data *sstatus = status_get_status_data(src);
// Post skill/vit reduction damage increases
if( sc ) { // SC skill damages
if(sc->data[SC_AURABLADE]
#ifndef RENEWAL
&& skill_id != LK_SPIRALPIERCE && skill_id != ML_SPIRALPIERCE
#endif
) {
#ifdef RENEWAL
ATK_ADD(wd->damage, wd->damage2, (3 + sc->data[SC_AURABLADE]->val1) * status_get_lv(src)); // !TODO: Confirm formula
#else
ATK_ADD(wd->damage, wd->damage2, 20 * sc->data[SC_AURABLADE]->val1);
#endif
}
}
#ifndef RENEWAL
battle_calc_attack_masteries(wd, src, target, skill_id, skill_lv);
//Refine bonus
if (sd && battle_skill_stacks_masteries_vvs(skill_id) && skill_id != MO_INVESTIGATE && skill_id != MO_EXTREMITYFIST) { // Counts refine bonus multiple times
if (skill_id == MO_FINGEROFFENSIVE) {
ATK_ADD2(wd->damage, wd->damage2, wd->div_*sstatus->rhw.atk2, wd->div_*sstatus->lhw.atk2);
} else {
ATK_ADD2(wd->damage, wd->damage2, sstatus->rhw.atk2, sstatus->lhw.atk2);
}
}
#endif
//Set to min of 1
if (is_attack_right_handed(src, skill_id) && wd->damage < 1) wd->damage = 1;
if (is_attack_left_handed(src, skill_id) && wd->damage2 < 1) wd->damage2 = 1;
switch (skill_id) {
case AS_SONICBLOW:
if(sd && pc_checkskill(sd,AS_SONICACCEL)>0)
#ifdef RENEWAL
ATK_ADDRATE(wd->damage, wd->damage2, 90);
#else
ATK_ADDRATE(wd->damage, wd->damage2, 10);
#endif
break;
}
}
/*=================================================================================
* "Plant"-type (mobs that only take 1 damage from all sources) damage calculation
*---------------------------------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_attack_plant(struct Damage* wd, struct block_list *src,struct block_list *target, uint16 skill_id, uint16 skill_lv)
{
struct status_data *tstatus = status_get_status_data(target);
bool attack_hits = is_attack_hitting(wd, src, target, skill_id, skill_lv, false);
if (skill_id != SN_SHARPSHOOTING && skill_id != RA_ARROWSTORM)
status_change_end(src, SC_CAMOUFLAGE, INVALID_TIMER);
//Plants receive 1 damage when hit
if( attack_hits || wd->damage > 0 )
wd->damage = 1; //In some cases, right hand no need to have a weapon to deal a damage
if( is_attack_left_handed(src, skill_id) && (attack_hits || wd->damage2 > 0) ) {
struct map_session_data *sd = BL_CAST(BL_PC, src);
if (sd && sd->status.weapon == W_KATAR)
wd->damage2 = 0; //No backhand damage against plants
else
wd->damage2 = 1; //Deal 1 HP damage as long as there is a weapon in the left hand
}
if (attack_hits && target->type == BL_MOB) {
struct status_change *sc = status_get_sc(target);
int64 damage_dummy = 1;
if (sc && !battle_status_block_damage(src, target, sc, wd, damage_dummy, skill_id, skill_lv)) { // Statuses that reduce damage to 0.
wd->damage = wd->damage2 = 0;
return;
}
}
if( attack_hits && status_get_class(target) == MOBID_EMPERIUM ) {
if(target && !battle_can_hit_gvg_target(src,target,skill_id,(skill_id)?BF_SKILL:0) && map_flag_gvg2(target->m)) {
wd->damage = wd->damage2 = 0;
return;
}
const int right_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_R, false);
const int left_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_L, false);
if (wd->damage > 0) {
wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv);
wd->damage = battle_calc_gvg_damage(src, target, wd->damage, skill_id, wd->flag);
} else if (wd->damage2 > 0) {
wd->damage2 = battle_attr_fix(src, target, wd->damage2, left_element, tstatus->def_ele, tstatus->ele_lv);
wd->damage2 = battle_calc_gvg_damage(src, target, wd->damage2, skill_id, wd->flag);
}
return;
}
//For plants we don't continue with the weapon attack code, so we have to apply DAMAGE_DIV_FIX here
battle_apply_div_fix(wd, skill_id);
//If there is left hand damage, total damage can never exceed 2, even on multiple hits
if(wd->damage > 1 && wd->damage2 > 0) {
wd->damage = 1;
wd->damage2 = 1;
}
}
/*========================================================================================
* Perform left/right hand weapon damage calculation based on previously calculated damage
*----------------------------------------------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_attack_left_right_hands(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
if (sd) {
int skill;
if (!is_attack_right_handed(src, skill_id) && is_attack_left_handed(src, skill_id)) {
wd->damage = wd->damage2;
wd->damage2 = 0;
} else if(sd->status.weapon == W_KATAR && !skill_id) { //Katars (offhand damage only applies to normal attacks, tested on Aegis 10.2)
skill = pc_checkskill(sd,TF_DOUBLE);
wd->damage2 = (int64)wd->damage * (1 + (skill * 2))/100;
} else if(is_attack_right_handed(src, skill_id) && is_attack_left_handed(src, skill_id)) { //Dual-wield
if (wd->damage) {
if( (sd->class_&MAPID_BASEMASK) == MAPID_THIEF ) {
skill = pc_checkskill(sd,AS_RIGHT);
ATK_RATER(wd->damage, 50 + (skill * 10))
}
else if(sd->class_ == MAPID_KAGEROUOBORO) {
skill = pc_checkskill(sd,KO_RIGHT);
ATK_RATER(wd->damage, 70 + (skill * 10))
}
if(wd->damage < 1)
wd->damage = 1;
}
if (wd->damage2) {
if( (sd->class_&MAPID_BASEMASK) == MAPID_THIEF) {
skill = pc_checkskill(sd,AS_LEFT);
ATK_RATEL(wd->damage2, 30 + (skill * 10))
}
else if(sd->class_ == MAPID_KAGEROUOBORO) {
skill = pc_checkskill(sd,KO_LEFT);
ATK_RATEL(wd->damage2, 50 + (skill * 10))
}
if(wd->damage2 < 1)
wd->damage2 = 1;
}
}
}
if(!is_attack_right_handed(src, skill_id) && !is_attack_left_handed(src, skill_id) && wd->damage)
wd->damage=0;
if(!is_attack_left_handed(src, skill_id) && wd->damage2)
wd->damage2=0;
}
/**
* Check if bl is devoted by someone
* @param bl
* @return 'd_bl' if devoted or NULL if not devoted
*/
struct block_list *battle_check_devotion(struct block_list *bl) {
struct block_list *d_bl = NULL;
if (battle_config.devotion_rdamage && battle_config.devotion_rdamage > rnd() % 100) {
struct status_change *sc = status_get_sc(bl);
if (sc && sc->data[SC_DEVOTION])
d_bl = map_id2bl(sc->data[SC_DEVOTION]->val1);
}
return d_bl;
}
/*==========================================
* BG/GvG attack modifiers
*------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_attack_gvg_bg(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
if( wd->damage + wd->damage2 ) { //There is a total damage value
if( src != target && //Don't reflect your own damage (Grand Cross)
(!skill_id || skill_id ||
(src->type == BL_SKILL && (skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM))) ) {
int64 damage = wd->damage + wd->damage2, rdamage = 0;
struct map_session_data *tsd = BL_CAST(BL_PC, target);
struct status_data *sstatus = status_get_status_data(src);
t_tick tick = gettick(), rdelay = 0;
rdamage = battle_calc_return_damage(target, src, &damage, wd->flag, skill_id, false);
if( rdamage > 0 ) { //Item reflect gets calculated before any mapflag reducing is applicated
struct block_list *d_bl = battle_check_devotion(src);
rdelay = clif_damage(src, (!d_bl) ? src : d_bl, tick, wd->amotion, sstatus->dmotion, rdamage, 1, DMG_ENDURE, 0, false);
if( tsd )
battle_drain(tsd, src, rdamage, rdamage, sstatus->race, sstatus->class_);
//Use Reflect Shield to signal this kind of skill trigger [Skotlex]
battle_delay_damage(tick, wd->amotion, target, (!d_bl) ? src : d_bl, 0, CR_REFLECTSHIELD, 0, rdamage, ATK_DEF, rdelay, true, false);
skill_additional_effect(target, (!d_bl) ? src : d_bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL, ATK_DEF, tick);
}
}
struct map_data *mapdata = map_getmapdata(target->m);
if(!wd->damage2) {
wd->damage = battle_calc_damage(src,target,wd,wd->damage,skill_id,skill_lv);
if( mapdata_flag_gvg2(mapdata) )
wd->damage=battle_calc_gvg_damage(src,target,wd->damage,skill_id,wd->flag);
else if( mapdata->flag[MF_BATTLEGROUND] )
wd->damage=battle_calc_bg_damage(src,target,wd->damage,skill_id,wd->flag);
}
else if(!wd->damage) {
wd->damage2 = battle_calc_damage(src,target,wd,wd->damage2,skill_id,skill_lv);
if( mapdata_flag_gvg2(mapdata) )
wd->damage2 = battle_calc_gvg_damage(src,target,wd->damage2,skill_id,wd->flag);
else if( mapdata->flag[MF_BATTLEGROUND] )
wd->damage2 = battle_calc_bg_damage(src,target,wd->damage2,skill_id,wd->flag);
}
else {
int64 d1 = wd->damage + wd->damage2,d2 = wd->damage2;
wd->damage = battle_calc_damage(src,target,wd,d1,skill_id,skill_lv);
if( mapdata_flag_gvg2(mapdata) )
wd->damage = battle_calc_gvg_damage(src,target,wd->damage,skill_id,wd->flag);
else if( mapdata->flag[MF_BATTLEGROUND] )
wd->damage = battle_calc_bg_damage(src,target,wd->damage,skill_id,wd->flag);
wd->damage2 = (int64)d2*100/d1 * wd->damage/100;
if(wd->damage > 1 && wd->damage2 < 1) wd->damage2 = 1;
wd->damage-=wd->damage2;
}
}
}
/*==========================================
* final ATK modifiers - after BG/GvG calc
*------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static void battle_calc_weapon_final_atk_modifiers(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv)
{
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct map_session_data *tsd = BL_CAST(BL_PC, target);
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
int skill_damage = 0;
//Reject Sword bugreport:4493 by Daegaladh
if(wd->damage && tsc && tsc->data[SC_REJECTSWORD] &&
(src->type!=BL_PC || (
((TBL_PC *)src)->weapontype1 == W_DAGGER ||
((TBL_PC *)src)->weapontype1 == W_1HSWORD ||
((TBL_PC *)src)->status.weapon == W_2HSWORD
)) &&
rnd()%100 < tsc->data[SC_REJECTSWORD]->val2
)
{
ATK_RATER(wd->damage, 50)
status_fix_damage(target,src,wd->damage,clif_damage(target,src,gettick(),0,0,wd->damage,0,DMG_NORMAL,0,false),ST_REJECTSWORD);
clif_skill_nodamage(target,target,ST_REJECTSWORD,tsc->data[SC_REJECTSWORD]->val1,1);
if( --(tsc->data[SC_REJECTSWORD]->val3) <= 0 )
status_change_end(target, SC_REJECTSWORD, INVALID_TIMER);
}
if( tsc && tsc->data[SC_CRESCENTELBOW] && wd->flag&BF_SHORT && rnd()%100 < tsc->data[SC_CRESCENTELBOW]->val2 ) {
//ATK [{(Target HP / 100) x Skill Level} x Caster Base Level / 125] % + [Received damage x {1 + (Skill Level x 0.2)}]
int64 rdamage = 0;
int ratio = (int64)(status_get_hp(src) / 100) * tsc->data[SC_CRESCENTELBOW]->val1 * status_get_lv(target) / 125;
if (ratio > 5000) ratio = 5000; // Maximum of 5000% ATK
rdamage = battle_calc_base_damage(target,tstatus,&tstatus->rhw,tsc,sstatus->size,0);
rdamage = (int64)rdamage * ratio / 100 + wd->damage * (10 + tsc->data[SC_CRESCENTELBOW]->val1 * 20 / 10) / 10;
skill_blown(target, src, skill_get_blewcount(SR_CRESCENTELBOW_AUTOSPELL, tsc->data[SC_CRESCENTELBOW]->val1), unit_getdir(src), BLOWN_NONE);
clif_skill_damage(target, src, gettick(), status_get_amotion(src), 0, rdamage,
1, SR_CRESCENTELBOW_AUTOSPELL, tsc->data[SC_CRESCENTELBOW]->val1, DMG_SINGLE); // This is how official does
clif_damage(src, target, gettick(), status_get_amotion(src)+1000, 0, rdamage/10, 1, DMG_NORMAL, 0, false);
status_damage(target, src, rdamage, 0, 0, 0, 0);
status_damage(src, target, rdamage/10, 0, 0, 1, 0);
status_change_end(target, SC_CRESCENTELBOW, INVALID_TIMER);
}
if( sc ) {
//SC_FUSION hp penalty [Komurka]
if (sc->data[SC_FUSION]) {
unsigned int hp = sstatus->max_hp;
if (sd && tsd) {
hp = hp / 13;
if (((int64)sstatus->hp * 100) <= ((int64)sstatus->max_hp * 20))
hp = sstatus->hp;
} else
hp = 2*hp/100; //2% hp loss per hit
status_zap(src, hp, 0);
}
if (sc->data[SC_VIGOR])
status_zap(src, sc->data[SC_VIGOR]->val2, 0);
// Only affecting non-skills
if (!skill_id && wd->dmg_lv > ATK_BLOCK) {
if (sc->data[SC_ENCHANTBLADE]) {
//[((Skill Lv x 20) + 100) x (casterBaseLevel / 150)] + casterInt + MATK - MDEF - MDEF2
int64 enchant_dmg = sc->data[SC_ENCHANTBLADE]->val2;
if (sstatus->matk_max > sstatus->matk_min)
enchant_dmg = enchant_dmg + sstatus->matk_min + rnd() % (sstatus->matk_max - sstatus->matk_min);
else
enchant_dmg = enchant_dmg + sstatus->matk_min;
enchant_dmg = enchant_dmg - (tstatus->mdef + tstatus->mdef2);
if (enchant_dmg > 0)
ATK_ADD(wd->damage, wd->damage2, enchant_dmg);
}
}
if (skill_id != SN_SHARPSHOOTING && skill_id != RA_ARROWSTORM)
status_change_end(src, SC_CAMOUFLAGE, INVALID_TIMER);
}
#ifndef RENEWAL
if (skill_id == ASC_BREAKER) { //Breaker's int-based damage (a misc attack?)
struct Damage md = battle_calc_misc_attack(src, target, skill_id, skill_lv, wd->miscflag);
wd->damage += md.damage;
}
#endif
// Skill damage adjustment
if ((skill_damage = battle_skill_damage(src, target, skill_id)) != 0)
ATK_ADDRATE(wd->damage, wd->damage2, skill_damage);
}
/*====================================================
* Basic wd init - not influenced by HIT/MISS/DEF/etc.
*----------------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static struct Damage initialize_weapon_data(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, int wflag)
{
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
struct status_change *sc = status_get_sc(src);
struct map_session_data *sd = BL_CAST(BL_PC, src);
struct Damage wd;
wd.type = DMG_NORMAL; //Normal attack
wd.div_ = skill_id?skill_get_num(skill_id,skill_lv):1;
wd.amotion = (skill_id && skill_get_inf(skill_id)&INF_GROUND_SKILL)?0:sstatus->amotion; //Amotion should be 0 for ground skills.
// counter attack DOES obey ASPD delay on official, uncomment if you want the old (bad) behavior [helvetica]
/*if(skill_id == KN_AUTOCOUNTER)
wd.amotion >>= 1; */
wd.dmotion = tstatus->dmotion;
wd.blewcount =skill_get_blewcount(skill_id,skill_lv);
wd.miscflag = wflag;
wd.flag = BF_WEAPON; //Initial Flag
wd.flag |= (skill_id||wd.miscflag)?BF_SKILL:BF_NORMAL; // Baphomet card's splash damage is counted as a skill. [Inkfish]
wd.isspdamage = false;
wd.damage = wd.damage2 =
#ifdef RENEWAL
wd.statusAtk = wd.statusAtk2 = wd.equipAtk = wd.equipAtk2 = wd.weaponAtk = wd.weaponAtk2 = wd.masteryAtk = wd.masteryAtk2 =
#endif
0;
wd.dmg_lv=ATK_DEF; //This assumption simplifies the assignation later
if(sd)
wd.blewcount += battle_blewcount_bonus(sd, skill_id);
if (skill_id) {
wd.flag |= battle_range_type(src, target, skill_id, skill_lv);
switch(skill_id)
{
#ifdef RENEWAL
case RG_BACKSTAP:
if (sd && sd->status.weapon == W_DAGGER)
wd.div_ = 2;
break;
case MO_CHAINCOMBO:
if (sd && sd->status.weapon == W_KNUCKLE)
wd.div_ = -6;
break;
#endif
case MH_SONIC_CRAW:{
TBL_HOM *hd = BL_CAST(BL_HOM,src);
wd.div_ = hd->homunculus.spiritball;
}
break;
case MO_FINGEROFFENSIVE:
if (sd) {
if (battle_config.finger_offensive_type)
wd.div_ = 1;
else if ((sd->spiritball + sd->spiritball_old) < wd.div_)
wd.div_ = sd->spiritball + sd->spiritball_old;
}
break;
case KN_PIERCE:
case ML_PIERCE:
wd.div_= (wd.div_>0?tstatus->size+1:-(tstatus->size+1));
break;
case TF_DOUBLE: //For NPC used skill.
case GS_CHAINACTION:
wd.type = DMG_MULTI_HIT;
break;
case GS_GROUNDDRIFT:
wd.amotion = sstatus->amotion;
//Fall through
case KN_SPEARSTAB:
#ifndef RENEWAL
case KN_BOWLINGBASH:
#endif
case MS_BOWLINGBASH:
case MO_BALKYOUNG:
case TK_TURNKICK:
wd.blewcount = 0;
break;
#ifdef RENEWAL
case KN_BOWLINGBASH:
if (sd && sd->status.weapon == W_2HSWORD) {
if (wd.miscflag >= 2 && wd.miscflag <= 3)
wd.div_ = 3;
else if (wd.miscflag >= 4)
wd.div_ = 4;
}
break;
#endif
case KN_AUTOCOUNTER:
wd.flag = (wd.flag&~BF_SKILLMASK)|BF_NORMAL;
break;
case LK_SPIRALPIERCE:
if (!sd) wd.flag = (wd.flag&~(BF_RANGEMASK|BF_WEAPONMASK))|BF_LONG|BF_MISC;
break;
case RK_WINDCUTTER:
if (sd && (sd->status.weapon == W_1HSPEAR || sd->status.weapon == W_2HSPEAR))
wd.flag |= BF_LONG;
break;
case NC_BOOSTKNUCKLE:
case NC_VULCANARM:
case NC_ARMSCANNON:
if (sc && sc->data[SC_ABR_DUAL_CANNON])
wd.div_ = 2;
break;
case NC_POWERSWING:
if (sc && sc->data[SC_ABR_BATTLE_WARIOR])
wd.div_ = -2;
break;
case GN_CARTCANNON:
if (sc && sc->data[SC_BIONIC_WOODENWARRIOR])
wd.div_ = 2;
break;
case DK_SERVANT_W_PHANTOM:
case DK_SERVANT_W_DEMOL:
if (sd && (sd->servantball + sd->servantball_old) < wd.div_)
wd.div_ = sd->servantball + sd->servantball_old;
break;
case IQ_THIRD_FLAME_BOMB:
wd.div_ = min(wd.div_ + wd.miscflag, 3); // Number of hits doesn't go above 3.
break;
case IG_OVERSLASH:
wd.div_ = min(wd.div_ + wd.miscflag, 5); // Number of hits doesn't appear to go above 5.
break;
case SHC_ETERNAL_SLASH:
if (sc && sc->data[SC_E_SLASH_COUNT])
wd.div_ = sc->data[SC_E_SLASH_COUNT]->val1;
break;
case SHC_SHADOW_STAB:
if (wd.miscflag == 2)
wd.div_ = 2;
break;
case SHC_IMPACT_CRATER:
if (sc && sc->data[SC_ROLLINGCUTTER])
wd.div_ = sc->data[SC_ROLLINGCUTTER]->val1;
break;
case MT_AXE_STOMP:
if (sd && sd->status.weapon == W_2HAXE)
wd.div_ = 2;
break;
}
} else {
bool is_long = false;
if (is_skill_using_arrow(src, skill_id) || (sc && sc->data[SC_SOULATTACK]))
is_long = true;
wd.flag |= is_long ? BF_LONG : BF_SHORT;
}
return wd;
}
/**
* Check if we should reflect the damage and calculate it if so
* @param attack_type : BL_WEAPON,BL_MAGIC or BL_MISC
* @param wd : weapon damage
* @param src : bl who did the attack
* @param target : target of the attack
* @param skill_id : id of casted skill, 0 = basic atk
* @param skill_lv : lvl of skill casted
*/
void battle_do_reflect(int attack_type, struct Damage *wd, struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv)
{
// Don't reflect your own damage (Grand Cross)
if ((wd->damage + wd->damage2) && src && target && src != target && (src->type != BL_SKILL ||
(src->type == BL_SKILL && (skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM ))))
{
int64 damage = wd->damage + wd->damage2, rdamage = 0;
struct map_session_data *tsd = BL_CAST(BL_PC, target);
struct status_change *tsc = status_get_sc(target);
struct status_data *sstatus = status_get_status_data(src);
struct unit_data *ud = unit_bl2ud(target);
t_tick tick = gettick(), rdelay = 0;
if (!tsc)
return;
// Calculate skill reflect damage separately
if ((ud && !ud->immune_attack) || !status_bl_has_mode(target, MD_SKILLIMMUNE))
rdamage = battle_calc_return_damage(target, src, &damage, wd->flag, skill_id,true);
if( rdamage > 0 ) {
struct block_list *d_bl = battle_check_devotion(src);
status_change *sc = status_get_sc(src);
if (sc && sc->data[SC_VITALITYACTIVATION])
rdamage /= 2;
if (tsc->data[SC_MAXPAIN]) {
tsc->data[SC_MAXPAIN]->val2 = (int)rdamage;
skill_castend_damage_id(target, src, NPC_MAXPAIN_ATK, tsc->data[SC_MAXPAIN]->val1, tick, wd->flag);
tsc->data[SC_MAXPAIN]->val2 = 0;
}
else if( attack_type == BF_WEAPON && tsc->data[SC_REFLECTDAMAGE] ) // Don't reflect your own damage (Grand Cross)
map_foreachinshootrange(battle_damage_area,target,skill_get_splash(LG_REFLECTDAMAGE,1),BL_CHAR,tick,target,wd->amotion,sstatus->dmotion,rdamage,wd->flag);
else if( attack_type == BF_WEAPON || attack_type == BF_MISC) {
rdelay = clif_damage(src, (!d_bl) ? src : d_bl, tick, wd->amotion, sstatus->dmotion, rdamage, 1, DMG_ENDURE, 0, false);
if( tsd )
battle_drain(tsd, src, rdamage, rdamage, sstatus->race, sstatus->class_);
// It appears that official servers give skill reflect damage a longer delay
battle_delay_damage(tick, wd->amotion, target, (!d_bl) ? src : d_bl, 0, CR_REFLECTSHIELD, 0, rdamage, ATK_DEF, rdelay ,true, false);
skill_additional_effect(target, (!d_bl) ? src : d_bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL, ATK_DEF, tick);
}
}
}
}
/*============================================
* Calculate "weapon"-type attacks and skills
*--------------------------------------------
* Credits:
* Original coder Skotlex
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
static struct Damage battle_calc_weapon_attack(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, int wflag)
{
struct map_session_data *sd, *tsd;
struct Damage wd;
struct status_change *sc = status_get_sc(src);
struct status_change *tsc = status_get_sc(target);
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
int right_element, left_element;
bool infdef = false;
memset(&wd,0,sizeof(wd));
if (src == NULL || target == NULL) {
nullpo_info(NLP_MARK);
return wd;
}
wd = initialize_weapon_data(src, target, skill_id, skill_lv, wflag);
right_element = battle_get_weapon_element(&wd, src, target, skill_id, skill_lv, EQI_HAND_R, false);
left_element = battle_get_weapon_element(&wd, src, target, skill_id, skill_lv, EQI_HAND_L, false);
if (sc && !sc->count)
sc = NULL; //Skip checking as there are no status changes active.
if (tsc && !tsc->count)
tsc = NULL; //Skip checking as there are no status changes active.
sd = BL_CAST(BL_PC, src);
tsd = BL_CAST(BL_PC, target);
//Check for Lucky Dodge
if ((!skill_id || skill_id == PA_SACRIFICE) && tstatus->flee2 && rnd()%1000 < tstatus->flee2) {
wd.type = DMG_LUCY_DODGE;
wd.dmg_lv = ATK_LUCKY;
if(wd.div_ < 0)
wd.div_ *= -1;
return wd;
}
// on official check for multi hit first so we can override crit on double attack [helvetica]
battle_calc_multi_attack(&wd, src, target, skill_id, skill_lv);
// crit check is next since crits always hit on official [helvetica]
if (is_attack_critical(&wd, src, target, skill_id, skill_lv, true)) {
#if PACKETVER >= 20161207
if (wd.type&DMG_MULTI_HIT)
wd.type = DMG_MULTI_HIT_CRITICAL;
else
wd.type = DMG_CRITICAL;
#else
wd.type = DMG_CRITICAL;
#endif
}
std::bitset<NK_MAX> nk = battle_skill_get_damage_properties(skill_id, wd.miscflag);
// check if we're landing a hit
if(!is_attack_hitting(&wd, src, target, skill_id, skill_lv, true))
wd.dmg_lv = ATK_FLEE;
else if(!(infdef = is_infinite_defense(target, wd.flag))) { //no need for math against plants
int64 ratio = 0;
int i = 0;
battle_calc_skill_base_damage(&wd, src, target, skill_id, skill_lv); // base skill damage
ratio = battle_calc_attack_skill_ratio(&wd, src, target, skill_id, skill_lv); // skill level ratios
ATK_RATE(wd.damage, wd.damage2, ratio);
RE_ALLATK_RATE(&wd, ratio);
int64 bonus_damage = battle_calc_skill_constant_addition(&wd, src, target, skill_id, skill_lv); // other skill bonuses
ATK_ADD(wd.damage, wd.damage2, bonus_damage);
#ifdef RENEWAL
if(skill_id == HW_MAGICCRASHER) { // Add weapon attack for MATK onto Magic Crasher
struct status_data *sstatus = status_get_status_data(src);
if (sstatus->matk_max > sstatus->matk_min) {
ATK_ADD(wd.weaponAtk, wd.weaponAtk2, sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min));
} else
ATK_ADD(wd.weaponAtk, wd.weaponAtk2, sstatus->matk_min);
}
#endif
// add any miscellaneous player ATK bonuses
if( sd && skill_id && (i = pc_skillatk_bonus(sd, skill_id))) {
ATK_ADDRATE(wd.damage, wd.damage2, i);
RE_ALLATK_ADDRATE(&wd, i);
}
if (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id))) {
ATK_ADDRATE(wd.damage, wd.damage2, -i);
RE_ALLATK_ADDRATE(&wd, -i);
}
#ifdef RENEWAL
// In Renewal we only cardfix to the weapon and equip ATK
//Card Fix for attacker (sd), 2 is added to the "left" flag meaning "attacker cards only"
if (sd) {
wd.weaponAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk, 2, wd.flag);
wd.equipAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk, 2, wd.flag);
if (is_attack_left_handed(src, skill_id)) {
wd.weaponAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk2, 3, wd.flag);
wd.equipAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk2, 3, wd.flag);
}
}
//Card Fix for target (tsd), 2 is not added to the "left" flag meaning "target cards only"
if (tsd && skill_id != NJ_ISSEN && skill_id != GN_FIRE_EXPANSION_ACID) { // These skills will do a card fix later
std::bitset<NK_MAX> ignoreele_nk = nk;
ignoreele_nk.set(NK_IGNOREELEMENT);
wd.statusAtk += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.statusAtk, 0, wd.flag);
wd.weaponAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk, 0, wd.flag);
wd.equipAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk, 0, wd.flag);
wd.masteryAtk += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.masteryAtk, 0, wd.flag);
if (is_attack_left_handed(src, skill_id)) {
wd.statusAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.statusAtk2, 1, wd.flag);
wd.weaponAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk2, 1, wd.flag);
wd.equipAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk2, 1, wd.flag);
wd.masteryAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.masteryAtk2, 1, wd.flag);
}
}
// final attack bonuses that aren't affected by cards
battle_attack_sc_bonus(&wd, src, target, skill_id, skill_lv);
if (sd) { //monsters, homuns and pets have their damage computed directly
wd.damage = (wd.statusAtk + wd.weaponAtk + wd.equipAtk) * (100 + sstatus->patk) / 100 + wd.masteryAtk + bonus_damage;
if (is_attack_left_handed(src, skill_id))
wd.damage2 = (wd.statusAtk2 + wd.weaponAtk2 + wd.equipAtk2) * (100 + sstatus->patk) / 100 + wd.masteryAtk2 + bonus_damage;
if (wd.flag & BF_SHORT)
ATK_ADDRATE(wd.damage, wd.damage2, sd->bonus.short_attack_atk_rate);
if(wd.flag&BF_LONG && (skill_id != RA_WUGBITE && skill_id != RA_WUGSTRIKE)) //Long damage rate addition doesn't use weapon + equip attack
ATK_ADDRATE(wd.damage, wd.damage2, sd->bonus.long_attack_atk_rate);
}
// Res reduces physical damage by a percentage and
// is calculated before DEF and other reductions.
// This should be the official formula. [Rytech]
if ((wd.damage + wd.damage2) && tstatus->res > 0) {
short res = tstatus->res;
short ignore_res = 0;// Value used as a percentage.
// Attacker status's that pierce Res.
if (sc) {
if (sc->data[SC_A_TELUM])
ignore_res += sc->data[SC_A_TELUM]->val2;
if (sc->data[SC_POTENT_VENOM])
ignore_res += sc->data[SC_POTENT_VENOM]->val2;
}
ignore_res = min(ignore_res, 100);
if (ignore_res > 0)
res -= res * ignore_res / 100;
// Max damage reduction from Res is officially 50%.
// That means 625 Res is needed to hit that cap.
if (res > battle_config.max_res_mres_reduction)
res = battle_config.max_res_mres_reduction;
// Apply damage reduction.
wd.damage = wd.damage * (5000 + res) / (5000 + 10 * res);
wd.damage2 = wd.damage2 * (5000 + res) / (5000 + 10 * res);
}
#else
// final attack bonuses that aren't affected by cards
battle_attack_sc_bonus(&wd, src, target, skill_id, skill_lv);
#endif
if (wd.damage + wd.damage2) { //Check if attack ignores DEF
if(!attack_ignores_def(&wd, src, target, skill_id, skill_lv, EQI_HAND_L) || !attack_ignores_def(&wd, src, target, skill_id, skill_lv, EQI_HAND_R))
battle_calc_defense_reduction(&wd, src, target, skill_id, skill_lv);
battle_calc_attack_post_defense(&wd, src, target, skill_id, skill_lv);
}
}
battle_calc_element_damage(&wd, src, target, skill_id, skill_lv);
if(skill_id == CR_GRANDCROSS || skill_id == NPC_GRANDDARKNESS)
return wd; //Enough, rest is not needed.
#ifdef RENEWAL
if (is_attack_critical(&wd, src, target, skill_id, skill_lv, false)) {
if (sd) { //Check for player so we don't crash out, monsters don't have bonus crit rates [helvetica]
if (skill_id > 0)
wd.damage = (int64)floor((float)((wd.damage * (140 + sstatus->crate)) / 100 * (100 + (sd->bonus.crit_atk_rate / 2))) / 100);
else
wd.damage = (int64)floor((float)((wd.damage * (140 + sstatus->crate)) / 100 * (100 + sd->bonus.crit_atk_rate)) / 100);
if (is_attack_left_handed(src, skill_id)) {
if (skill_id > 0)
wd.damage2 = (int64)floor((float)((wd.damage2 * (140 + sstatus->crate)) / 100 * (100 + (sd->bonus.crit_atk_rate / 2))) / 100);
else
wd.damage2 = (int64)floor((float)((wd.damage2 * (140 + sstatus->crate)) / 100 * (100 + sd->bonus.crit_atk_rate)) / 100);
}
} else
wd.damage = (int64)floor((float)(wd.damage * 140) / 100);
if (tsd && tsd->bonus.crit_def_rate != 0) {
ATK_ADDRATE(wd.damage, wd.damage2, -tsd->bonus.crit_def_rate);
}
}
#endif
switch (skill_id) {
#ifndef RENEWAL
case NJ_KUNAI:
ATK_ADD(wd.damage, wd.damage2, 90);
break;
#endif
case TK_DOWNKICK:
case TK_STORMKICK:
case TK_TURNKICK:
case TK_COUNTER:
if(sd && sd->weapontype1 == W_FIST && sd->weapontype2 == W_FIST)
ATK_ADD(wd.damage, wd.damage2, 10 * pc_checkskill(sd, TK_RUN));
break;
case SR_TIGERCANNON:
// (Tiger Cannon skill level x 240) + (Target Base Level x 40)
if (wd.miscflag&8) {
ATK_ADD(wd.damage, wd.damage2, skill_lv * 500 + status_get_lv(target) * 40);
} else
ATK_ADD(wd.damage, wd.damage2, skill_lv * 240 + status_get_lv(target) * 40);
break;
case SR_GATEOFHELL: {
status_data *sstatus = status_get_status_data(src);
double bonus = 1 + skill_lv * 2 / 10;
ATK_ADD(wd.damage, wd.damage2, sstatus->max_hp - sstatus->hp);
if(sc && sc->data[SC_COMBO] && sc->data[SC_COMBO]->val1 == SR_FALLENEMPIRE) {
ATK_ADD(wd.damage, wd.damage2, static_cast<int64>(sstatus->max_sp * bonus) + 40 * status_get_lv(src));
} else
ATK_ADD(wd.damage, wd.damage2, static_cast<int64>(sstatus->sp * bonus) + 10 * status_get_lv(src));
}
break;
case MH_TINDER_BREAKER:
ATK_ADD(wd.damage, wd.damage2, 2500 * skill_lv + status_get_lv(src)); // !TODO: Confirm base level bonus
break;
case MH_CBC:
ATK_ADD(wd.damage, wd.damage2, 4000 * skill_lv + status_get_lv(src)); // !TODO: Confirm base level bonus
break;
case MH_EQC:
ATK_ADD(wd.damage, wd.damage2, 6000 * skill_lv + status_get_lv(src)); // !TODO: Confirm base level bonus
break;
}
if(sd) {
#ifndef RENEWAL
uint16 skill;
if ((skill = pc_checkskill(sd, BS_WEAPONRESEARCH)) > 0)
ATK_ADD(wd.damage, wd.damage2, skill * 2);
if (skill_id == TF_POISON)
ATK_ADD(wd.damage, wd.damage2, 15 * skill_lv);
if (skill_id == GS_GROUNDDRIFT)
ATK_ADD(wd.damage, wd.damage2, 50 * skill_lv);
if (skill_id != CR_SHIELDBOOMERANG) //Only Shield boomerang doesn't takes the Star Crumbs bonus.
ATK_ADD2(wd.damage, wd.damage2, ((wd.div_ < 1) ? 1 : wd.div_) * sd->right_weapon.star, ((wd.div_ < 1) ? 1 : wd.div_) * sd->left_weapon.star);
if (skill_id != MC_CARTREVOLUTION && pc_checkskill(sd, BS_HILTBINDING) > 0)
ATK_ADD(wd.damage, wd.damage2, 4);
if (skill_id == MO_FINGEROFFENSIVE) { //The finger offensive spheres on moment of attack do count. [Skotlex]
ATK_ADD(wd.damage, wd.damage2, ((wd.div_ < 1) ? 1 : wd.div_) * sd->spiritball_old * 3);
} else
ATK_ADD(wd.damage, wd.damage2, ((wd.div_ < 1) ? 1 : wd.div_) * sd->spiritball * 3);
#endif
if( skill_id == CR_SHIELDBOOMERANG || skill_id == PA_SHIELDCHAIN ) { //Refine bonus applies after cards and elements.
short index = sd->equip_index[EQI_HAND_L];
if( index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR )
ATK_ADD(wd.damage, wd.damage2, 10*sd->inventory.u.items_inventory[index].refine);
}
#ifndef RENEWAL
//Card Fix for attacker (sd), 2 is added to the "left" flag meaning "attacker cards only"
switch(skill_id) {
case RK_DRAGONBREATH:
case RK_DRAGONBREATH_WATER:
if(wd.flag&BF_LONG) { //Add check here, because we want to apply the same behavior in pre-renewal [exneval]
wd.damage = wd.damage * (100 + sd->bonus.long_attack_atk_rate) / 100;
if(is_attack_left_handed(src, skill_id))
wd.damage2 = wd.damage2 * (100 + sd->bonus.long_attack_atk_rate) / 100;
}
break;
default:
wd.damage += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage, 2, wd.flag);
if( is_attack_left_handed(src, skill_id ))
wd.damage2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage2, 3, wd.flag);
break;
}
#endif
}
#ifdef RENEWAL
// In renewal only do it for non player attacks
if( tsd && !sd ){
#else
if( tsd ){
#endif
// Card Fix for target (tsd), 2 is not added to the "left" flag meaning "target cards only"
wd.damage += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage, 0, wd.flag);
if(is_attack_left_handed(src, skill_id))
wd.damage2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage2, 1, wd.flag);
}
// only do 1 dmg to plant, no need to calculate rest
if(infdef){
battle_calc_attack_plant(&wd, src, target, skill_id, skill_lv);
return wd;
}
//Apply DAMAGE_DIV_FIX and check for min damage
battle_apply_div_fix(&wd, skill_id);
battle_calc_attack_left_right_hands(&wd, src, target, skill_id, skill_lv);
#ifdef RENEWAL
switch (skill_id) {
case NJ_ISSEN:
case GN_FIRE_EXPANSION_ACID:
return wd; //These skills will do a GVG fix later
default:
#endif
battle_calc_attack_gvg_bg(&wd, src, target, skill_id, skill_lv);
#ifdef RENEWAL
break;
}
#endif
battle_calc_weapon_final_atk_modifiers(&wd, src, target, skill_id, skill_lv);
battle_absorb_damage(target, &wd);
battle_do_reflect(BF_WEAPON,&wd, src, target, skill_id, skill_lv); //WIP [lighta]
return wd;
}
/*==========================================
* Calculate "magic"-type attacks and skills
*------------------------------------------
* Credits:
* Original coder DracoRPG
* Refined and optimized by helvetica
*/
struct Damage battle_calc_magic_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag)
{
int i, skill_damage = 0;
short s_ele = 0;
TBL_PC *sd;
TBL_PC *tsd;
struct status_change *sc, *tsc;
struct Damage ad;
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
struct {
unsigned imdef : 1;
unsigned infdef : 1;
} flag;
memset(&ad,0,sizeof(ad));
memset(&flag,0,sizeof(flag));
if (src == NULL || target == NULL) {
nullpo_info(NLP_MARK);
return ad;
}
//Initial Values
ad.damage = 1;
ad.div_ = skill_get_num(skill_id,skill_lv);
ad.amotion = (skill_get_inf(skill_id)&INF_GROUND_SKILL ? 0 : sstatus->amotion); //Amotion should be 0 for ground skills.
ad.dmotion = tstatus->dmotion;
ad.blewcount = skill_get_blewcount(skill_id, skill_lv);
ad.flag = BF_MAGIC|BF_SKILL;
ad.dmg_lv = ATK_DEF;
std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id);
std::bitset<NK_MAX> nk;
if (skill)
nk = skill->nk;
flag.imdef = nk[NK_IGNOREDEFENSE] ? 1 : 0;
sd = BL_CAST(BL_PC, src);
tsd = BL_CAST(BL_PC, target);
s_elemental_data* ed = BL_CAST(BL_ELEM, src);
sc = status_get_sc(src);
tsc = status_get_sc(target);
//Initialize variables that will be used afterwards
s_ele = skill_get_ele(skill_id, skill_lv);
if (s_ele == ELE_WEAPON) { // pl=-1 : the skill takes the weapon's element
s_ele = sstatus->rhw.ele;
if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm >= MAX_SPIRITCHARM)
s_ele = sd->spiritcharm_type; // Summoning 10 spiritcharm will endow your weapon
} else if (s_ele == ELE_ENDOWED) //Use status element
s_ele = status_get_attack_sc_element(src,status_get_sc(src));
else if (s_ele == ELE_RANDOM) //Use random element
s_ele = rnd()%ELE_ALL;
switch(skill_id) {
case NPC_EARTHQUAKE:
s_ele = ELE_NEUTRAL;
break;
case WL_HELLINFERNO:
if (mflag & 2) { // ELE_DARK
s_ele = ELE_DARK;
ad.div_ = 3;
}
break;
case WM_REVERBERATION:
if (sd)
s_ele = sd->bonus.arrow_ele;
break;
case NPC_PSYCHIC_WAVE:
case SO_PSYCHIC_WAVE:
if (sd && (sd->weapontype1 == W_STAFF || sd->weapontype1 == W_2HSTAFF || sd->weapontype1 == W_BOOK))
ad.div_ = 2;
if( sc && sc->count ) {
if( sc->data[SC_HEATER_OPTION] )
s_ele = sc->data[SC_HEATER_OPTION]->val3;
else if( sc->data[SC_COOLER_OPTION] )
s_ele = sc->data[SC_COOLER_OPTION]->val3;
else if( sc->data[SC_BLAST_OPTION] )
s_ele = sc->data[SC_BLAST_OPTION]->val3;
else if( sc->data[SC_CURSED_SOIL_OPTION] )
s_ele = sc->data[SC_CURSED_SOIL_OPTION]->val3;
else if( sc->data[SC_FLAMETECHNIC_OPTION] )
s_ele = sc->data[SC_FLAMETECHNIC_OPTION]->val3;
else if( sc->data[SC_COLD_FORCE_OPTION] )
s_ele = sc->data[SC_COLD_FORCE_OPTION]->val3;
else if( sc->data[SC_GRACE_BREEZE_OPTION] )
s_ele = sc->data[SC_GRACE_BREEZE_OPTION]->val3;
else if( sc->data[SC_EARTH_CARE_OPTION] )
s_ele = sc->data[SC_EARTH_CARE_OPTION]->val3;
else if( sc->data[SC_DEEP_POISONING_OPTION] )
s_ele = sc->data[SC_DEEP_POISONING_OPTION]->val3;
}
break;
case KO_KAIHOU:
if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm > 0)
s_ele = sd->spiritcharm_type;
break;
case AB_ADORAMUS:
if (sc && sc->data[SC_ANCILLA])
s_ele = ELE_NEUTRAL;
break;
case LG_RAYOFGENESIS:
if (sc && sc->data[SC_INSPIRATION])
s_ele = ELE_NEUTRAL;
break;
case AG_DESTRUCTIVE_HURRICANE:
if (sc && sc->data[SC_CLIMAX] && sc->data[SC_CLIMAX]->val1 == 2)
ad.blewcount = 2;
break;
case AG_CRYSTAL_IMPACT:
if (sc && sc->data[SC_CLIMAX] && sc->data[SC_CLIMAX]->val1 == 2)
ad.div_ = 2;
break;
case ABC_ABYSS_SQUARE:
if (mflag == 2)
ad.div_ = 2;
break;
case TR_METALIC_FURY:// Deals up to 5 additional hits. But what affects the number of hits? [Rytech]
ad.div_ = min(ad.div_ + mflag, 5); // Number of hits doesn't go above 5.
// Fall through and check arrow element
case TR_SOUNDBLEND:
if (sd)
s_ele = sd->bonus.arrow_ele;
break;
}
//Set miscellaneous data that needs be filled
if(sd) {
sd->state.arrow_atk = 0;
ad.blewcount += battle_blewcount_bonus(sd, skill_id);
}
//Skill Range Criteria
ad.flag |= battle_range_type(src, target, skill_id, skill_lv);
//Infinite defense (plant mode)
flag.infdef = is_infinite_defense(target, ad.flag)?1:0;
switch(skill_id) {
case MG_FIREWALL:
if ( tstatus->def_ele == ELE_FIRE || battle_check_undead(tstatus->race, tstatus->def_ele) )
ad.blewcount = 0; //No knockback
//Fall through
case NJ_KAENSIN:
case PR_SANCTUARY:
ad.dmotion = 1; //No flinch animation.
break;
}
if (!flag.infdef) { //No need to do the math for plants
unsigned int skillratio = 100; //Skill dmg modifiers.
#ifdef RENEWAL
ad.damage = 0; //reinitialize..
#endif
//MATK_RATE scales the damage. 100 = no change. 50 is halved, 200 is doubled, etc
#define MATK_RATE(a) { ad.damage = ad.damage * (a) / 100; }
//Adds dmg%. 100 = +100% (double) damage. 10 = +10% damage
#define MATK_ADDRATE(a) { ad.damage += ad.damage * (a) / 100; }
//Adds an absolute value to damage. 100 = +100 damage
#define MATK_ADD(a) { ad.damage += a; }
//Calc base damage according to skill
switch (skill_id) {
case AL_HEAL:
case PR_BENEDICTIO:
case PR_SANCTUARY:
case AB_HIGHNESSHEAL:
ad.damage = skill_calc_heal(src, target, skill_id, skill_lv, false);
break;
case PR_ASPERSIO:
ad.damage = 40;
break;
case ALL_RESURRECTION:
case PR_TURNUNDEAD:
//Undead check is on skill_castend_damageid code.
#ifdef RENEWAL
i = 10 * skill_lv + sstatus->luk + sstatus->int_ + status_get_lv(src)
+ 300 - 300 * tstatus->hp / tstatus->max_hp;
#else
i = 20 * skill_lv + sstatus->luk + sstatus->int_ + status_get_lv(src)
+ 200 - 200 * tstatus->hp / tstatus->max_hp;
#endif
if(i > 700)
i = 700;
if(rnd()%1000 < i && !status_has_mode(tstatus,MD_STATUSIMMUNE))
ad.damage = tstatus->hp;
else {
#ifdef RENEWAL
if (sstatus->matk_max > sstatus->matk_min) {
MATK_ADD(sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min));
} else {
MATK_ADD(sstatus->matk_min);
}
MATK_RATE(skill_lv);
#else
ad.damage = status_get_lv(src) + sstatus->int_ + skill_lv * 10;
#endif
}
break;
case NPC_DARKBREATH:
ad.damage = tstatus->hp * (skill_lv <= 5 ? 100 / (2 * 6 - skill_lv) : 50) / 100;
break;
case PF_SOULBURN:
ad.damage = tstatus->sp * 2;
break;
case AB_RENOVATIO:
ad.damage = status_get_lv(src) * 10 + sstatus->int_;
break;
case NPC_EARTHQUAKE:
if (mflag & NPC_EARTHQUAKE_FLAG) {
ad.flag |= NPC_EARTHQUAKE_FLAG; // Pass flag to battle_calc_damage
mflag &= ~NPC_EARTHQUAKE_FLAG; // Remove before NK_SPLASHSPLIT check
}
if (src->type == BL_PC)
ad.damage = sstatus->str * 2 + battle_calc_weapon_attack(src, target, skill_id, skill_lv, mflag).damage;
else
ad.damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, 0);
MATK_RATE(200 + 100 * skill_lv + 100 * (skill_lv / 2) + ((skill_lv > 4) ? 100 : 0));
if (nk[NK_SPLASHSPLIT] && mflag > 1)
ad.damage /= mflag;
break;
case NPC_ICEMINE:
case NPC_FLAMECROSS:
ad.damage = sstatus->rhw.atk * 20 * skill_lv;
break;
default: {
if (sstatus->matk_max > sstatus->matk_min) {
MATK_ADD(sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min));
} else {
MATK_ADD(sstatus->matk_min);
}
if (sd) { // Soul energy spheres adds MATK.
MATK_ADD(3*sd->soulball);
}
if (nk[NK_SPLASHSPLIT]) { // Divide MATK in case of multiple targets skill
if (mflag>0)
ad.damage /= mflag;
else
ShowError("0 enemies targeted by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id));
}
switch(skill_id) {
case MG_NAPALMBEAT:
skillratio += -30 + 10 * skill_lv;
break;
case MG_FIREBALL:
#ifdef RENEWAL
skillratio += 40 + 20 * skill_lv;
if(ad.miscflag == 2) //Enemies at the edge of the area will take 75% of the damage
skillratio = skillratio * 3 / 4;
#else
skillratio += -30 + 10 * skill_lv;
#endif
break;
case MG_SOULSTRIKE:
if (battle_check_undead(tstatus->race,tstatus->def_ele))
skillratio += 5 * skill_lv;
break;
case MG_FIREWALL:
skillratio -= 50;
break;
case MG_FIREBOLT:
case MG_COLDBOLT:
case MG_LIGHTNINGBOLT:
if (sc) {
if ((skill_id == MG_FIREBOLT && sc->data[SC_FLAMETECHNIC_OPTION]) ||
(skill_id == MG_COLDBOLT && sc->data[SC_COLD_FORCE_OPTION]) ||
(skill_id == MG_LIGHTNINGBOLT && sc->data[SC_GRACE_BREEZE_OPTION]))
skillratio *= 2;
if (sc->data[SC_SPELLFIST] && mflag & BF_SHORT) {
skillratio += (sc->data[SC_SPELLFIST]->val3 * 100) + (sc->data[SC_SPELLFIST]->val1 * 50 - 50) - 100; // val3 = used bolt level, val1 = used spellfist level. [Rytech]
ad.div_ = 1; // ad mods, to make it work similar to regular hits [Xazax]
ad.flag = BF_WEAPON | BF_SHORT;
ad.type = DMG_NORMAL;
}
}
break;
case MG_THUNDERSTORM:
// in Renewal Thunder Storm boost is 100% (in pre-re, 80%)
#ifndef RENEWAL
skillratio -= 20;
#endif
break;
case MG_FROSTDIVER:
skillratio += 10 * skill_lv;
break;
case AL_HOLYLIGHT:
skillratio += 25;
if (sd && sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_PRIEST)
skillratio *= 5; //Does 5x damage include bonuses from other skills?
break;
case AL_RUWACH:
skillratio += 45;
break;
case WZ_FROSTNOVA:
skillratio += -100 + (100 + skill_lv * 10) * 2 / 3;
break;
case WZ_FIREPILLAR:
if (sd && ad.div_ > 0)
ad.div_ *= -1; //For players, damage is divided by number of hits
skillratio += -60 + 20 * skill_lv; //20% MATK each hit
break;
case WZ_SIGHTRASHER:
skillratio += 20 * skill_lv;
break;
case WZ_WATERBALL:
skillratio += 30 * skill_lv;
break;
case WZ_STORMGUST:
#ifdef RENEWAL
skillratio -= 30; // Offset only once
skillratio += 50 * skill_lv;
#else
skillratio += 40 * skill_lv;
#endif
break;
#ifdef RENEWAL
case WZ_EARTHSPIKE:
skillratio += 100;
if (sc && sc->data[SC_EARTH_CARE_OPTION])
skillratio += skillratio * 80 / 100;
break;
#endif
case HW_NAPALMVULCAN:
#ifdef RENEWAL
skillratio += -100 + 70 * skill_lv;
RE_LVL_DMOD(100);
#else
skillratio += 25;
#endif
break;
case SL_STIN: //Target size must be small (0) for full damage
skillratio += (tstatus->size != SZ_SMALL ? -99 : 10 * skill_lv);
break;
case SL_STUN:
skillratio += 5 * skill_lv;
break;
case SL_SMA: //Base damage is 40% + lv%
skillratio += -60 + status_get_lv(src);
break;
case NJ_KOUENKA:
skillratio -= 10;
if(sd && sd->spiritcharm_type == CHARM_TYPE_FIRE && sd->spiritcharm > 0)
skillratio += 10 * sd->spiritcharm;
break;
case NJ_KAENSIN:
skillratio -= 50;
if(sd && sd->spiritcharm_type == CHARM_TYPE_FIRE && sd->spiritcharm > 0)
skillratio += 20 * sd->spiritcharm;
break;
case NJ_BAKUENRYU:
skillratio += 50 + 150 * skill_lv;
if(sd && sd->spiritcharm_type == CHARM_TYPE_FIRE && sd->spiritcharm > 0)
skillratio += 100 * sd->spiritcharm;
break;
case NJ_HYOUSENSOU:
#ifdef RENEWAL
skillratio -= 30;
if (sc && sc->data[SC_SUITON])
skillratio += 2 * skill_lv;
#endif
if(sd && sd->spiritcharm_type == CHARM_TYPE_WATER && sd->spiritcharm > 0)
skillratio += 20 * sd->spiritcharm;
break;
case NJ_HYOUSYOURAKU:
skillratio += 50 * skill_lv;
if(sd && sd->spiritcharm_type == CHARM_TYPE_WATER && sd->spiritcharm > 0)
skillratio += 100 * sd->spiritcharm;
break;
case NJ_RAIGEKISAI:
#ifdef RENEWAL
skillratio += 100 * skill_lv;
#else
skillratio += 60 + 40 * skill_lv;
#endif
if(sd && sd->spiritcharm_type == CHARM_TYPE_WIND && sd->spiritcharm > 0)
skillratio += 20 * sd->spiritcharm;
break;
case NJ_KAMAITACHI:
skillratio += 100 * skill_lv;
if(sd && sd->spiritcharm_type == CHARM_TYPE_WIND && sd->spiritcharm > 0)
skillratio += 100 * sd->spiritcharm;
break;
case NJ_HUUJIN:
#ifdef RENEWAL
skillratio += 50;
#endif
if(sd && sd->spiritcharm_type == CHARM_TYPE_WIND && sd->spiritcharm > 0)
skillratio += 10 * sd->spiritcharm;
break;
case NPC_ENERGYDRAIN:
skillratio += 100 * skill_lv;
break;
#ifdef RENEWAL
case WZ_HEAVENDRIVE:
skillratio += 25;
break;
case WZ_METEOR:
skillratio += 25;
break;
case WZ_VERMILION:
if(sd)
skillratio += 300 + skill_lv * 100;
else
skillratio += 20 * skill_lv - 20; //Monsters use old formula
break;
case PR_MAGNUS:
if (battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON)
skillratio += 30;
break;
case BA_DISSONANCE:
skillratio += skill_lv * 10;
if (sd)
skillratio += 3 * pc_checkskill(sd, BA_MUSICALLESSON);
break;
case HW_GRAVITATION:
skillratio += -100 + 100 * skill_lv;
RE_LVL_DMOD(100);
break;
case PA_PRESSURE:
skillratio += -100 + 500 + 150 * skill_lv;
RE_LVL_DMOD(100);
break;
case WZ_SIGHTBLASTER:
skillratio += 500;
break;
#else
case WZ_VERMILION:
skillratio += 20 * skill_lv - 20;
break;
#endif
case AB_JUDEX:
skillratio += -100 + 300 + 70 * skill_lv;
RE_LVL_DMOD(100);
break;
case AB_ADORAMUS:
skillratio += - 100 + 300 + 250 * skill_lv;
RE_LVL_DMOD(100);
break;
case AB_DUPLELIGHT_MAGIC:
skillratio += 300 + 40 * skill_lv;
break;
case WL_SOULEXPANSION:
skillratio += -100 + 1000 + skill_lv * 200 + sstatus->int_ / 6; // !TODO: Confirm INT bonus
RE_LVL_DMOD(100);
break;
case WL_FROSTMISTY:
skillratio += -100 + 200 + 100 * skill_lv;
RE_LVL_DMOD(100);
break;
case NPC_JACKFROST:
if (tsc && tsc->data[SC_FREEZING]) {
skillratio += 900 + 300 * skill_lv;
RE_LVL_DMOD(100);
} else {
skillratio += 400 + 100 * skill_lv;
RE_LVL_DMOD(150);
}
break;
case WL_JACKFROST:
if (tsc && tsc->data[SC_MISTY_FROST])
skillratio += -100 + 1200 + 600 * skill_lv;
else
skillratio += -100 + 1000 + 300 * skill_lv;
RE_LVL_DMOD(100);
break;
case WL_DRAINLIFE:
skillratio += -100 + 200 * skill_lv + sstatus->int_;
RE_LVL_DMOD(100);
break;
case WL_CRIMSONROCK:
skillratio += -100 + 700 + 600 * skill_lv;
RE_LVL_DMOD(100);
break;
case WL_HELLINFERNO:
skillratio += -100 + 400 * skill_lv;
if (mflag & 2) // ELE_DARK
skillratio += 200;
RE_LVL_DMOD(100);
break;
case WL_COMET:
skillratio += -100 + 2500 + 700 * skill_lv;
RE_LVL_DMOD(100);
break;
case WL_CHAINLIGHTNING_ATK:
skillratio += 400 + 100 * skill_lv;
RE_LVL_DMOD(100);
if (mflag > 0)
skillratio += 100 * mflag;
break;
case WL_EARTHSTRAIN:
skillratio += -100 + 1000 + 600 * skill_lv;
RE_LVL_DMOD(100);
break;
case WL_TETRAVORTEX_FIRE:
case WL_TETRAVORTEX_WATER:
case WL_TETRAVORTEX_WIND:
case WL_TETRAVORTEX_GROUND:
skillratio += -100 + 800 + 400 * skill_lv;
break;
case WL_SUMMON_ATK_FIRE:
case WL_SUMMON_ATK_WATER:
case WL_SUMMON_ATK_WIND:
case WL_SUMMON_ATK_GROUND:
skillratio += -100 + (1 + skill_lv) / 2 * (status_get_lv(src) + (sd ? sd->status.job_level : 0));
RE_LVL_DMOD(100); // ! TODO: Confirm new formula
break;
case LG_RAYOFGENESIS:
skillratio += -100 + 230 * skill_lv + sstatus->int_ / 6; // !TODO: What's the INT bonus?
if (sc && sc->data[SC_INSPIRATION])
skillratio += 70 * skill_lv;
RE_LVL_DMOD(100);
break;
case NPC_RAYOFGENESIS:
skillratio += -100 + 200 * skill_lv;
break;
case WM_METALICSOUND:
skillratio += -100 + 120 * skill_lv + 60 * ((sd) ? pc_checkskill(sd, WM_LESSON) : 1);
if (tsc && tsc->data[SC_SLEEP])
skillratio += 100; // !TODO: Confirm target sleeping bonus
RE_LVL_DMOD(100);
if (tsc && tsc->data[SC_SOUNDBLEND])
skillratio += skillratio * 50 / 100;
break;
case WM_REVERBERATION:
// MATK [{(Skill Level x 300) + 400} x Casters Base Level / 100] %
skillratio += -100 + 700 + 300 * skill_lv;
RE_LVL_DMOD(100);
if (tsc && tsc->data[SC_SOUNDBLEND])
skillratio += skillratio * 50 / 100;
break;
case SO_FIREWALK:
skillratio += -100 + 60 * skill_lv;
RE_LVL_DMOD(100);
if( sc && sc->data[SC_HEATER_OPTION] )
skillratio += (sd ? sd->status.job_level / 2 : 0);
break;
case SO_ELECTRICWALK:
skillratio += -100 + 60 * skill_lv;
RE_LVL_DMOD(100);
if( sc && sc->data[SC_BLAST_OPTION] )
skillratio += (sd ? sd->status.job_level / 2 : 0);
break;
case NPC_FIREWALK:
case NPC_ELECTRICWALK:
skillratio += -100 + 100 * skill_lv;
break;
case SO_EARTHGRAVE:
skillratio += -100 + 2 * sstatus->int_ + 300 * pc_checkskill(sd, SA_SEISMICWEAPON) + sstatus->int_ * skill_lv;
RE_LVL_DMOD(100);
if( sc && sc->data[SC_CURSED_SOIL_OPTION] )
skillratio += (sd ? sd->status.job_level * 5 : 0);
break;
case SO_DIAMONDDUST:
skillratio += -100 + 2 * sstatus->int_ + 300 * pc_checkskill(sd, SA_FROSTWEAPON) + sstatus->int_ * skill_lv;
RE_LVL_DMOD(100);
if( sc && sc->data[SC_COOLER_OPTION] )
skillratio += (sd ? sd->status.job_level * 5 : 0);
break;
case SO_POISON_BUSTER:
skillratio += -100 + 1000 + 300 * skill_lv + sstatus->int_ / 6; // !TODO: Confirm INT bonus
if( tsc && tsc->data[SC_CLOUD_POISON] )
skillratio += 200 * skill_lv;
RE_LVL_DMOD(100);
if( sc && sc->data[SC_CURSED_SOIL_OPTION] )
skillratio += (sd ? sd->status.job_level * 5 : 0);
break;
case NPC_POISON_BUSTER:
skillratio += -100 + 1500 * skill_lv;
break;
case SO_PSYCHIC_WAVE:
skillratio += -100 + 70 * skill_lv + 3 * sstatus->int_;
RE_LVL_DMOD(100);
if (sc && (sc->data[SC_HEATER_OPTION] || sc->data[SC_COOLER_OPTION] ||
sc->data[SC_BLAST_OPTION] || sc->data[SC_CURSED_SOIL_OPTION]))
skillratio += 20;
break;
case NPC_PSYCHIC_WAVE:
skillratio += -100 + 500 * skill_lv;
break;
case SO_CLOUD_KILL:
skillratio += -100 + 40 * skill_lv;
RE_LVL_DMOD(100);
if (sc) {
if (sc->data[SC_CURSED_SOIL_OPTION])
skillratio += (sd ? sd->status.job_level : 0);
if (sc->data[SC_DEEP_POISONING_OPTION])
skillratio += skillratio * 50 / 100;
}
break;
case NPC_CLOUD_KILL:
skillratio += -100 + 50 * skill_lv;
break;
case SO_VARETYR_SPEAR:
skillratio += -100 + (2 * sstatus->int_ + 150 * (pc_checkskill(sd, SO_STRIKING) + pc_checkskill(sd, SA_LIGHTNINGLOADER)) + sstatus->int_ * skill_lv / 2) / 3;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_BLAST_OPTION])
skillratio += (sd ? sd->status.job_level * 5 : 0);
break;
case GN_DEMONIC_FIRE:
if (skill_lv > 20) // Fire expansion Lv.2
skillratio += 10 + 20 * (skill_lv - 20) + status_get_int(src) * 10;
else if (skill_lv > 10) { // Fire expansion Lv.1
skillratio += 10 + 20 * (skill_lv - 10) + status_get_int(src) + ((sd) ? sd->status.job_level : 50);
RE_LVL_DMOD(100);
} else
skillratio += 10 + 20 * skill_lv;
break;
case KO_KAIHOU:
if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm > 0) {
skillratio += -100 + 200 * sd->spiritcharm;
RE_LVL_DMOD(100);
pc_delspiritcharm(sd, sd->spiritcharm, sd->spiritcharm_type);
}
break;
// Magical Elemental Spirits Attack Skills
case EL_FIRE_MANTLE:
case EL_WATER_SCREW:
skillratio += 900;
break;
case EL_FIRE_ARROW:
case EL_ROCK_CRUSHER_ATK:
skillratio += 200;
break;
case EL_FIRE_BOMB:
case EL_ICE_NEEDLE:
case EL_HURRICANE_ATK:
skillratio += 400;
break;
case EL_FIRE_WAVE:
case EL_TYPOON_MIS_ATK:
skillratio += 1100;
break;
case MH_ERASER_CUTTER:
case MH_XENO_SLASHER:
skillratio += -100 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->int_ / 6; // !TODO: Confirm Base Level and INT bonus
break;
case MH_HEILIGE_STANGE:
skillratio += -100 + 1500 + 250 * skill_lv * status_get_lv(src) / 150 + sstatus->vit / 6; // !TODO: Confirm VIT bonus
break;
case MH_POISON_MIST:
skillratio += -100 + 200 * skill_lv * status_get_lv(src) / 100 + sstatus->dex / 6; // ! TODO: Confirm DEX bonus
break;
case SU_SV_STEMSPEAR:
skillratio += 600;
break;
case SU_CN_METEOR:
case SU_CN_METEOR2:
skillratio += 100 + 100 * skill_lv;
break;
case NPC_VENOMFOG:
skillratio += 600 + 100 * skill_lv;
break;
case NPC_COMET:
i = (sc ? distance_xy(target->x, target->y, sc->comet_x, sc->comet_y) : 8) / 2;
i = cap_value(i, 1, 4);
skillratio = 2500 + ((skill_lv - i + 1) * 500);
break;
case NPC_FIRESTORM:
skillratio += 200;
break;
case NPC_HELLBURNING:
skillratio += 900;
break;
case NPC_PULSESTRIKE2:
skillratio += 100;
break;
case SP_CURSEEXPLOSION:
if (tsc && tsc->data[SC_SOULCURSE])
skillratio += 1400 + 200 * skill_lv;
else
skillratio += 300 + 100 * skill_lv;
break;
case SP_SPA:
skillratio += 400 + 250 * skill_lv;
RE_LVL_DMOD(100);
break;
case SP_SHA:
skillratio += -100 + 5 * skill_lv;
break;
case SP_SWHOO:
skillratio += 1000 + 200 * skill_lv;
RE_LVL_DMOD(100);
break;
case NPC_STORMGUST2:
skillratio += 200 * skill_lv;
break;
case AG_DEADLY_PROJECTION:
skillratio += -100 + 600 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_DESTRUCTIVE_HURRICANE:
skillratio += -100 + 1600 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CLIMAX])
{
if (sc->data[SC_CLIMAX]->val1 == 3)
skillratio *= 2;
else if (sc->data[SC_CLIMAX]->val1 == 5)
skillratio += skillratio * 70 / 100;
}
break;
case AG_RAIN_OF_CRYSTAL:
skillratio += -100 + 150 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_MYSTERY_ILLUSION:
skillratio += -100 + 250 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_VIOLENT_QUAKE_ATK:
skillratio += -100 + 120 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CLIMAX]) {
if (sc->data[SC_CLIMAX]->val1 == 1)
skillratio /= 2;
else if (sc->data[SC_CLIMAX]->val1 == 3)
skillratio *= 2;
}
break;
case AG_SOUL_VC_STRIKE:
skillratio += -100 + 180 * skill_lv + 3 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_STRANTUM_TREMOR:
skillratio += -100 + 250 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_ALL_BLOOM_ATK:
skillratio += -100 + 100 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CLIMAX]) {
if (sc->data[SC_CLIMAX]->val1 == 2)
skillratio /= 2;
else if (sc->data[SC_CLIMAX]->val1 == 3)
skillratio *= 2;
}
break;
case AG_ALL_BLOOM_ATK2:// Is this affected by BaseLV and SPL too??? [Rytech]
skillratio += -100 + 7000 + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_CRYSTAL_IMPACT:
skillratio += -100 + 800 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CLIMAX]) {
if (sc->data[SC_CLIMAX]->val1 == 3)
skillratio += skillratio * 50 / 100;
else if (sc->data[SC_CLIMAX]->val1 == 4)
skillratio /= 2;
}
break;
case AG_CRYSTAL_IMPACT_ATK:// Said to deal the same damage as the main attack.
skillratio += -100 + 800 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_CLIMAX] && sc->data[SC_CLIMAX]->val1 == 4)
skillratio += skillratio * 150 / 100;
break;
case AG_TORNADO_STORM:
skillratio += -100 + 90 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_FLORAL_FLARE_ROAD:
skillratio += -100 + 200 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_ASTRAL_STRIKE:
skillratio += -100 + 500 * skill_lv + 10 * sstatus->spl;
if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DRAGON)
skillratio += 600 * skill_lv;
RE_LVL_DMOD(100);
break;
case AG_ASTRAL_STRIKE_ATK:
skillratio += -100 + 200 * skill_lv + 10 * sstatus->spl;
// Not confirmed, but if the main hit deal additional damage
// on certain races then the repeated damage should too right?
// Guessing a formula here for now. [Rytech]
if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DRAGON)
skillratio += 200 * skill_lv;
RE_LVL_DMOD(100);
break;
case AG_ROCK_DOWN:
skillratio += -100 + 750 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_STORM_CANNON:
skillratio += -100 + 600 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_CRIMSON_ARROW:
case AG_CRIMSON_ARROW_ATK:
skillratio += -100 + 300 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case AG_FROZEN_SLASH:
skillratio += -100 + 750 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case IG_JUDGEMENT_CROSS:
skillratio += -100 + 750 * skill_lv + 10 * sstatus->spl;
if (tstatus->race == RC_PLANT || tstatus->race == RC_INSECT)
skillratio += 350 * skill_lv;
RE_LVL_DMOD(100);
if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0)
skillratio += skillratio * i / 100;
break;
case IG_CROSS_RAIN:// Need official damage increase from Spear and Sword Mastery. [Rytech]
skillratio += -100 + 30 * skill_lv + 5 * sstatus->spl + 5 * pc_checkskill(sd, IG_SPEAR_SWORD_M);
RE_LVL_DMOD(100);
if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0)
skillratio += skillratio * i / 100;
if (sc && sc->data[SC_HOLY_S])
skillratio += 20 * skill_lv;
break;
case CD_ARBITRIUM:
case CD_ARBITRIUM_ATK:
skillratio += -100 + 200 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case CD_PNEUMATICUS_PROCELLA:
skillratio += -100 + 200 * skill_lv + 10 * sstatus->spl;
if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DEMON)
skillratio += 100 * skill_lv;
RE_LVL_DMOD(100);
break;
case CD_FRAMEN:
skillratio += -100 + 500 * skill_lv + 5 * sstatus->spl;
if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DEMON)
skillratio += 150 * skill_lv;
RE_LVL_DMOD(100);
break;
case AG_DESTRUCTIVE_HURRICANE_CLIMAX:// Is this affected by BaseLV and SPL too??? [Rytech]
skillratio += -100 + 500 + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case ABC_ABYSS_STRIKE:
skillratio += -100 + 600 * skill_lv + 10 * sstatus->spl;
if (tstatus->race == RC_DEMON || tstatus->race == RC_ANGEL)
skillratio += 550 * skill_lv;
RE_LVL_DMOD(100);
break;
case ABC_ABYSS_SQUARE:
skillratio += -100 + 140 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case TR_METALIC_FURY:
skillratio += -100 + 600 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case TR_SOUNDBLEND:
skillratio += -100 + 120 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_MYSTIC_SYMPHONY]) {
skillratio += skillratio * 40 / 100;
if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN)
skillratio += skillratio * 50 / 100;
}
break;
case EM_DIAMOND_STORM:
skillratio += -100 + 700 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SUMMON_ELEMENTAL_DILUVIO])
skillratio += skillratio * 30 / 100;
break;
case EM_LIGHTNING_LAND:
skillratio += -100 + 150 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SUMMON_ELEMENTAL_PROCELLA])
skillratio += skillratio * 30 / 100;
break;
case EM_VENOM_SWAMP:
skillratio += -100 + 150 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SUMMON_ELEMENTAL_SERPENS])
skillratio += skillratio * 30 / 100;
break;
case EM_CONFLAGRATION:
skillratio += -100 + 150 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SUMMON_ELEMENTAL_ARDOR])
skillratio += skillratio * 30 / 100;
break;
case EM_TERRA_DRIVE:
skillratio += -100 + 700 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
if (sc && sc->data[SC_SUMMON_ELEMENTAL_TERREMOTUS])
skillratio += skillratio * 30 / 100;
break;
case ABC_FROM_THE_ABYSS_ATK:
skillratio += 50 + 70 * skill_lv + 5 * sstatus->spl;
RE_LVL_DMOD(100);
break;
case EM_ELEMENTAL_BUSTER_FIRE:
case EM_ELEMENTAL_BUSTER_WATER:
case EM_ELEMENTAL_BUSTER_WIND:
case EM_ELEMENTAL_BUSTER_GROUND:
case EM_ELEMENTAL_BUSTER_POISON:
skillratio += -100 + 480 * skill_lv + 10 * sstatus->spl;
if (tstatus->race == RC_FORMLESS || tstatus->race == RC_DRAGON)
skillratio += 620 * skill_lv;
RE_LVL_DMOD(100);
break;
case EM_EL_FLAMEROCK:
skillratio += -100 + 2400;
if (ed)
skillratio += skillratio * status_get_lv(&ed->master->bl) / 100;
break;
case EM_EL_AGE_OF_ICE:
skillratio += -100 + 3700;
if (ed)
skillratio += skillratio * status_get_lv(&ed->master->bl) / 100;
break;
case EM_EL_STORM_WIND:
skillratio += -100 + 2600;
if (ed)
skillratio += skillratio * status_get_lv(&ed->master->bl) / 100;
break;
case EM_EL_AVALANCHE:
skillratio += -100 + 450;
if (ed)
skillratio += skillratio * status_get_lv(&ed->master->bl) / 100;
break;
case EM_EL_DEADLY_POISON:
skillratio += -100 + 700;
if (ed)
skillratio += skillratio * status_get_lv(&ed->master->bl) / 100;
break;
}
if (sc) {// Insignia's increases the damage of offensive magic by a fixed percentage depending on the element.
if ((sc->data[SC_FIRE_INSIGNIA] && sc->data[SC_FIRE_INSIGNIA]->val1 == 3 && s_ele == ELE_FIRE) ||
(sc->data[SC_WATER_INSIGNIA] && sc->data[SC_WATER_INSIGNIA]->val1 == 3 && s_ele == ELE_WATER) ||
(sc->data[SC_WIND_INSIGNIA] && sc->data[SC_WIND_INSIGNIA]->val1 == 3 && s_ele == ELE_WIND) ||
(sc->data[SC_EARTH_INSIGNIA] && sc->data[SC_EARTH_INSIGNIA]->val1 == 3 && s_ele == ELE_EARTH))
skillratio += 25;
}
MATK_RATE(skillratio);
//Constant/misc additions from skills
if (skill_id == WZ_FIREPILLAR)
MATK_ADD(100 + 50 * skill_lv);
break;
}
}
#ifdef RENEWAL
ad.damage += battle_calc_cardfix(BF_MAGIC, src, target, nk, s_ele, 0, ad.damage, 0, ad.flag);
#endif
if(sd) {
//Damage bonuses
if ((i = pc_skillatk_bonus(sd, skill_id)))
ad.damage += (int64)ad.damage*i/100;
//Ignore Defense?
if (!flag.imdef && (
sd->bonus.ignore_mdef_ele & ( 1 << tstatus->def_ele ) || sd->bonus.ignore_mdef_ele & ( 1 << ELE_ALL ) ||
sd->bonus.ignore_mdef_race & ( 1 << tstatus->race ) || sd->bonus.ignore_mdef_race & ( 1 << RC_ALL ) ||
sd->bonus.ignore_mdef_class & ( 1 << tstatus->class_ ) || sd->bonus.ignore_mdef_class & ( 1 << CLASS_ALL )
))
flag.imdef = 1;
}
if (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id)))
ad.damage -= (int64)ad.damage*i/100;
#ifdef RENEWAL
if (sd && sstatus->smatk > 0)
ad.damage += ad.damage * sstatus->smatk / 100;
// MRes reduces magical damage by a percentage and
// is calculated before MDEF and other reductions.
// This should be the official formula. [Rytech]
if (ad.damage && tstatus->mres > 0) {
short mres = tstatus->mres;
short ignore_mres = 0;// Value used as percentage.
// Attacker status's that pierce MRes.
if (sc && sc->data[SC_A_VITA])
ignore_mres += sc->data[SC_A_VITA]->val2;
ignore_mres = min(ignore_mres, 100);
if (ignore_mres > 0)
mres -= mres * ignore_mres / 100;
// Max damage reduction from MRes is officially 50%.
// That means 625 MRes is needed to hit that cap.
if (mres > battle_config.max_res_mres_reduction)
mres = battle_config.max_res_mres_reduction;
// Apply damage reduction.
ad.damage = ad.damage * (5000 + mres) / (5000 + 10 * mres);
}
#endif
if(!flag.imdef){
defType mdef = tstatus->mdef;
int mdef2= tstatus->mdef2;
if (sc && sc->data[SC_EXPIATIO]) {
i = 5 * sc->data[SC_EXPIATIO]->val1; // 5% per level
i = min(i, 100); //cap it to 100 for 5 mdef min
mdef -= mdef * i / 100;
//mdef2 -= mdef2 * i / 100;
}
if(sd) {
i = sd->indexed_bonus.ignore_mdef_by_race[tstatus->race] + sd->indexed_bonus.ignore_mdef_by_race[RC_ALL];
i += sd->indexed_bonus.ignore_mdef_by_class[tstatus->class_] + sd->indexed_bonus.ignore_mdef_by_class[CLASS_ALL];
std::vector<e_race2> race2 = status_get_race2(target);
for (const auto &raceit : race2)
i += sd->indexed_bonus.ignore_mdef_by_race2[raceit];
if (i)
{
if (i > 100) i = 100;
mdef -= mdef * i/100;
//mdef2-= mdef2* i/100;
}
}
#ifdef RENEWAL
/**
* RE MDEF Reduction
* Damage = Magic Attack * (1000+eMDEF)/(1000+eMDEF) - sMDEF
*/
if (mdef < 0)
mdef = 0; // Negative eMDEF is treated as 0 on official
ad.damage = ad.damage * (1000 + mdef) / (1000 + mdef * 10) - mdef2;
#else
if(battle_config.magic_defense_type)
ad.damage = ad.damage - mdef*battle_config.magic_defense_type - mdef2;
else
ad.damage = ad.damage * (100-mdef)/100 - mdef2;
#endif
}
if(ad.damage<1)
ad.damage=1;
else if(sc) { //only applies when hit
switch(skill_id) {
case MG_LIGHTNINGBOLT:
case MG_THUNDERSTORM:
if(sc->data[SC_GUST_OPTION])
ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30;
break;
case MG_FIREBOLT:
case MG_FIREWALL:
if(sc->data[SC_PYROTECHNIC_OPTION])
ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30;
break;
case MG_COLDBOLT:
case MG_FROSTDIVER:
if(sc->data[SC_AQUAPLAY_OPTION])
ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30;
break;
case WZ_EARTHSPIKE:
case WZ_HEAVENDRIVE:
if(sc->data[SC_PETROLOGY_OPTION])
ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30;
break;
}
}
if (!nk[NK_IGNOREELEMENT])
ad.damage = battle_attr_fix(src, target, ad.damage, s_ele, tstatus->def_ele, tstatus->ele_lv);
//Apply the physical part of the skill's damage. [Skotlex]
switch(skill_id) {
case CR_GRANDCROSS:
case NPC_GRANDDARKNESS: {
struct Damage wd = battle_calc_weapon_attack(src,target,skill_id,skill_lv,mflag);
ad.damage = battle_attr_fix(src, target, wd.damage + ad.damage, s_ele, tstatus->def_ele, tstatus->ele_lv) * (100 + 40 * skill_lv) / 100;
if(src == target) {
if(src->type == BL_PC)
ad.damage = ad.damage / 2;
else
ad.damage = 0;
}
}
break;
}
#ifndef RENEWAL
ad.damage += battle_calc_cardfix(BF_MAGIC, src, target, nk, s_ele, 0, ad.damage, 0, ad.flag);
#endif
} //Hint: Against plants damage will still be 1 at this point
//Apply DAMAGE_DIV_FIX and check for min damage
battle_apply_div_fix(&ad, skill_id);
struct map_data *mapdata = map_getmapdata(target->m);
ad.damage = battle_calc_damage(src,target,&ad,ad.damage,skill_id,skill_lv);
if (mapdata_flag_gvg2(mapdata))
ad.damage = battle_calc_gvg_damage(src,target,ad.damage,skill_id,ad.flag);
else if (mapdata->flag[MF_BATTLEGROUND])
ad.damage = battle_calc_bg_damage(src,target,ad.damage,skill_id,ad.flag);
// Skill damage adjustment
if ((skill_damage = battle_skill_damage(src,target,skill_id)) != 0)
MATK_ADDRATE(skill_damage);
battle_absorb_damage(target, &ad);
//battle_do_reflect(BF_MAGIC,&ad, src, target, skill_id, skill_lv); //WIP [lighta] Magic skill has own handler at skill_attack
return ad;
}
/*==========================================
* Calculate "misc"-type attacks and skills
*------------------------------------------
* Credits:
* Original coder Skotlex
* Refined and optimized by helvetica
*/
struct Damage battle_calc_misc_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag)
{
int skill_damage = 0;
short i, s_ele;
struct map_session_data *sd, *tsd;
struct Damage md; //DO NOT CONFUSE with md of mob_data!
struct status_data *sstatus = status_get_status_data(src);
struct status_data *tstatus = status_get_status_data(target);
struct status_change *ssc = status_get_sc(src);
memset(&md,0,sizeof(md));
if (src == NULL || target == NULL) {
nullpo_info(NLP_MARK);
return md;
}
//Some initial values
md.amotion = (skill_get_inf(skill_id)&INF_GROUND_SKILL ? 0 : sstatus->amotion);
md.dmotion = tstatus->dmotion;
md.div_ = skill_get_num(skill_id,skill_lv);
md.blewcount = skill_get_blewcount(skill_id,skill_lv);
md.dmg_lv = ATK_DEF;
md.flag = BF_MISC|BF_SKILL;
std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id);
std::bitset<NK_MAX> nk;
if (skill)
nk = skill->nk;
sd = BL_CAST(BL_PC, src);
tsd = BL_CAST(BL_PC, target);
if(sd) {
sd->state.arrow_atk = 0;
md.blewcount += battle_blewcount_bonus(sd, skill_id);
}
s_ele = skill_get_ele(skill_id, skill_lv);
if (s_ele == ELE_WEAPON || s_ele == ELE_ENDOWED) //Attack that takes weapon's element for misc attacks? Make it neutral [Skotlex]
s_ele = ELE_NEUTRAL;
else if (s_ele == ELE_RANDOM) //Use random element
s_ele = rnd()%ELE_ALL;
//Skill Range Criteria
md.flag |= battle_range_type(src, target, skill_id, skill_lv);
switch (skill_id) {
case TF_THROWSTONE:
md.damage = 50;
md.flag |= BF_WEAPON;
break;
#ifdef RENEWAL
case HT_LANDMINE:
case MA_LANDMINE:
case HT_BLASTMINE:
case HT_CLAYMORETRAP:
md.damage = (int64)(skill_lv * sstatus->dex * (3.0 + (float)status_get_lv(src) / 100.0) * (1.0 + (float)sstatus->int_ / 35.0));
md.damage += md.damage * (rnd()%20 - 10) / 100;
md.damage += (sd ? pc_checkskill(sd,RA_RESEARCHTRAP) * 40 : 0);
break;
#else
case HT_LANDMINE:
case MA_LANDMINE:
md.damage = skill_lv * (sstatus->dex + 75) * (100 + sstatus->int_) / 100;
break;
case HT_BLASTMINE:
md.damage = skill_lv * (sstatus->dex / 2 + 50) * (100 + sstatus->int_) / 100;
break;
case HT_CLAYMORETRAP:
md.damage = skill_lv * (sstatus->dex / 2 + 75) * (100 + sstatus->int_) / 100;
break;
#endif
case HT_BLITZBEAT:
case SN_FALCONASSAULT:
{
uint16 skill;
//Blitz-beat Damage
if(!sd || !(skill = pc_checkskill(sd,HT_STEELCROW)))
skill = 0;
#ifdef RENEWAL
md.damage = (sstatus->dex / 10 + sstatus->agi / 2 + skill * 3 + 40) * 2;
RE_LVL_MDMOD(100);
#else
md.damage = (sstatus->dex / 10 + sstatus->int_ / 2 + skill * 3 + 40) * 2;
if(mflag > 1) //Autocasted Blitz
nk.set(NK_SPLASHSPLIT);
#endif
if (skill_id == SN_FALCONASSAULT) {
//Div fix of Blitzbeat
DAMAGE_DIV_FIX2(md.damage, skill_get_num(HT_BLITZBEAT, 5));
//Falcon Assault Modifier
md.damage = md.damage * (150 + 70 * skill_lv) / 100;
}
}
break;
#ifndef RENEWAL
case BA_DISSONANCE:
md.damage = 30 + skill_lv * 10;
if (sd)
md.damage += 3 * pc_checkskill(sd,BA_MUSICALLESSON);
break;
#endif
case NPC_SELFDESTRUCTION:
md.damage = sstatus->hp;
break;
case NPC_SMOKING:
md.damage = 3;
break;
case NPC_EVILLAND:
md.damage = skill_calc_heal(src,target,skill_id,skill_lv,false);
break;
#ifndef RENEWAL
case ASC_BREAKER:
md.damage = 500 + rnd()%500 + 5 * skill_lv * sstatus->int_;
nk.set(NK_IGNOREFLEE);
nk.set(NK_IGNOREELEMENT); //These two are not properties of the weapon based part.
break;
case HW_GRAVITATION:
md.damage = 200 + 200 * skill_lv;
md.dmotion = 0; //No flinch animation
break;
case PA_PRESSURE:
md.damage = 500 + 300 * skill_lv;
break;
#endif
case PA_GOSPEL:
if (mflag > 0)
md.damage = (rnd() % 4000) + 1500;
else {
md.damage = (rnd() % 5000) + 3000;
#ifdef RENEWAL
md.damage -= (int64)status_get_def(target);
#else
md.damage -= (md.damage * (int64)status_get_def(target)) / 100;
#endif
md.damage -= tstatus->def2;
if (md.damage < 0)
md.damage = 0;
}
break;
case GN_FIRE_EXPANSION_ACID:
#ifdef RENEWAL
// Official Renewal formula [helvetica]
// damage = 7 * ((atk + matk)/skill level) * (target vit/100)
// skill is a "forced neutral" type skill, it benefits from weapon element but final damage
// is considered "neutral" for purposes of resistances
{
struct Damage atk = battle_calc_weapon_attack(src, target, skill_id, skill_lv, 0);
struct Damage matk = battle_calc_magic_attack(src, target, skill_id, skill_lv, 0);
md.damage = 7 * ((atk.damage/skill_lv + matk.damage/skill_lv) * tstatus->vit / 100 );
// AD benefits from endow/element but damage is forced back to neutral
md.damage = battle_attr_fix(src, target, md.damage, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv);
}
// Fall through
#else
case CR_ACIDDEMONSTRATION:
if(tstatus->vit+sstatus->int_) //crash fix
md.damage = (int)((int64)7*tstatus->vit*sstatus->int_*sstatus->int_ / (10*(tstatus->vit+sstatus->int_)));
else
md.damage = 0;
if (tsd) md.damage>>=1;
#endif
break;
case NJ_ZENYNAGE:
case KO_MUCHANAGE:
md.damage = skill_get_zeny(skill_id, skill_lv);
if (!md.damage)
md.damage = (skill_id == NJ_ZENYNAGE ? 2 : 10);
md.damage = (skill_id == NJ_ZENYNAGE ? rnd()%md.damage + md.damage : md.damage * rnd_value(50,100)) / (skill_id == NJ_ZENYNAGE ? 1 : 100);
if (sd && skill_id == KO_MUCHANAGE && !pc_checkskill(sd, NJ_TOBIDOUGU))
md.damage = md.damage / 2;
if (status_get_class_(target) == CLASS_BOSS) // Specific to Boss Class
md.damage = md.damage / (skill_id == NJ_ZENYNAGE ? 3 : 2);
else if (tsd && skill_id == NJ_ZENYNAGE)
md.damage = md.damage / 2;
break;
#ifdef RENEWAL
case NJ_ISSEN:
// Official Renewal formula [helvetica]
// base damage = currenthp + ((atk * currenthp * skill level) / maxhp)
// final damage = base damage + ((mirror image count + 1) / 5 * base damage) - (edef + sdef)
// modified def formula
{
short totaldef;
struct Damage atk = battle_calc_weapon_attack(src, target, skill_id, skill_lv, 0);
struct status_change *sc = status_get_sc(src);
md.damage = (int64)sstatus->hp + (atk.damage * (int64)sstatus->hp * skill_lv) / (int64)sstatus->max_hp;
if (sc && sc->data[SC_BUNSINJYUTSU] && (i = sc->data[SC_BUNSINJYUTSU]->val2) > 0) { // mirror image bonus only occurs if active
md.div_ = -(i + 2); // mirror image count + 2
md.damage += (md.damage * (((i + 1) * 10) / 5)) / 10;
}
// modified def reduction, final damage = base damage - (edef + sdef)
totaldef = tstatus->def2 + (short)status_get_def(target);
md.damage -= totaldef;
md.flag |= BF_WEAPON;
}
break;
#endif
case GS_FLING:
md.damage = (sd ? sd->status.job_level : status_get_lv(src));
break;
case HVAN_EXPLOSION: //[orn]
md.damage = (int64)sstatus->max_hp * (50 + 50 * skill_lv) / 100;
break;
case RA_CLUSTERBOMB:
case RA_FIRINGTRAP:
case RA_ICEBOUNDTRAP:
md.damage = skill_lv * status_get_dex(src) + status_get_int(src) * 5 ;
RE_LVL_TMDMOD();
if(sd) {
int researchskill_lv = pc_checkskill(sd,RA_RESEARCHTRAP);
if(researchskill_lv)
md.damage = md.damage * 20 * researchskill_lv / (skill_id == RA_CLUSTERBOMB ? 50 : 100);
else
md.damage = 0;
} else
md.damage = md.damage * 200 / (skill_id == RA_CLUSTERBOMB ? 50 : 100);
nk.set(NK_IGNOREELEMENT);
nk.set(NK_IGNOREFLEE);
nk.set(NK_IGNOREDEFCARD);
break;
case NC_MAGMA_ERUPTION_DOTDAMAGE: // 'Eruption' damage
md.damage = 800 + 200 * skill_lv;
break;
case NPC_MAGMA_ERUPTION_DOTDAMAGE:
md.damage = 1000 * skill_lv;
break;
case GN_THORNS_TRAP:
md.damage = 100 + 200 * skill_lv + status_get_int(src);
break;
case RL_B_TRAP:
// kRO 2014-02-12: Damage: Caster's DEX, Target's current HP, Skill Level
md.damage = status_get_dex(src) * 10 + (skill_lv * 3 * status_get_hp(target)) / 100;
if (status_bl_has_mode(target, MD_STATUSIMMUNE))
md.damage /= 10;
break;
case NPC_MAXPAIN_ATK:
if (ssc && ssc->data[SC_MAXPAIN])
md.damage = ssc->data[SC_MAXPAIN]->val2;
else
md.damage = 0;
break;
case NPC_WIDESUCK:
md.damage = tstatus->max_hp * 15 / 100;
break;
case SU_SV_ROOTTWIST_ATK:
md.damage = 100;
break;
case SP_SOULEXPLOSION:
md.damage = tstatus->hp * (20 + 10 * skill_lv) / 100;
break;
case SJ_NOVAEXPLOSING:
// (Base ATK + Weapon ATK) * Ratio
md.damage = (sstatus->batk + sstatus->rhw.atk) * (200 + 100 * skill_lv) / 100;
// Additional Damage
md.damage += sstatus->max_hp / (6 - min(5, skill_lv)) + status_get_max_sp(src) * (2 * skill_lv);
break;
}
if (nk[NK_SPLASHSPLIT]) { // Divide ATK among targets
if(mflag > 0)
md.damage /= mflag;
else
ShowError("0 enemies targeted by %d:%s, divide per 0 avoided!\n", skill_id, skill_get_name(skill_id));
}
if (!nk[NK_IGNOREFLEE]) {
struct status_change *sc = status_get_sc(target);
i = 0; //Temp for "hit or no hit"
if(sc && sc->opt1 && sc->opt1 != OPT1_STONEWAIT && sc->opt1 != OPT1_BURNING)
i = 1;
else {
short
flee = tstatus->flee,
#ifdef RENEWAL
hitrate = 0; //Default hitrate
#else
hitrate = 80; //Default hitrate
#endif
if(battle_config.agi_penalty_type && battle_config.agi_penalty_target&target->type) {
unsigned char attacker_count = unit_counttargeted(target); //256 max targets should be a sane max
if(attacker_count >= battle_config.agi_penalty_count) {
if (battle_config.agi_penalty_type == 1)
flee = (flee * (100 - (attacker_count - (battle_config.agi_penalty_count - 1))*battle_config.agi_penalty_num))/100;
else //assume type 2: absolute reduction
flee -= (attacker_count - (battle_config.agi_penalty_count - 1))*battle_config.agi_penalty_num;
if(flee < 1)
flee = 1;
}
}
hitrate += sstatus->hit - flee;
#ifdef RENEWAL
if( sd ) //in Renewal hit bonus from Vultures Eye is not shown anymore in status window
hitrate += pc_checkskill(sd,AC_VULTURE);
#endif
hitrate = cap_value(hitrate, battle_config.min_hitrate, battle_config.max_hitrate);
if(rnd()%100 < hitrate)
i = 1;
}
if (!i) {
md.damage = 0;
md.dmg_lv = ATK_FLEE;
}
}
md.damage += battle_calc_cardfix(BF_MISC, src, target, nk, s_ele, 0, md.damage, 0, md.flag);
if (sd && (i = pc_skillatk_bonus(sd, skill_id)))
md.damage += (int64)md.damage*i/100;
if (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id)))
md.damage -= (int64)md.damage*i/100;
if(!nk[NK_IGNOREELEMENT])
md.damage=battle_attr_fix(src, target, md.damage, s_ele, tstatus->def_ele, tstatus->ele_lv);
//Plant damage
if(md.damage < 0)
md.damage = 0;
else if(md.damage && is_infinite_defense(target, md.flag)) {
md.damage = 1;
}
//Apply DAMAGE_DIV_FIX and check for min damage
battle_apply_div_fix(&md, skill_id);
switch(skill_id) {
case RA_FIRINGTRAP:
case RA_ICEBOUNDTRAP:
if (md.damage == 1)
break;
case RA_CLUSTERBOMB:
{
struct Damage wd = battle_calc_weapon_attack(src,target,skill_id,skill_lv,mflag);
md.damage += wd.damage;
}
break;
case NJ_ZENYNAGE:
if (sd) {
if (md.damage > sd->status.zeny)
md.damage = sd->status.zeny;
pc_payzeny(sd,(int)cap_value(md.damage, INT_MIN, INT_MAX),LOG_TYPE_STEAL,NULL);
}
break;
}
struct map_data *mapdata = map_getmapdata(target->m);
md.damage = battle_calc_damage(src,target,&md,md.damage,skill_id,skill_lv);
if(mapdata_flag_gvg2(mapdata))
md.damage = battle_calc_gvg_damage(src,target,md.damage,skill_id,md.flag);
else if(mapdata->flag[MF_BATTLEGROUND])
md.damage = battle_calc_bg_damage(src,target,md.damage,skill_id,md.flag);
// Skill damage adjustment
if ((skill_damage = battle_skill_damage(src,target,skill_id)) != 0)
md.damage += (int64)md.damage * skill_damage / 100;
battle_absorb_damage(target, &md);
battle_do_reflect(BF_MISC,&md, src, target, skill_id, skill_lv); //WIP [lighta]
return md;
}
/**
* Calculate vanish damage on a target
* @param sd: Player with vanish item
* @param target: Target to vanish HP/SP
* @param flag: Damage struct battle flag
*/
void battle_vanish_damage(struct map_session_data *sd, struct block_list *target, int flag)
{
nullpo_retv(sd);
nullpo_retv(target);
// bHPVanishRate
int16 vanish_hp = 0;
if (!sd->hp_vanish.empty()) {
for (auto &it : sd->hp_vanish) {
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
if (it.rate && (it.rate >= 1000 || rnd() % 1000 < it.rate))
vanish_hp += it.per;
}
}
// bSPVanishRate
int16 vanish_sp = 0;
if (!sd->sp_vanish.empty()) {
for (auto &it : sd->sp_vanish) {
if (!(((it.flag)&flag)&BF_WEAPONMASK &&
((it.flag)&flag)&BF_RANGEMASK &&
((it.flag)&flag)&BF_SKILLMASK))
continue;
if (it.rate && (it.rate >= 1000 || rnd() % 1000 < it.rate))
vanish_sp += it.per;
}
}
if (vanish_hp > 0 || vanish_sp > 0)
status_percent_damage(&sd->bl, target, -vanish_hp, -vanish_sp, false); // Damage HP/SP applied once
}
/*==========================================
* Battle main entry, from skill_attack
*------------------------------------------
* Credits:
* Original coder unknown
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
struct Damage battle_calc_attack(int attack_type,struct block_list *bl,struct block_list *target,uint16 skill_id,uint16 skill_lv,int flag)
{
struct Damage d;
switch(attack_type) {
case BF_WEAPON: d = battle_calc_weapon_attack(bl,target,skill_id,skill_lv,flag); break;
case BF_MAGIC: d = battle_calc_magic_attack(bl,target,skill_id,skill_lv,flag); break;
case BF_MISC: d = battle_calc_misc_attack(bl,target,skill_id,skill_lv,flag); break;
default:
ShowError("battle_calc_attack: unknown attack type! %d (skill_id=%d, skill_lv=%d)\n", attack_type, skill_id, skill_lv);
memset(&d,0,sizeof(d));
break;
}
if( d.damage + d.damage2 < 1 )
{ //Miss/Absorbed
//Weapon attacks should go through to cause additional effects.
if (d.dmg_lv == ATK_DEF /*&& attack_type&(BF_MAGIC|BF_MISC)*/) // Isn't it that additional effects don't apply if miss?
d.dmg_lv = ATK_MISS;
d.dmotion = 0;
}
else // Some skills like Weaponry Research will cause damage even if attack is dodged
d.dmg_lv = ATK_DEF;
struct map_session_data *sd = BL_CAST(BL_PC, bl);
if (sd && d.damage + d.damage2 > 1)
battle_vanish_damage(sd, target, d.flag);
return d;
}
/*==========================================
* Final damage return function
*------------------------------------------
* Credits:
* Original coder unknown
* Initial refactoring by Baalberith
* Refined and optimized by helvetica
*/
int64 battle_calc_return_damage(struct block_list* bl, struct block_list *src, int64 *dmg, int flag, uint16 skill_id, bool status_reflect){
struct map_session_data* sd;
int64 rdamage = 0, damage = *dmg;
int max_damage = status_get_max_hp(bl);
struct status_change *sc, *ssc;
sd = BL_CAST(BL_PC, bl);
sc = status_get_sc(bl);
ssc = status_get_sc(src);
if (sc) { // These statuses do not reflect any damage (off the target)
if (sc->data[SC_WHITEIMPRISON] || sc->data[SC_DARKCROW] || sc->data[SC_KYOMU])
return 0;
}
if (ssc) {
if (ssc->data[SC_HELLS_PLANT])
return 0;
}
if (flag & BF_SHORT) {//Bounces back part of the damage.
if ( (skill_get_inf2(skill_id, INF2_ISTRAP) || !status_reflect) && sd && sd->bonus.short_weapon_damage_return ) {
rdamage += damage * sd->bonus.short_weapon_damage_return / 100;
rdamage = i64max(rdamage, 1);
} else if( status_reflect && sc && sc->count ) {
if( sc->data[SC_REFLECTSHIELD] ) {
struct status_change_entry *sce_d;
struct block_list *d_bl = NULL;
if( (sce_d = sc->data[SC_DEVOTION]) && (d_bl = map_id2bl(sce_d->val1)) &&
((d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == bl->id) ||
(d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce_d->val2] == bl->id)) )
{ //Don't reflect non-skill attack if has SC_REFLECTSHIELD from Devotion bonus inheritance
if( (!skill_id && battle_config.devotion_rdamage_skill_only && sc->data[SC_REFLECTSHIELD]->val4) ||
!check_distance_bl(bl,d_bl,sce_d->val3) )
return 0;
}
}
if ( sc->data[SC_REFLECTSHIELD] && skill_id != WS_CARTTERMINATION ) {
// Don't reflect non-skill attack if has SC_REFLECTSHIELD from Devotion bonus inheritance
if (!skill_id && battle_config.devotion_rdamage_skill_only && sc->data[SC_REFLECTSHIELD]->val4)
rdamage = 0;
else {
rdamage += damage * sc->data[SC_REFLECTSHIELD]->val2 / 100;
rdamage = i64max(rdamage, 1);
}
}
if (sc->data[SC_DEATHBOUND] && skill_id != WS_CARTTERMINATION && skill_id != GN_HELLS_PLANT_ATK && !status_bl_has_mode(src,MD_STATUSIMMUNE)) {
if (distance_bl(src,bl) <= 0 || !map_check_dir(map_calc_dir(bl,src->x,src->y), unit_getdir(bl))) {
int64 rd1 = min(damage, status_get_max_hp(bl)) * sc->data[SC_DEATHBOUND]->val2 / 100; // Amplify damage.
*dmg = rd1 * 30 / 100; // Received damage = 30% of amplified damage.
clif_skill_damage(src, bl, gettick(), status_get_amotion(src), 0, -30000, 1, RK_DEATHBOUND, sc->data[SC_DEATHBOUND]->val1, DMG_SINGLE);
skill_blown(bl, src, skill_get_blewcount(RK_DEATHBOUND, 1), unit_getdir(src), BLOWN_NONE);
status_change_end(bl, SC_DEATHBOUND, INVALID_TIMER);
rdamage += rd1 * 70 / 100; // Target receives 70% of the amplified damage. [Rytech]
}
}
}
} else {
if (!status_reflect && sd && sd->bonus.long_weapon_damage_return) {
rdamage += damage * sd->bonus.long_weapon_damage_return / 100;
rdamage = i64max(rdamage, 1);
}
}
if (rdamage > 0) {
map_session_data* ssd = BL_CAST(BL_PC, src);
if (ssd && ssd->bonus.reduce_damage_return != 0) {
rdamage -= rdamage * ssd->bonus.reduce_damage_return / 100;
rdamage = i64max(rdamage, 1);
}
}
if (ssc) {
if (ssc->data[SC_REFLECTDAMAGE]) {
rdamage -= damage * ssc->data[SC_REFLECTDAMAGE]->val2 / 100;
if (--(ssc->data[SC_REFLECTDAMAGE]->val3) < 1) // TODO: Confirm if reflect count still exists
status_change_end(bl, SC_REFLECTDAMAGE, INVALID_TIMER);
}
if (ssc->data[SC_VENOMBLEED] && ssc->data[SC_VENOMBLEED]->val3 == 0)
rdamage -= damage * ssc->data[SC_VENOMBLEED]->val2 / 100;
if (rdamage > 0 && ssc->data[SC_REF_T_POTION])
return 1; // Returns 1 damage
}
if (sc) {
if (sc->data[SC_MAXPAIN])
rdamage = damage * sc->data[SC_MAXPAIN]->val1 * 10 / 100;
}
return cap_value(min(rdamage,max_damage),INT_MIN,INT_MAX);
}
/**
* Calculate Vellum damage on a target
* @param sd: Player with vanish item
* @param target: Target to vanish HP/SP
* @param wd: Damage struct reference
* @return True on damage done or false if not
*/
bool battle_vellum_damage(struct map_session_data *sd, struct block_list *target, struct Damage *wd)
{
nullpo_retr(false, sd);
nullpo_retr(false, target);
nullpo_retr(false, wd);
struct status_data *tstatus = status_get_status_data(target);
// bHPVanishRaceRate
int16 vellum_rate_hp = cap_value(sd->hp_vanish_race[tstatus->race].rate + sd->hp_vanish_race[RC_ALL].rate, 0, INT16_MAX);
int8 vellum_hp = cap_value(sd->hp_vanish_race[tstatus->race].per + sd->hp_vanish_race[RC_ALL].per, INT8_MIN, INT8_MAX);
// bSPVanishRaceRate
int16 vellum_rate_sp = cap_value(sd->sp_vanish_race[tstatus->race].rate + sd->sp_vanish_race[RC_ALL].rate, 0, INT16_MAX);
int8 vellum_sp = cap_value(sd->sp_vanish_race[tstatus->race].per + sd->sp_vanish_race[RC_ALL].per, INT8_MIN, INT8_MAX);
// The HP and SP damage bonus from these items don't stack because of the special damage display for SP.
// Vellum damage overrides any other damage done as well.
if (vellum_hp && vellum_rate_hp && (vellum_rate_hp >= 1000 || rnd() % 1000 < vellum_rate_hp)) {
wd->damage = apply_rate(tstatus->max_hp, vellum_hp);
wd->damage2 = 0;
} else if (vellum_sp && vellum_rate_sp && (vellum_rate_sp >= 1000 || rnd() % 1000 < vellum_rate_sp)) {
wd->damage = apply_rate(tstatus->max_sp, vellum_sp);
wd->damage2 = 0;
wd->isspdamage = true;
} else
return false;
return true;
}
/*===========================================
* Perform battle drain effects (HP/SP loss)
*-------------------------------------------*/
void battle_drain(struct map_session_data *sd, struct block_list *tbl, int64 rdamage, int64 ldamage, int race, int class_)
{
struct weapon_data *wd;
int64 *damage;
int thp = 0, // HP gained
tsp = 0, // SP gained
//rhp = 0, // HP reduced from target
//rsp = 0, // SP reduced from target
hp = 0, sp = 0;
if (!CHK_RACE(race) && !CHK_CLASS(class_))
return;
for (int i = 0; i < 4; i++) {
//First two iterations: Right hand
if (i < 2) {
wd = &sd->right_weapon;
damage = &rdamage;
} else {
wd = &sd->left_weapon;
damage = &ldamage;
}
if (*damage <= 0)
continue;
if (i == 1 || i == 3) {
hp = wd->hp_drain_class[class_] + wd->hp_drain_class[CLASS_ALL];
hp += battle_calc_drain(*damage, wd->hp_drain_rate.rate, wd->hp_drain_rate.per);
sp = wd->sp_drain_class[class_] + wd->sp_drain_class[CLASS_ALL];
sp += battle_calc_drain(*damage, wd->sp_drain_rate.rate, wd->sp_drain_rate.per);
if( hp ) {
//rhp += hp;
thp += hp;
}
if( sp ) {
//rsp += sp;
tsp += sp;
}
} else {
hp = wd->hp_drain_race[race] + wd->hp_drain_race[RC_ALL];
sp = wd->sp_drain_race[race] + wd->sp_drain_race[RC_ALL];
if( hp ) {
//rhp += hp;
thp += hp;
}
if( sp ) {
//rsp += sp;
tsp += sp;
}
}
}
if (!thp && !tsp)
return;
status_heal(&sd->bl, thp, tsp, battle_config.show_hp_sp_drain?3:1);
//if (rhp || rsp)
// status_zap(tbl, rhp, rsp);
}
/*===========================================
* Deals the same damage to targets in area.
*-------------------------------------------
* Credits:
* Original coder pakpil
*/
int battle_damage_area(struct block_list *bl, va_list ap) {
t_tick tick;
int64 damage;
int amotion, dmotion;
struct block_list *src;
nullpo_ret(bl);
tick = va_arg(ap, t_tick);
src = va_arg(ap,struct block_list *);
amotion = va_arg(ap,int);
dmotion = va_arg(ap,int);
damage = va_arg(ap,int);
if (status_bl_has_mode(bl, MD_SKILLIMMUNE) || status_get_class(bl) == MOBID_EMPERIUM)
return 0;
if( bl != src && battle_check_target(src,bl,BCT_ENEMY) > 0 ) {
map_freeblock_lock();
if( src->type == BL_PC )
battle_drain((TBL_PC*)src, bl, damage, damage, status_get_race(bl), status_get_class_(bl));
if( amotion )
battle_delay_damage(tick, amotion,src,bl,0,CR_REFLECTSHIELD,0,damage,ATK_DEF,0,true,false);
else
status_fix_damage(src,bl,damage,0,LG_REFLECTDAMAGE);
clif_damage(bl,bl,tick,amotion,dmotion,damage,1,DMG_ENDURE,0,false);
skill_additional_effect(src, bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick);
map_freeblock_unlock();
}
return 0;
}
/**
* Triggers aftercast delay for autocasted skills.
* @param src: Source data
* @param skill_id: Skill used
* @param skill_lv: Skill level used
* @param tick: Server tick
*/
void battle_autocast_aftercast(struct block_list* src, uint16 skill_id, uint16 skill_lv, t_tick tick)
{
unit_data *ud = unit_bl2ud(src);
if (ud) {
int autocast_tick = skill_delayfix(src, skill_id, skill_lv);
if (DIFF_TICK(ud->canact_tick, tick + autocast_tick) < 0) {
ud->canact_tick = i64max(tick + autocast_tick, ud->canact_tick);
if (battle_config.display_status_timers && src->type == BL_PC)
clif_status_change(src, EFST_POSTDELAY, 1, autocast_tick, 0, 0, 0);
}
}
}
/**
* Triggers autocasted skills from super elemental supportive buffs.
* @param sd: Player data
* @param target: Target data
* @param skill_id: Skill used
* @param tick: Server tick
* @param flag: Special skill flags
*/
void battle_autocast_elembuff_skill(struct map_session_data* sd, struct block_list* target, uint16 skill_id, t_tick tick, int flag)
{
uint16 skill_lv = pc_checkskill(sd, skill_id);
skill_lv = max(1, skill_lv);
sd->state.autocast = 1;
if (status_charge(&sd->bl, 0, skill_get_sp(skill_id, skill_lv))) {
skill_castend_damage_id(&sd->bl, target, skill_id, skill_lv, tick, flag);
battle_autocast_aftercast(&sd->bl, skill_id, skill_lv, tick);
}
sd->state.autocast = 0;
}
/*==========================================
* Do a basic physical attack (call through unit_attack_timer)
*------------------------------------------*/
enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* target, t_tick tick, int flag) {
struct map_session_data *sd = NULL, *tsd = NULL;
struct status_data *sstatus, *tstatus;
struct status_change *sc, *tsc;
int64 damage;
int skillv;
struct Damage wd;
bool vellum_damage = false;
nullpo_retr(ATK_NONE, src);
nullpo_retr(ATK_NONE, target);
if (src->prev == NULL || target->prev == NULL)
return ATK_NONE;
sd = BL_CAST(BL_PC, src);
tsd = BL_CAST(BL_PC, target);
sstatus = status_get_status_data(src);
tstatus = status_get_status_data(target);
sc = status_get_sc(src);
tsc = status_get_sc(target);
if (sc && !sc->count) //Avoid sc checks when there's none to check for. [Skotlex]
sc = NULL;
if (tsc && !tsc->count)
tsc = NULL;
if (sd)
{
sd->state.arrow_atk = (sd->status.weapon == W_BOW || (sd->status.weapon >= W_REVOLVER && sd->status.weapon <= W_GRENADE));
if (sd->state.arrow_atk)
{
short index = sd->equip_index[EQI_AMMO];
if (index < 0) {
if (sd->weapontype1 > W_KATAR && sd->weapontype1 < W_HUUMA)
clif_skill_fail(sd,0,USESKILL_FAIL_NEED_MORE_BULLET,0);
else
clif_arrow_fail(sd,0);
return ATK_NONE;
}
//Ammo check by Ishizu-chan
if (sd->inventory_data[index]) {
switch (sd->status.weapon) {
case W_BOW:
if (sd->inventory_data[index]->subtype != AMMO_ARROW) {
clif_arrow_fail(sd,0);
return ATK_NONE;
}
break;
case W_REVOLVER:
case W_RIFLE:
case W_GATLING:
case W_SHOTGUN:
if (sd->inventory_data[index]->subtype != AMMO_BULLET) {
clif_skill_fail(sd,0,USESKILL_FAIL_NEED_MORE_BULLET,0);
return ATK_NONE;
}
break;
case W_GRENADE:
if (sd->inventory_data[index]->subtype !=
#ifdef RENEWAL
AMMO_BULLET) {
#else
AMMO_GRENADE) {
#endif
clif_skill_fail(sd,0,USESKILL_FAIL_NEED_MORE_BULLET,0);
return ATK_NONE;
}
break;
}
}
}
}
if (sc && sc->count) {
if (sc->data[SC_CLOAKING] && !(sc->data[SC_CLOAKING]->val4 & 2))
status_change_end(src, SC_CLOAKING, INVALID_TIMER);
else if (sc->data[SC_CLOAKINGEXCEED] && !(sc->data[SC_CLOAKINGEXCEED]->val4 & 2))
status_change_end(src, SC_CLOAKINGEXCEED, INVALID_TIMER);
else if (sc->data[SC_NEWMOON] && --(sc->data[SC_NEWMOON]->val2) <= 0)
status_change_end(src, SC_NEWMOON, INVALID_TIMER);
}
if (tsc && tsc->data[SC_AUTOCOUNTER] && status_check_skilluse(target, src, KN_AUTOCOUNTER, 1)) {
uint8 dir = map_calc_dir(target,src->x,src->y);
int t_dir = unit_getdir(target);
int dist = distance_bl(src, target);
if (dist <= 0 || (!map_check_dir(dir,t_dir) && dist <= tstatus->rhw.range+1)) {
uint16 skill_lv = tsc->data[SC_AUTOCOUNTER]->val1;
clif_skillcastcancel(target); //Remove the casting bar. [Skotlex]
clif_damage(src, target, tick, sstatus->amotion, 1, 0, 1, DMG_NORMAL, 0, false); //Display MISS.
status_change_end(target, SC_AUTOCOUNTER, INVALID_TIMER);
skill_attack(BF_WEAPON,target,target,src,KN_AUTOCOUNTER,skill_lv,tick,0);
return ATK_BLOCK;
}
}
if( tsc && tsc->data[SC_BLADESTOP_WAIT] &&
#ifndef RENEWAL
status_get_class_(src) != CLASS_BOSS &&
#endif
(src->type == BL_PC || tsd == NULL || distance_bl(src, target) <= (tsd->status.weapon == W_FIST ? 1 : 2)) )
{
uint16 skill_lv = tsc->data[SC_BLADESTOP_WAIT]->val1;
int duration = skill_get_time2(MO_BLADESTOP,skill_lv);
#ifdef RENEWAL
if (status_get_class_(src) == CLASS_BOSS)
duration = 2000; // Only lasts 2 seconds for Boss monsters
#endif
status_change_end(target, SC_BLADESTOP_WAIT, INVALID_TIMER);
if(sc_start4(src,src, SC_BLADESTOP, 100, sd?pc_checkskill(sd, MO_BLADESTOP):5, 0, 0, target->id, duration))
{ //Target locked.
clif_damage(src, target, tick, sstatus->amotion, 1, 0, 1, DMG_NORMAL, 0, false); //Display MISS.
clif_bladestop(target, src->id, 1);
sc_start4(src,target, SC_BLADESTOP, 100, skill_lv, 0, 0, src->id, duration);
return ATK_BLOCK;
}
}
if(sd && (skillv = pc_checkskill(sd,MO_TRIPLEATTACK)) > 0) {
#ifdef RENEWAL
int triple_rate = 30; //Base Rate
#else
int triple_rate = 30 - skillv; //Base Rate
#endif
if (sc && sc->data[SC_SKILLRATE_UP] && sc->data[SC_SKILLRATE_UP]->val1 == MO_TRIPLEATTACK) {
triple_rate+= triple_rate*(sc->data[SC_SKILLRATE_UP]->val2)/100;
status_change_end(src, SC_SKILLRATE_UP, INVALID_TIMER);
}
if (rnd()%100 < triple_rate) {
//Need to apply canact_tick here because it doesn't go through skill_castend_id
sd->ud.canact_tick = i64max(tick + skill_delayfix(src, MO_TRIPLEATTACK, skillv), sd->ud.canact_tick);
if( skill_attack(BF_WEAPON,src,src,target,MO_TRIPLEATTACK,skillv,tick,0) )
return ATK_DEF;
return ATK_MISS;
}
}
if (sc) {
if (sc->data[SC_SACRIFICE]) {
uint16 skill_lv = sc->data[SC_SACRIFICE]->val1;
damage_lv ret_val;
if( --sc->data[SC_SACRIFICE]->val2 <= 0 )
status_change_end(src, SC_SACRIFICE, INVALID_TIMER);
/**
* We need to calculate the DMG before the hp reduction, because it can kill the source.
* For further information: bugreport:4950
*/
ret_val = (damage_lv)skill_attack(BF_WEAPON,src,src,target,PA_SACRIFICE,skill_lv,tick,0);
status_zap(src, sstatus->max_hp*9/100, 0);//Damage to self is always 9%
if( ret_val == ATK_NONE )
return ATK_MISS;
return ret_val;
}
if (sc->data[SC_MAGICALATTACK]) {
if( skill_attack(BF_MAGIC,src,src,target,NPC_MAGICALATTACK,sc->data[SC_MAGICALATTACK]->val1,tick,0) )
return ATK_DEF;
return ATK_MISS;
}
if( sc->data[SC_GT_ENERGYGAIN] ) {
int spheres = 5;
if( sc->data[SC_RAISINGDRAGON] )
spheres += sc->data[SC_RAISINGDRAGON]->val1;
if( sd && rnd()%100 < sc->data[SC_GT_ENERGYGAIN]->val2 )
pc_addspiritball(sd, skill_get_time2(SR_GENTLETOUCH_ENERGYGAIN, sc->data[SC_GT_ENERGYGAIN]->val1), spheres);
}
}
if( tsc && tsc->data[SC_GT_ENERGYGAIN] ) {
int spheres = 5;
if( tsc->data[SC_RAISINGDRAGON] )
spheres += tsc->data[SC_RAISINGDRAGON]->val1;
if( tsd && rnd()%100 < tsc->data[SC_GT_ENERGYGAIN]->val2 )
pc_addspiritball(tsd, skill_get_time2(SR_GENTLETOUCH_ENERGYGAIN, tsc->data[SC_GT_ENERGYGAIN]->val1), spheres);
}
if (tsc && tsc->data[SC_MTF_MLEATKED] && rnd()%100 < tsc->data[SC_MTF_MLEATKED]->val2)
clif_skill_nodamage(target, target, SM_ENDURE, tsc->data[SC_MTF_MLEATKED]->val1, sc_start(src, target, SC_ENDURE, 100, tsc->data[SC_MTF_MLEATKED]->val1, skill_get_time(SM_ENDURE, tsc->data[SC_MTF_MLEATKED]->val1)));
if(tsc && tsc->data[SC_KAAHI] && tstatus->hp < tstatus->max_hp && status_charge(target, 0, tsc->data[SC_KAAHI]->val3)) {
int hp_heal = tstatus->max_hp - tstatus->hp;
if (hp_heal > tsc->data[SC_KAAHI]->val2)
hp_heal = tsc->data[SC_KAAHI]->val2;
if (hp_heal)
status_heal(target, hp_heal, 0, 2);
}
wd = battle_calc_attack(BF_WEAPON, src, target, 0, 0, flag);
if (sd && wd.damage + wd.damage2 > 0 && battle_vellum_damage(sd, target, &wd))
vellum_damage = true;
if( sc && sc->count ) {
if (sc->data[SC_EXEEDBREAK])
status_change_end(src, SC_EXEEDBREAK, INVALID_TIMER);
if( sc->data[SC_SPELLFIST] && !vellum_damage ){
if (status_charge(src, 0, 20)) {
if (!is_infinite_defense(target, wd.flag)) {
struct Damage ad = battle_calc_attack(BF_MAGIC, src, target, sc->data[SC_SPELLFIST]->val2, sc->data[SC_SPELLFIST]->val3, flag | BF_SHORT);
wd.damage = ad.damage;
DAMAGE_DIV_FIX(wd.damage, wd.div_); // Double the damage for multiple hits.
} else {
wd.damage = 1;
DAMAGE_DIV_FIX(wd.damage, wd.div_);
}
} else
status_change_end(src,SC_SPELLFIST,INVALID_TIMER);
}
if (sc->data[SC_GIANTGROWTH] && (wd.flag&BF_SHORT) && rnd()%100 < sc->data[SC_GIANTGROWTH]->val2 && !is_infinite_defense(target, wd.flag) && !vellum_damage)
wd.damage += wd.damage * 150 / 100; // 2.5 times damage
if( sd && battle_config.arrow_decrement && sc->data[SC_FEARBREEZE] && sc->data[SC_FEARBREEZE]->val4 > 0) {
short idx = sd->equip_index[EQI_AMMO];
if (idx >= 0 && sd->inventory.u.items_inventory[idx].amount >= sc->data[SC_FEARBREEZE]->val4) {
pc_delitem(sd,idx,sc->data[SC_FEARBREEZE]->val4,0,1,LOG_TYPE_CONSUME);
sc->data[SC_FEARBREEZE]->val4 = 0;
}
}
}
if (sd && sd->state.arrow_atk) //Consume arrow.
battle_consume_ammo(sd, 0, 0);
damage = wd.damage + wd.damage2;
if( damage > 0 && src != target )
{
if (sc && sc->data[SC_DUPLELIGHT] && (wd.flag & BF_SHORT)) { // Activates only from regular melee damage. Success chance is seperate for both duple light attacks.
uint16 duple_rate = 10 + 2 * sc->data[SC_DUPLELIGHT]->val1;
if (rand() % 100 < duple_rate)
skill_castend_damage_id(src, target, AB_DUPLELIGHT_MELEE, sc->data[SC_DUPLELIGHT]->val1, tick, flag | SD_LEVEL);
if (rand() % 100 < duple_rate)
skill_castend_damage_id(src, target, AB_DUPLELIGHT_MAGIC, sc->data[SC_DUPLELIGHT]->val1, tick, flag | SD_LEVEL);
}
}
wd.dmotion = clif_damage(src, target, tick, wd.amotion, wd.dmotion, wd.damage, wd.div_ , wd.type, wd.damage2, wd.isspdamage);
if (sd && sd->bonus.splash_range > 0 && damage > 0)
skill_castend_damage_id(src, target, 0, 1, tick, 0);
if ( target->type == BL_SKILL && damage > 0 ) {
TBL_SKILL *su = (TBL_SKILL*)target;
if (su && su->group) {
if (su->group->skill_id == HT_BLASTMINE)
skill_blown(src, target, 3, -1, BLOWN_NONE);
if (su->group->skill_id == GN_WALLOFTHORN) {
if (--su->val2 <= 0)
skill_delunit(su);
}
}
}
map_freeblock_lock();
if( !(tsc && tsc->data[SC_DEVOTION]) && !vellum_damage && skill_check_shadowform(target, damage, wd.div_) ) {
if( !status_isdead(target) )
skill_additional_effect(src, target, 0, 0, wd.flag, wd.dmg_lv, tick);
if( wd.dmg_lv > ATK_BLOCK )
skill_counter_additional_effect(src, target, 0, 0, wd.flag, tick);
} else
battle_delay_damage(tick, wd.amotion, src, target, wd.flag, 0, 0, damage, wd.dmg_lv, wd.dmotion, true, wd.isspdamage);
if( tsc ) {
if( tsc->data[SC_DEVOTION] ) {
struct status_change_entry *sce = tsc->data[SC_DEVOTION];
struct block_list *d_bl = map_id2bl(sce->val1);
if( d_bl && (
(d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == target->id) ||
(d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce->val2] == target->id)
) && check_distance_bl(target, d_bl, sce->val3) )
{
// Only trigger if the devoted player was hit
if( damage > 0 ){
int64 devotion_damage = damage;
struct map_session_data* dsd = BL_CAST( BL_PC, d_bl );
// Needed to check the devotion master for Rebound Shield status.
struct status_change *d_sc = status_get_sc(d_bl);
// The devoting player needs to stand up
if( dsd && pc_issit( dsd ) ){
pc_setstand( dsd, true );
skill_sit( dsd, 0 );
}
if (d_sc && d_sc->data[SC_REBOUND_S])
devotion_damage -= devotion_damage * d_sc->data[SC_REBOUND_S]->val2 / 100;
clif_damage(d_bl, d_bl, gettick(), wd.amotion, wd.dmotion, devotion_damage, 1, DMG_NORMAL, 0, false);
status_fix_damage(NULL, d_bl, devotion_damage, 0, CR_DEVOTION);
}
}
else
status_change_end(target, SC_DEVOTION, INVALID_TIMER);
}
if (target->type == BL_PC && (wd.flag&BF_SHORT) && tsc->data[SC_CIRCLE_OF_FIRE_OPTION]) {
s_elemental_data *ed = ((TBL_PC*)target)->ed;
if (ed) {
clif_skill_damage(&ed->bl, target, tick, status_get_amotion(src), 0, -30000, 1, EL_CIRCLE_OF_FIRE, tsc->data[SC_CIRCLE_OF_FIRE_OPTION]->val1, DMG_SINGLE);
skill_attack(BF_WEAPON,&ed->bl,&ed->bl,src,EL_CIRCLE_OF_FIRE,tsc->data[SC_CIRCLE_OF_FIRE_OPTION]->val1,tick,wd.flag);
}
}
if (tsc->data[SC_WATER_SCREEN_OPTION]) {
struct block_list *e_bl = map_id2bl(tsc->data[SC_WATER_SCREEN_OPTION]->val1);
if (e_bl && !status_isdead(e_bl)) {
clif_damage(e_bl, e_bl, tick, 0, 0, damage, wd.div_, DMG_NORMAL, 0, false);
status_fix_damage(NULL, e_bl, damage, 0, EL_WATER_SCREEN);
}
}
}
if (sc && sc->data[SC_AUTOSPELL] && rnd()%100 < sc->data[SC_AUTOSPELL]->val4) {
int sp = 0;
uint16 skill_id = sc->data[SC_AUTOSPELL]->val2;
uint16 skill_lv = sc->data[SC_AUTOSPELL]->val3;
int i = rnd()%100;
if (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_SAGE)
i = 0; //Max chance, no skill_lv reduction. [Skotlex]
//reduction only for skill_lv > 1
if (skill_lv > 1) {
if (i >= 50) skill_lv /= 2;
else if (i >= 15) skill_lv--;
}
sp = skill_get_sp(skill_id,skill_lv) * 2 / 3;
if (status_charge(src, 0, sp)) {
struct unit_data *ud = unit_bl2ud(src);
switch (skill_get_casttype(skill_id)) {
case CAST_GROUND:
skill_castend_pos2(src, target->x, target->y, skill_id, skill_lv, tick, flag);
break;
case CAST_NODAMAGE:
skill_castend_nodamage_id(src, target, skill_id, skill_lv, tick, flag);
break;
case CAST_DAMAGE:
skill_castend_damage_id(src, target, skill_id, skill_lv, tick, flag);
break;
}
if (ud) {
int autospell_tick = skill_delayfix(src, skill_id, skill_lv);
if (DIFF_TICK(ud->canact_tick, tick + autospell_tick) < 0) {
ud->canact_tick = i64max(tick + autospell_tick, ud->canact_tick);
if (battle_config.display_status_timers && sd)
clif_status_change(src, EFST_POSTDELAY, 1, autospell_tick, 0, 0, 0);
}
}
}
}
if (sd) {
uint16 r_skill = 0, sk_idx = 0;
if( wd.flag&BF_WEAPON && sc && sc->data[SC__AUTOSHADOWSPELL] && rnd()%100 < sc->data[SC__AUTOSHADOWSPELL]->val3 &&
(r_skill = (uint16)sc->data[SC__AUTOSHADOWSPELL]->val1) && (sk_idx = skill_get_index(r_skill)) &&
sd->status.skill[sk_idx].id != 0 && sd->status.skill[sk_idx].flag == SKILL_FLAG_PLAGIARIZED )
{
if (r_skill != AL_HOLYLIGHT && r_skill != PR_MAGNUS) {
int r_lv = sc->data[SC__AUTOSHADOWSPELL]->val2, type;
if( (type = skill_get_casttype(r_skill)) == CAST_GROUND ) {
int maxcount = 0;
std::shared_ptr<s_skill_db> skill = skill_db.find(r_skill);
if( !(BL_PC&battle_config.skill_reiteration) && skill->unit_flag[UF_NOREITERATION] )
type = -1;
if( BL_PC&battle_config.skill_nofootset && skill->unit_flag[UF_NOFOOTSET] )
type = -1;
if( BL_PC&battle_config.land_skill_limit &&
(maxcount = skill_get_maxcount(r_skill, r_lv)) > 0
) {
unit_skillunit_maxcount(sd->ud, r_skill, maxcount);
if( maxcount == 0 )
type = -1;
}
if( type != CAST_GROUND ){
clif_skill_fail(sd,r_skill,USESKILL_FAIL_LEVEL,0);
map_freeblock_unlock();
return wd.dmg_lv;
}
}
if (sd->state.autocast == 0) {
sd->state.autocast = 1;
skill_consume_requirement(sd, r_skill, r_lv, 3);
switch (type) {
case CAST_GROUND:
skill_castend_pos2(src, target->x, target->y, r_skill, r_lv, tick, flag);
break;
case CAST_NODAMAGE:
skill_castend_nodamage_id(src, target, r_skill, r_lv, tick, flag);
break;
case CAST_DAMAGE:
skill_castend_damage_id(src, target, r_skill, r_lv, tick, flag);
break;
}
}
sd->state.autocast = 0;
sd->ud.canact_tick = i64max(tick + skill_delayfix(src, r_skill, r_lv), sd->ud.canact_tick);
clif_status_change(src, EFST_POSTDELAY, 1, skill_delayfix(src, r_skill, r_lv), 0, 0, 1);
}
}
if (wd.flag&BF_WEAPON && sc && sc->data[SC_FALLINGSTAR] && rand()%100 < sc->data[SC_FALLINGSTAR]->val2) {
if (sd)
sd->state.autocast = 1;
if (status_charge(src, 0, skill_get_sp(SJ_FALLINGSTAR_ATK, sc->data[SC_FALLINGSTAR]->val1)))
skill_castend_nodamage_id(src, src, SJ_FALLINGSTAR_ATK, sc->data[SC_FALLINGSTAR]->val1, tick, flag);
if (sd)
sd->state.autocast = 0;
}
if( sc ){
// It has a success chance of triggering even tho the description says nothing about it.
// TODO: Need to find out what the official success chance is. [Rytech]
if( sc->data[SC_SERVANTWEAPON] && sd->servantball > 0 && rnd() % 100 < 20 ){
uint16 skill_id = DK_SERVANTWEAPON_ATK;
uint16 skill_lv = sc->data[SC_SERVANTWEAPON]->val1;
sd->state.autocast = 1;
pc_delservantball( *sd );
skill_castend_damage_id( src, target, skill_id, skill_lv, tick, flag );
battle_autocast_aftercast( src, skill_id, skill_lv, tick );
sd->state.autocast = 0;
}
// TODO: Whats the official success chance? Is SP consumed for every autocast? [Rytech]
if( sc->data[SC_DUPLELIGHT] && pc_checkskill(sd, CD_PETITIO) > 0 && rnd() % 100 < 20 ){
uint16 skill_id = CD_PETITIO;
uint16 skill_lv = pc_checkskill( sd, CD_PETITIO );
sd->state.autocast = 1;
skill_castend_damage_id( src, target, skill_id, skill_lv, tick, flag );
battle_autocast_aftercast( src, skill_id, skill_lv, tick );
sd->state.autocast = 0;
}
// It has a success chance of triggering even tho the description says nothing about it.
// TODO: Need to find out what the official success chance is. [Rytech]
if( sc->data[SC_ABYSSFORCEWEAPON] && sd->abyssball > 0 && rnd() % 100 < 20 ){
uint16 skill_id = ABC_FROM_THE_ABYSS_ATK;
uint16 skill_lv = sc->data[SC_ABYSSFORCEWEAPON]->val1;
sd->state.autocast = 1;
pc_delabyssball( *sd );
skill_castend_damage_id( src, target, skill_id, skill_lv, tick, flag );
battle_autocast_aftercast( src, skill_id, skill_lv, tick );
sd->state.autocast = 0;
}
// It has a success chance of triggering even tho the description says nothing about it.
// TODO: Need to find out what the official success chance is. [Rytech]
if( sc->data[SC_ABYSSFORCEWEAPON] && rnd() % 100 < 20 ){
uint16 skill_id = ABC_ABYSS_SQUARE;
uint16 skill_lv = pc_checkskill(sd, ABC_ABYSS_SQUARE);
sd->state.autocast = 1;
skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag );
battle_autocast_aftercast( src, skill_id, skill_lv, tick );
sd->state.autocast = 0;
}
// Autocasted skills from super elemental supportive buffs.
if (sc->data[SC_FLAMETECHNIC_OPTION] && rnd() % 100 < 7)
battle_autocast_elembuff_skill(sd, target, MG_FIREBOLT, tick, flag);
if (sc->data[SC_COLD_FORCE_OPTION] && rnd() % 100 < 7)
battle_autocast_elembuff_skill(sd, target, MG_COLDBOLT, tick, flag);
if (sc->data[SC_GRACE_BREEZE_OPTION] && rnd() % 100 < 7)
battle_autocast_elembuff_skill(sd, target, MG_LIGHTNINGBOLT, tick, flag);
if (sc->data[SC_EARTH_CARE_OPTION] && rnd() % 100 < 7)
battle_autocast_elembuff_skill(sd, target, WZ_EARTHSPIKE, tick, flag);
if (sc->data[SC_DEEP_POISONING_OPTION] && rnd() % 100 < 7)
battle_autocast_elembuff_skill(sd, target, SO_POISON_BUSTER, tick, flag);
}
if (wd.flag & BF_WEAPON && src != target && damage > 0) {
if (battle_config.left_cardfix_to_right)
battle_drain(sd, target, wd.damage, wd.damage, tstatus->race, tstatus->class_);
else
battle_drain(sd, target, wd.damage, wd.damage2, tstatus->race, tstatus->class_);
}
}
if (tsc) {
if (damage > 0 && tsc->data[SC_POISONREACT] &&
(rnd()%100 < tsc->data[SC_POISONREACT]->val3
|| sstatus->def_ele == ELE_POISON) &&
// check_distance_bl(src, target, tstatus->rhw.range+1) && Doesn't checks range! o.O;
status_check_skilluse(target, src, TF_POISON, 0)
) { //Poison React
struct status_change_entry *sce = tsc->data[SC_POISONREACT];
if (sstatus->def_ele == ELE_POISON) {
sce->val2 = 0;
skill_attack(BF_WEAPON,target,target,src,AS_POISONREACT,sce->val1,tick,0);
} else {
skill_attack(BF_WEAPON,target,target,src,TF_POISON, 5, tick, 0);
--sce->val2;
}
if (sce->val2 <= 0)
status_change_end(target, SC_POISONREACT, INVALID_TIMER);
}
}
if (sd && tsc && wd.flag&BF_LONG && tsc->data[SC_WINDSIGN] && rand()%100 < tsc->data[SC_WINDSIGN]->val2)
status_heal(src, 0, 0, 1, 0);
map_freeblock_unlock();
return wd.dmg_lv;
}
/*=========================
* Check for undead status
*-------------------------
* Credits:
* Original coder Skotlex
* Refactored by Baalberith
*/
int battle_check_undead(int race,int element)
{
if(battle_config.undead_detect_type == 0) {
if(element == ELE_UNDEAD)
return 1;
}
else if(battle_config.undead_detect_type == 1) {
if(race == RC_UNDEAD)
return 1;
}
else {
if(element == ELE_UNDEAD || race == RC_UNDEAD)
return 1;
}
return 0;
}
/*================================================================
* Returns the upmost level master starting with the given object
*----------------------------------------------------------------*/
struct block_list* battle_get_master(struct block_list *src)
{
struct block_list *prev; //Used for infinite loop check (master of yourself?)
do {
prev = src;
switch (src->type) {
case BL_PET:
if (((TBL_PET*)src)->master)
src = (struct block_list*)((TBL_PET*)src)->master;
break;
case BL_MOB:
if (((TBL_MOB*)src)->master_id)
src = map_id2bl(((TBL_MOB*)src)->master_id);
break;
case BL_HOM:
if (((TBL_HOM*)src)->master)
src = (struct block_list*)((TBL_HOM*)src)->master;
break;
case BL_MER:
if (((TBL_MER*)src)->master)
src = (struct block_list*)((TBL_MER*)src)->master;
break;
case BL_ELEM:
if (((TBL_ELEM*)src)->master)
src = (struct block_list*)((TBL_ELEM*)src)->master;
break;
case BL_SKILL:
if (((TBL_SKILL*)src)->group && ((TBL_SKILL*)src)->group->src_id)
src = map_id2bl(((TBL_SKILL*)src)->group->src_id);
break;
}
} while (src && src != prev);
return prev;
}
/*==========================================
* Checks the state between two targets
* (enemy, friend, party, guild, etc)
*------------------------------------------
* Usage:
* See battle.hpp for possible values/combinations
* to be used here (BCT_* constants)
* Return value is:
* 1: flag holds true (is enemy, party, etc)
* -1: flag fails
* 0: Invalid target (non-targetable ever)
*
* Credits:
* Original coder unknown
* Rewritten by Skotlex
*/
int battle_check_target( struct block_list *src, struct block_list *target,int flag)
{
int16 m; //map
int state = 0; //Initial state none
int strip_enemy = 1; //Flag which marks whether to remove the BCT_ENEMY status if it's also friend/ally.
struct block_list *s_bl = src, *t_bl = target;
struct unit_data *ud = NULL;
nullpo_ret(src);
nullpo_ret(target);
ud = unit_bl2ud(target);
m = target->m;
//t_bl/s_bl hold the 'master' of the attack, while src/target are the actual
//objects involved.
if( (t_bl = battle_get_master(target)) == NULL )
t_bl = target;
if( (s_bl = battle_get_master(src)) == NULL )
s_bl = src;
if ( s_bl->type == BL_PC ) {
switch( t_bl->type ) {
case BL_MOB: // Source => PC, Target => MOB
if ( pc_has_permission((TBL_PC*)s_bl, PC_PERM_DISABLE_PVM) )
return 0;
break;
case BL_PC:
if (pc_has_permission((TBL_PC*)s_bl, PC_PERM_DISABLE_PVP))
return 0;
break;
default:/* anything else goes */
break;
}
}
struct map_data *mapdata = map_getmapdata(m);
switch( target->type ) { // Checks on actual target
case BL_PC: {
struct status_change* sc = status_get_sc(src);
if (((TBL_PC*)target)->invincible_timer != INVALID_TIMER || pc_isinvisible((TBL_PC*)target))
return -1; //Cannot be targeted yet.
if( sc && sc->count ) {
if( sc->data[SC_VOICEOFSIREN] && sc->data[SC_VOICEOFSIREN]->val2 == target->id )
return -1;
}
}
break;
case BL_MOB:
{
struct mob_data *md = ((TBL_MOB*)target);
if (ud && ud->immune_attack)
return 0;
if(((md->special_state.ai == AI_SPHERE || //Marine Spheres
(md->special_state.ai == AI_FLORA && battle_config.summon_flora&1)) && s_bl->type == BL_PC && src->type != BL_MOB) || //Floras
(md->special_state.ai == AI_ZANZOU && t_bl->id != s_bl->id) || //Zanzou
(md->special_state.ai == AI_FAW && (t_bl->id != s_bl->id || (s_bl->type == BL_PC && src->type != BL_MOB)))
){ //Targettable by players
state |= BCT_ENEMY;
strip_enemy = 0;
}
break;
}
case BL_SKILL:
{
TBL_SKILL *su = (TBL_SKILL*)target;
uint16 skill_id = battle_getcurrentskill(src);
if( !su || !su->group)
return 0;
if( skill_get_inf2(su->group->skill_id, INF2_ISTRAP) && su->group->unit_id != UNT_USED_TRAPS) {
if (!skill_id || su->group->skill_id == NPC_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD) {
;
}
else if (skill_get_inf2(skill_id, INF2_TARGETTRAP)) { // Only a few skills can target traps
switch (skill_id) {
case RK_DRAGONBREATH:
case RK_DRAGONBREATH_WATER:
case NC_SELFDESTRUCTION:
case NC_AXETORNADO:
case SR_SKYNETBLOW:
// Can only hit traps in PVP/GVG maps
if (!mapdata->flag[MF_PVP] && !mapdata->flag[MF_GVG])
return 0;
break;
}
}
else
return 0;
state |= BCT_ENEMY;
strip_enemy = 0;
} else if (su->group->skill_id == WZ_ICEWALL || (su->group->skill_id == GN_WALLOFTHORN && skill_id != GN_CARTCANNON)) {
switch (skill_id) {
case RK_DRAGONBREATH:
case RK_DRAGONBREATH_WATER:
case NC_SELFDESTRUCTION:
case NC_AXETORNADO:
case SR_SKYNETBLOW:
// Can only hit icewall in PVP/GVG maps
if (!mapdata->flag[MF_PVP] && !mapdata->flag[MF_GVG])
return 0;
break;
case HT_CLAYMORETRAP:
// Can't hit icewall
return 0;
default:
// Usually BCT_ALL stands for only hitting chars, but skills specifically set to hit traps also hit icewall
if ((flag&BCT_ALL) == BCT_ALL && !skill_get_inf2(skill_id, INF2_TARGETTRAP))
return -1;
}
state |= BCT_ENEMY;
strip_enemy = 0;
} else //Excepting traps, Icewall, and Wall of Thorns, you should not be able to target skills.
return 0;
}
break;
case BL_MER:
case BL_HOM:
case BL_ELEM:
if (ud && ud->immune_attack)
return 0;
break;
//All else not specified is an invalid target.
default:
return 0;
} //end switch actual target
switch( t_bl->type ) { //Checks on target master
case BL_PC: {
struct map_session_data *sd;
struct status_change *sc = NULL;
if( t_bl == s_bl )
break;
sd = BL_CAST(BL_PC, t_bl);
sc = status_get_sc(t_bl);
if( ((sd->state.block_action & PCBLOCK_IMMUNE) || (sc->data[SC_KINGS_GRACE] && s_bl->type != BL_PC)) && flag&BCT_ENEMY )
return 0; // Global immunity only to Attacks
if( sd->status.karma && s_bl->type == BL_PC && ((TBL_PC*)s_bl)->status.karma )
state |= BCT_ENEMY; // Characters with bad karma may fight amongst them
if( sd->state.killable ) {
state |= BCT_ENEMY; // Everything can kill it
strip_enemy = 0;
}
break;
}
case BL_MOB:
{
struct mob_data *md = BL_CAST(BL_MOB, t_bl);
if( md->guardian_data && md->guardian_data->guild_id && !mapdata_flag_gvg(mapdata) )
return 0; // Disable guardians/emperiums owned by Guilds on non-woe times.
break;
}
default: break; //other type doesn't have slave yet
} //end switch master target
switch( src->type ) { //Checks on actual src type
case BL_PET:
if (t_bl->type != BL_MOB && flag&BCT_ENEMY)
return 0; //Pet may not attack non-mobs.
if (t_bl->type == BL_MOB && flag & BCT_ENEMY) {
mob_data *md = BL_CAST(BL_MOB, t_bl);
if (md->guardian_data || md->special_state.ai == AI_GUILD)
return 0; //pet may not attack Guardians/Emperium
}
break;
case BL_SKILL: {
struct skill_unit *su = (struct skill_unit *)src;
struct status_change* sc = status_get_sc(target);
if (!su || !su->group)
return 0;
std::bitset<INF2_MAX> inf2 = skill_db.find(su->group->skill_id)->inf2;
if (su->group->src_id == target->id) {
if (inf2[INF2_NOTARGETSELF])
return -1;
if (inf2[INF2_TARGETSELF])
return 1;
}
//Status changes that prevent traps from triggering
if (sc && sc->count && inf2[INF2_ISTRAP]) {
if( sc->data[SC_SIGHTBLASTER] && sc->data[SC_SIGHTBLASTER]->val2 > 0 && sc->data[SC_SIGHTBLASTER]->val4%2 == 0)
return -1;
}
}
break;
case BL_MER:
if (t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->mob_id == MOBID_EMPERIUM && flag&BCT_ENEMY)
return 0; //mercenary may not attack Emperium
break;
} //end switch actual src
switch( s_bl->type )
{ //Checks on source master
case BL_PC:
{
struct map_session_data *sd = BL_CAST(BL_PC, s_bl);
if( s_bl != t_bl )
{
if( sd->state.killer )
{
state |= BCT_ENEMY; // Can kill anything
strip_enemy = 0;
}
else if( sd->duel_group && !((!battle_config.duel_allow_pvp && mapdata->flag[MF_PVP]) || (!battle_config.duel_allow_gvg && mapdata_flag_gvg(mapdata))) )
{
if( t_bl->type == BL_PC && (sd->duel_group == ((TBL_PC*)t_bl)->duel_group) )
return (BCT_ENEMY&flag)?1:-1; // Duel targets can ONLY be your enemy, nothing else.
else
return 0; // You can't target anything out of your duel
}
}
if( !sd->status.guild_id && t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->mob_id == MOBID_EMPERIUM && mapdata_flag_gvg(mapdata) )
return 0; //If you don't belong to a guild, can't target emperium.
if( t_bl->type != BL_PC )
state |= BCT_ENEMY; //Natural enemy.
break;
}
case BL_MOB:
{
struct mob_data *md = BL_CAST(BL_MOB, s_bl);
if( md->guardian_data && md->guardian_data->guild_id && !mapdata_flag_gvg(mapdata) )
return 0; // Disable guardians/emperium owned by Guilds on non-woe times.
if( !md->special_state.ai )
{ //Normal mobs
if(
( target->type == BL_MOB && t_bl->type == BL_PC && ( ((TBL_MOB*)target)->special_state.ai != AI_ZANZOU && ((TBL_MOB*)target)->special_state.ai != AI_ATTACK ) ) ||
( t_bl->type == BL_MOB && (((TBL_MOB*)t_bl)->special_state.ai == AI_NONE || ((TBL_MOB*)t_bl)->special_state.ai == AI_WAVEMODE ))
)
state |= BCT_PARTY; //Normal mobs with no ai or with AI_WAVEMODE are friends.
else
state |= BCT_ENEMY; //However, all else are enemies.
}
else
{
if( t_bl->type == BL_MOB && !((TBL_MOB*)t_bl)->special_state.ai )
state |= BCT_ENEMY; //Natural enemy for AI mobs are normal mobs.
}
break;
}
default:
//Need some sort of default behaviour for unhandled types.
if (t_bl->type != s_bl->type)
state |= BCT_ENEMY;
break;
} //end switch on src master
if( (flag&BCT_ALL) == BCT_ALL )
{ //All actually stands for all attackable chars, icewall and traps
if(target->type&(BL_CHAR|BL_SKILL))
return 1;
else
return -1;
}
if( flag == BCT_NOONE ) //Why would someone use this? no clue.
return -1;
if( t_bl == s_bl )
{ //No need for further testing.
state |= BCT_SELF|BCT_PARTY|BCT_GUILD;
if( state&BCT_ENEMY && strip_enemy )
state&=~BCT_ENEMY;
return (flag&state)?1:-1;
}
if( mapdata_flag_vs(mapdata) )
{ //Check rivalry settings.
int sbg_id = 0, tbg_id = 0;
if(mapdata->flag[MF_BATTLEGROUND] )
{
sbg_id = bg_team_get_id(s_bl);
tbg_id = bg_team_get_id(t_bl);
}
if( flag&(BCT_PARTY|BCT_ENEMY) )
{
int s_party = status_get_party_id(s_bl);
if( s_party && s_party == status_get_party_id(t_bl) && !(mapdata->flag[MF_PVP] && mapdata->flag[MF_PVP_NOPARTY]) && !(mapdata_flag_gvg(mapdata) && mapdata->flag[MF_GVG_NOPARTY]) && (!mapdata->flag[MF_BATTLEGROUND] || sbg_id == tbg_id) )
state |= BCT_PARTY;
else
state |= BCT_ENEMY;
}
if( flag&(BCT_GUILD|BCT_ENEMY) )
{
int s_guild = status_get_guild_id(s_bl);
int t_guild = status_get_guild_id(t_bl);
if( !(mapdata->flag[MF_PVP] && mapdata->flag[MF_PVP_NOGUILD]) && s_guild && t_guild && (s_guild == t_guild || (!(flag&BCT_SAMEGUILD) && guild_isallied(s_guild, t_guild))) && (!mapdata->flag[MF_BATTLEGROUND] || sbg_id == tbg_id) )
state |= BCT_GUILD;
else
state |= BCT_ENEMY;
}
if( state&BCT_ENEMY && mapdata->flag[MF_BATTLEGROUND] && sbg_id && sbg_id == tbg_id )
state &= ~BCT_ENEMY;
if( state&BCT_ENEMY && battle_config.pk_mode && !mapdata_flag_gvg(mapdata) && s_bl->type == BL_PC && t_bl->type == BL_PC )
{ // Prevent novice engagement on pk_mode (feature by Valaris)
TBL_PC *sd = (TBL_PC*)s_bl, *sd2 = (TBL_PC*)t_bl;
if (
(sd->class_&MAPID_UPPERMASK) == MAPID_NOVICE ||
(sd2->class_&MAPID_UPPERMASK) == MAPID_NOVICE ||
(int)sd->status.base_level < battle_config.pk_min_level ||
(int)sd2->status.base_level < battle_config.pk_min_level ||
(battle_config.pk_level_range && abs((int)sd->status.base_level - (int)sd2->status.base_level) > battle_config.pk_level_range)
)
state &= ~BCT_ENEMY;
}
}//end map_flag_vs chk rivality
else
{ //Non pvp/gvg, check party/guild settings.
if( flag&BCT_PARTY || state&BCT_ENEMY )
{
int s_party = status_get_party_id(s_bl);
if(s_party && s_party == status_get_party_id(t_bl))
state |= BCT_PARTY;
}
if( flag&BCT_GUILD || state&BCT_ENEMY )
{
int s_guild = status_get_guild_id(s_bl);
int t_guild = status_get_guild_id(t_bl);
if(s_guild && t_guild && (s_guild == t_guild || (!(flag&BCT_SAMEGUILD) && guild_isallied(s_guild, t_guild))))
state |= BCT_GUILD;
}
} //end non pvp/gvg chk rivality
if( !state ) //If not an enemy, nor a guild, nor party, nor yourself, it's neutral.
state = BCT_NEUTRAL;
//Alliance state takes precedence over enemy one.
else if( state&BCT_ENEMY && strip_enemy && state&(BCT_SELF|BCT_PARTY|BCT_GUILD) )
state&=~BCT_ENEMY;
return (flag&state)?1:-1;
}
/*==========================================
* Check if can attack from this range
* Basic check then calling path_search for obstacle etc..
*------------------------------------------
*/
bool battle_check_range(struct block_list *src, struct block_list *bl, int range)
{
int d;
nullpo_retr(false, src);
nullpo_retr(false, bl);
if( src->m != bl->m )
return false;
#ifndef CIRCULAR_AREA
if( src->type == BL_PC ) { // Range for players' attacks and skills should always have a circular check. [Angezerus]
if ( !check_distance_client_bl(src, bl, range) )
return false;
} else
#endif
if( !check_distance_bl(src, bl, range) )
return false;
if( (d = distance_bl(src, bl)) < 2 )
return true; // No need for path checking.
if( d > AREA_SIZE )
return false; // Avoid targetting objects beyond your range of sight.
return path_search_long(NULL,src->m,src->x,src->y,bl->x,bl->y,CELL_CHKWALL);
}
/*=============================================
* Battle.conf settings and default/max values
*---------------------------------------------
*/
static const struct _battle_data {
const char* str;
int* val;
int defval;
int min;
int max;
} battle_data[] = {
{ "warp_point_debug", &battle_config.warp_point_debug, 0, 0, 1, },
{ "enable_critical", &battle_config.enable_critical, BL_PC, BL_NUL, BL_ALL, },
{ "mob_critical_rate", &battle_config.mob_critical_rate, 100, 0, INT_MAX, },
{ "critical_rate", &battle_config.critical_rate, 100, 0, INT_MAX, },
{ "enable_baseatk", &battle_config.enable_baseatk, BL_CHAR|BL_HOM, BL_NUL, BL_ALL, },
{ "enable_baseatk_renewal", &battle_config.enable_baseatk_renewal, BL_ALL, BL_NUL, BL_ALL, },
{ "enable_perfect_flee", &battle_config.enable_perfect_flee, BL_PC|BL_PET, BL_NUL, BL_ALL, },
{ "casting_rate", &battle_config.cast_rate, 100, 0, INT_MAX, },
{ "delay_rate", &battle_config.delay_rate, 100, 0, INT_MAX, },
{ "delay_dependon_dex", &battle_config.delay_dependon_dex, 0, 0, 1, },
{ "delay_dependon_agi", &battle_config.delay_dependon_agi, 0, 0, 1, },
{ "skill_delay_attack_enable", &battle_config.sdelay_attack_enable, 0, 0, 1, },
{ "left_cardfix_to_right", &battle_config.left_cardfix_to_right, 0, 0, 1, },
{ "skill_add_range", &battle_config.skill_add_range, 0, 0, INT_MAX, },
{ "skill_out_range_consume", &battle_config.skill_out_range_consume, 1, 0, 1, },
{ "skillrange_by_distance", &battle_config.skillrange_by_distance, ~BL_PC, BL_NUL, BL_ALL, },
{ "skillrange_from_weapon", &battle_config.use_weapon_skill_range, BL_NUL, BL_NUL, BL_ALL, },
{ "player_damage_delay_rate", &battle_config.pc_damage_delay_rate, 100, 0, INT_MAX, },
{ "defunit_not_enemy", &battle_config.defnotenemy, 0, 0, 1, },
{ "gvg_traps_target_all", &battle_config.vs_traps_bctall, BL_PC, BL_NUL, BL_ALL, },
#ifdef RENEWAL
{ "traps_setting", &battle_config.traps_setting, 2, 0, 2, },
#else
{ "traps_setting", &battle_config.traps_setting, 0, 0, 2, },
#endif
{ "summon_flora_setting", &battle_config.summon_flora, 1|2, 0, 1|2, },
{ "clear_skills_on_death", &battle_config.clear_unit_ondeath, BL_NUL, BL_NUL, BL_ALL, },
{ "clear_skills_on_warp", &battle_config.clear_unit_onwarp, BL_ALL, BL_NUL, BL_ALL, },
{ "random_monster_checklv", &battle_config.random_monster_checklv, 0, 0, 1, },
{ "attribute_recover", &battle_config.attr_recover, 1, 0, 1, },
{ "flooritem_lifetime", &battle_config.flooritem_lifetime, 60000, 1000, INT_MAX, },
{ "item_auto_get", &battle_config.item_auto_get, 0, 0, 1, },
{ "item_first_get_time", &battle_config.item_first_get_time, 3000, 0, INT_MAX, },
{ "item_second_get_time", &battle_config.item_second_get_time, 1000, 0, INT_MAX, },
{ "item_third_get_time", &battle_config.item_third_get_time, 1000, 0, INT_MAX, },
{ "mvp_item_first_get_time", &battle_config.mvp_item_first_get_time, 10000, 0, INT_MAX, },
{ "mvp_item_second_get_time", &battle_config.mvp_item_second_get_time, 10000, 0, INT_MAX, },
{ "mvp_item_third_get_time", &battle_config.mvp_item_third_get_time, 2000, 0, INT_MAX, },
{ "drop_rate0item", &battle_config.drop_rate0item, 0, 0, 1, },
{ "base_exp_rate", &battle_config.base_exp_rate, 100, 0, INT_MAX, },
{ "job_exp_rate", &battle_config.job_exp_rate, 100, 0, INT_MAX, },
{ "pvp_exp", &battle_config.pvp_exp, 1, 0, 1, },
{ "death_penalty_type", &battle_config.death_penalty_type, 0, 0, 2, },
{ "death_penalty_base", &battle_config.death_penalty_base, 0, 0, INT_MAX, },
{ "death_penalty_job", &battle_config.death_penalty_job, 0, 0, INT_MAX, },
{ "zeny_penalty", &battle_config.zeny_penalty, 0, 0, INT_MAX, },
{ "hp_rate", &battle_config.hp_rate, 100, 1, INT_MAX, },
{ "sp_rate", &battle_config.sp_rate, 100, 1, INT_MAX, },
{ "restart_hp_rate", &battle_config.restart_hp_rate, 0, 0, 100, },
{ "restart_sp_rate", &battle_config.restart_sp_rate, 0, 0, 100, },
{ "guild_aura", &battle_config.guild_aura, 31, 0, 31, },
{ "mvp_hp_rate", &battle_config.mvp_hp_rate, 100, 1, INT_MAX, },
{ "mvp_exp_rate", &battle_config.mvp_exp_rate, 100, 0, INT_MAX, },
{ "monster_hp_rate", &battle_config.monster_hp_rate, 100, 1, INT_MAX, },
{ "monster_max_aspd", &battle_config.monster_max_aspd, 199, 100, 199, },
{ "view_range_rate", &battle_config.view_range_rate, 100, 0, INT_MAX, },
{ "chase_range_rate", &battle_config.chase_range_rate, 100, 0, INT_MAX, },
{ "gtb_sc_immunity", &battle_config.gtb_sc_immunity, 50, 0, INT_MAX, },
{ "guild_max_castles", &battle_config.guild_max_castles, 0, 0, INT_MAX, },
{ "guild_skill_relog_delay", &battle_config.guild_skill_relog_delay, 300000, 0, INT_MAX, },
{ "emergency_call", &battle_config.emergency_call, 11, 0, 31, },
{ "atcommand_spawn_quantity_limit", &battle_config.atc_spawn_quantity_limit, 100, 0, INT_MAX, },
{ "atcommand_slave_clone_limit", &battle_config.atc_slave_clone_limit, 25, 0, INT_MAX, },
{ "partial_name_scan", &battle_config.partial_name_scan, 0, 0, 1, },
{ "player_skillfree", &battle_config.skillfree, 0, 0, 1, },
{ "player_skillup_limit", &battle_config.skillup_limit, 1, 0, 1, },
{ "weapon_produce_rate", &battle_config.wp_rate, 100, 0, INT_MAX, },
{ "potion_produce_rate", &battle_config.pp_rate, 100, 0, INT_MAX, },
{ "monster_active_enable", &battle_config.monster_active_enable, 1, 0, 1, },
{ "monster_damage_delay_rate", &battle_config.monster_damage_delay_rate, 100, 0, INT_MAX, },
{ "monster_loot_type", &battle_config.monster_loot_type, 0, 0, 1, },
// { "mob_skill_use", &battle_config.mob_skill_use, 1, 0, 1, }, //Deprecated
{ "mob_skill_rate", &battle_config.mob_skill_rate, 100, 0, INT_MAX, },
{ "mob_skill_delay", &battle_config.mob_skill_delay, 100, 0, INT_MAX, },
{ "mob_count_rate", &battle_config.mob_count_rate, 100, 0, INT_MAX, },
{ "mob_spawn_delay", &battle_config.mob_spawn_delay, 100, 0, INT_MAX, },
{ "plant_spawn_delay", &battle_config.plant_spawn_delay, 100, 0, INT_MAX, },
{ "boss_spawn_delay", &battle_config.boss_spawn_delay, 100, 0, INT_MAX, },
{ "no_spawn_on_player", &battle_config.no_spawn_on_player, 0, 0, 100, },
{ "force_random_spawn", &battle_config.force_random_spawn, 0, 0, 1, },
{ "slaves_inherit_mode", &battle_config.slaves_inherit_mode, 4, 0, 4, },
{ "slaves_inherit_speed", &battle_config.slaves_inherit_speed, 3, 0, 3, },
{ "summons_trigger_autospells", &battle_config.summons_trigger_autospells, 1, 0, 1, },
{ "pc_damage_walk_delay_rate", &battle_config.pc_walk_delay_rate, 20, 0, INT_MAX, },
{ "damage_walk_delay_rate", &battle_config.walk_delay_rate, 100, 0, INT_MAX, },
{ "multihit_delay", &battle_config.multihit_delay, 80, 0, INT_MAX, },
{ "quest_skill_learn", &battle_config.quest_skill_learn, 0, 0, 1, },
{ "quest_skill_reset", &battle_config.quest_skill_reset, 0, 0, 1, },
{ "basic_skill_check", &battle_config.basic_skill_check, 1, 0, 1, },
{ "guild_emperium_check", &battle_config.guild_emperium_check, 1, 0, 1, },
{ "guild_exp_limit", &battle_config.guild_exp_limit, 50, 0, 99, },
{ "player_invincible_time", &battle_config.pc_invincible_time, 5000, 0, INT_MAX, },
{ "pet_catch_rate", &battle_config.pet_catch_rate, 100, 0, INT_MAX, },
{ "pet_rename", &battle_config.pet_rename, 0, 0, 1, },
{ "pet_friendly_rate", &battle_config.pet_friendly_rate, 100, 0, INT_MAX, },
{ "pet_hungry_delay_rate", &battle_config.pet_hungry_delay_rate, 100, 10, INT_MAX, },
{ "pet_hungry_friendly_decrease", &battle_config.pet_hungry_friendly_decrease, 5, 0, INT_MAX, },
{ "pet_status_support", &battle_config.pet_status_support, 0, 0, 1, },
{ "pet_attack_support", &battle_config.pet_attack_support, 0, 0, 1, },
{ "pet_damage_support", &battle_config.pet_damage_support, 0, 0, 1, },
{ "pet_support_min_friendly", &battle_config.pet_support_min_friendly, 900, 0, 950, },
{ "pet_support_rate", &battle_config.pet_support_rate, 100, 0, INT_MAX, },
{ "pet_attack_exp_to_master", &battle_config.pet_attack_exp_to_master, 0, 0, 1, },
{ "pet_attack_exp_rate", &battle_config.pet_attack_exp_rate, 100, 0, INT_MAX, },
{ "pet_lv_rate", &battle_config.pet_lv_rate, 0, 0, INT_MAX, },
{ "pet_max_stats", &battle_config.pet_max_stats, 99, 0, INT_MAX, },
{ "pet_max_atk1", &battle_config.pet_max_atk1, 750, 0, INT_MAX, },
{ "pet_max_atk2", &battle_config.pet_max_atk2, 1000, 0, INT_MAX, },
{ "pet_disable_in_gvg", &battle_config.pet_no_gvg, 0, 0, 1, },
{ "pet_master_dead", &battle_config.pet_master_dead, 0, 0, 1, },
{ "skill_min_damage", &battle_config.skill_min_damage, 2|4, 0, 1|2|4, },
{ "finger_offensive_type", &battle_config.finger_offensive_type, 0, 0, 1, },
{ "heal_exp", &battle_config.heal_exp, 0, 0, INT_MAX, },
{ "resurrection_exp", &battle_config.resurrection_exp, 0, 0, INT_MAX, },
{ "shop_exp", &battle_config.shop_exp, 0, 0, INT_MAX, },
{ "max_heal_lv", &battle_config.max_heal_lv, 11, 1, INT_MAX, },
{ "max_heal", &battle_config.max_heal, 9999, 0, INT_MAX, },
{ "combo_delay_rate", &battle_config.combo_delay_rate, 100, 0, INT_MAX, },
{ "item_check", &battle_config.item_check, 0x0, 0x0, 0x7, },
{ "item_use_interval", &battle_config.item_use_interval, 100, 0, INT_MAX, },
{ "cashfood_use_interval", &battle_config.cashfood_use_interval, 60000, 0, INT_MAX, },
{ "wedding_modifydisplay", &battle_config.wedding_modifydisplay, 0, 0, 1, },
{ "wedding_ignorepalette", &battle_config.wedding_ignorepalette, 0, 0, 1, },
{ "xmas_ignorepalette", &battle_config.xmas_ignorepalette, 0, 0, 1, },
{ "summer_ignorepalette", &battle_config.summer_ignorepalette, 0, 0, 1, },
{ "hanbok_ignorepalette", &battle_config.hanbok_ignorepalette, 0, 0, 1, },
{ "oktoberfest_ignorepalette", &battle_config.oktoberfest_ignorepalette, 0, 0, 1, },
{ "natural_healhp_interval", &battle_config.natural_healhp_interval, 6000, NATURAL_HEAL_INTERVAL, INT_MAX, },
{ "natural_healsp_interval", &battle_config.natural_healsp_interval, 8000, NATURAL_HEAL_INTERVAL, INT_MAX, },
{ "natural_heal_skill_interval", &battle_config.natural_heal_skill_interval, 10000, NATURAL_HEAL_INTERVAL, INT_MAX, },
{ "natural_heal_weight_rate", &battle_config.natural_heal_weight_rate, 50, 0, 100 },
{ "natural_heal_weight_rate_renewal", &battle_config.natural_heal_weight_rate_renewal,70, 0, 100 },
{ "arrow_decrement", &battle_config.arrow_decrement, 1, 0, 2, },
{ "ammo_unequip", &battle_config.ammo_unequip, 1, 0, 1, },
{ "ammo_check_weapon", &battle_config.ammo_check_weapon, 1, 0, 1, },
{ "max_aspd", &battle_config.max_aspd, 190, 100, 199, },
{ "max_third_aspd", &battle_config.max_third_aspd, 193, 100, 199, },
{ "max_summoner_aspd", &battle_config.max_summoner_aspd, 193, 100, 199, },
{ "max_walk_speed", &battle_config.max_walk_speed, 300, 100, 100*DEFAULT_WALK_SPEED, },
{ "max_lv", &battle_config.max_lv, 99, 0, MAX_LEVEL, },
{ "aura_lv", &battle_config.aura_lv, 99, 0, INT_MAX, },
{ "max_hp_lv99", &battle_config.max_hp_lv99, 330000, 100, 1000000000, },
{ "max_hp_lv150", &battle_config.max_hp_lv150, 660000, 100, 1000000000, },
{ "max_hp", &battle_config.max_hp, 1100000, 100, 1000000000, },
{ "max_sp", &battle_config.max_sp, 32500, 100, 1000000000, },
{ "max_cart_weight", &battle_config.max_cart_weight, 8000, 100, 1000000, },
{ "max_parameter", &battle_config.max_parameter, 99, 10, SHRT_MAX, },
{ "max_baby_parameter", &battle_config.max_baby_parameter, 80, 10, SHRT_MAX, },
{ "max_def", &battle_config.max_def, 99, 0, INT_MAX, },
{ "over_def_bonus", &battle_config.over_def_bonus, 0, 0, 1000, },
{ "skill_log", &battle_config.skill_log, BL_NUL, BL_NUL, BL_ALL, },
{ "battle_log", &battle_config.battle_log, 0, 0, 1, },
{ "etc_log", &battle_config.etc_log, 1, 0, 1, },
{ "save_clothcolor", &battle_config.save_clothcolor, 1, 0, 1, },
{ "undead_detect_type", &battle_config.undead_detect_type, 0, 0, 2, },
{ "auto_counter_type", &battle_config.auto_counter_type, BL_ALL, BL_NUL, BL_ALL, },
{ "min_hitrate", &battle_config.min_hitrate, 5, 0, 100, },
{ "max_hitrate", &battle_config.max_hitrate, 100, 0, 100, },
{ "agi_penalty_target", &battle_config.agi_penalty_target, BL_PC, BL_NUL, BL_ALL, },
{ "agi_penalty_type", &battle_config.agi_penalty_type, 1, 0, 2, },
{ "agi_penalty_count", &battle_config.agi_penalty_count, 3, 2, INT_MAX, },
{ "agi_penalty_num", &battle_config.agi_penalty_num, 10, 0, INT_MAX, },
{ "vit_penalty_target", &battle_config.vit_penalty_target, BL_PC, BL_NUL, BL_ALL, },
{ "vit_penalty_type", &battle_config.vit_penalty_type, 1, 0, 2, },
{ "vit_penalty_count", &battle_config.vit_penalty_count, 3, 2, INT_MAX, },
{ "vit_penalty_num", &battle_config.vit_penalty_num, 5, 1, INT_MAX, },
{ "weapon_defense_type", &battle_config.weapon_defense_type, 0, 0, INT_MAX, },
{ "magic_defense_type", &battle_config.magic_defense_type, 0, 0, INT_MAX, },
{ "skill_reiteration", &battle_config.skill_reiteration, BL_NUL, BL_NUL, BL_ALL, },
{ "skill_nofootset", &battle_config.skill_nofootset, BL_PC, BL_NUL, BL_ALL, },
{ "player_cloak_check_type", &battle_config.pc_cloak_check_type, 1, 0, 1|2|4, },
{ "monster_cloak_check_type", &battle_config.monster_cloak_check_type, 4, 0, 1|2|4, },
{ "sense_type", &battle_config.estimation_type, 1|2, 0, 1|2, },
{ "gvg_short_attack_damage_rate", &battle_config.gvg_short_damage_rate, 80, 0, INT_MAX, },
{ "gvg_long_attack_damage_rate", &battle_config.gvg_long_damage_rate, 80, 0, INT_MAX, },
{ "gvg_weapon_attack_damage_rate", &battle_config.gvg_weapon_damage_rate, 60, 0, INT_MAX, },
{ "gvg_magic_attack_damage_rate", &battle_config.gvg_magic_damage_rate, 60, 0, INT_MAX, },
{ "gvg_misc_attack_damage_rate", &battle_config.gvg_misc_damage_rate, 60, 0, INT_MAX, },
{ "gvg_flee_penalty", &battle_config.gvg_flee_penalty, 20, 0, INT_MAX, },
{ "pk_short_attack_damage_rate", &battle_config.pk_short_damage_rate, 80, 0, INT_MAX, },
{ "pk_long_attack_damage_rate", &battle_config.pk_long_damage_rate, 70, 0, INT_MAX, },
{ "pk_weapon_attack_damage_rate", &battle_config.pk_weapon_damage_rate, 60, 0, INT_MAX, },
{ "pk_magic_attack_damage_rate", &battle_config.pk_magic_damage_rate, 60, 0, INT_MAX, },
{ "pk_misc_attack_damage_rate", &battle_config.pk_misc_damage_rate, 60, 0, INT_MAX, },
{ "mob_changetarget_byskill", &battle_config.mob_changetarget_byskill, 0, 0, 1, },
{ "attack_direction_change", &battle_config.attack_direction_change, BL_ALL, BL_NUL, BL_ALL, },
{ "land_skill_limit", &battle_config.land_skill_limit, BL_ALL, BL_NUL, BL_ALL, },
{ "monster_class_change_full_recover", &battle_config.monster_class_change_recover, 1, 0, 1, },
{ "produce_item_name_input", &battle_config.produce_item_name_input, 0x1|0x2, 0, 0x9F, },
{ "display_skill_fail", &battle_config.display_skill_fail, 2, 0, 1|2|4|8, },
{ "chat_warpportal", &battle_config.chat_warpportal, 0, 0, 1, },
{ "mob_warp", &battle_config.mob_warp, 0, 0, 1|2|4|8, },
{ "dead_branch_active", &battle_config.dead_branch_active, 1, 0, 1, },
{ "vending_max_value", &battle_config.vending_max_value, 10000000, 1, MAX_ZENY, },
{ "vending_over_max", &battle_config.vending_over_max, 1, 0, 1, },
{ "show_steal_in_same_party", &battle_config.show_steal_in_same_party, 0, 0, 1, },
{ "party_hp_mode", &battle_config.party_hp_mode, 0, 0, 1, },
{ "show_party_share_picker", &battle_config.party_show_share_picker, 1, 0, 1, },
{ "show_picker.item_type", &battle_config.show_picker_item_type, 112, 0, INT_MAX, },
{ "party_update_interval", &battle_config.party_update_interval, 1000, 100, INT_MAX, },
{ "party_item_share_type", &battle_config.party_share_type, 0, 0, 1|2|3, },
{ "attack_attr_none", &battle_config.attack_attr_none, ~BL_PC, BL_NUL, BL_ALL, },
{ "gx_allhit", &battle_config.gx_allhit, 0, 0, 1, },
{ "gx_disptype", &battle_config.gx_disptype, 1, 0, 1, },
{ "devotion_level_difference", &battle_config.devotion_level_difference, 10, 0, INT_MAX, },
{ "player_skill_partner_check", &battle_config.player_skill_partner_check, 1, 0, 1, },
{ "invite_request_check", &battle_config.invite_request_check, 1, 0, 1, },
{ "skill_removetrap_type", &battle_config.skill_removetrap_type, 0, 0, 1, },
{ "disp_experience", &battle_config.disp_experience, 0, 0, 1, },
{ "disp_zeny", &battle_config.disp_zeny, 0, 0, 1, },
{ "bone_drop", &battle_config.bone_drop, 0, 0, 2, },
{ "buyer_name", &battle_config.buyer_name, 1, 0, 1, },
{ "skill_wall_check", &battle_config.skill_wall_check, 1, 0, 1, },
{ "official_cell_stack_limit", &battle_config.official_cell_stack_limit, 1, 0, 255, },
{ "custom_cell_stack_limit", &battle_config.custom_cell_stack_limit, 1, 1, 255, },
{ "dancing_weaponswitch_fix", &battle_config.dancing_weaponswitch_fix, 1, 0, 1, },
// eAthena additions
{ "item_logarithmic_drops", &battle_config.logarithmic_drops, 0, 0, 1, },
{ "item_drop_common_min", &battle_config.item_drop_common_min, 1, 0, 10000, },
{ "item_drop_common_max", &battle_config.item_drop_common_max, 10000, 1, 10000, },
{ "item_drop_equip_min", &battle_config.item_drop_equip_min, 1, 0, 10000, },
{ "item_drop_equip_max", &battle_config.item_drop_equip_max, 10000, 1, 10000, },
{ "item_drop_card_min", &battle_config.item_drop_card_min, 1, 0, 10000, },
{ "item_drop_card_max", &battle_config.item_drop_card_max, 10000, 1, 10000, },
{ "item_drop_mvp_min", &battle_config.item_drop_mvp_min, 1, 0, 10000, },
{ "item_drop_mvp_max", &battle_config.item_drop_mvp_max, 10000, 1, 10000, },
{ "item_drop_mvp_mode", &battle_config.item_drop_mvp_mode, 0, 0, 2, },
{ "item_drop_heal_min", &battle_config.item_drop_heal_min, 1, 0, 10000, },
{ "item_drop_heal_max", &battle_config.item_drop_heal_max, 10000, 1, 10000, },
{ "item_drop_use_min", &battle_config.item_drop_use_min, 1, 0, 10000, },
{ "item_drop_use_max", &battle_config.item_drop_use_max, 10000, 1, 10000, },
{ "item_drop_add_min", &battle_config.item_drop_adddrop_min, 1, 0, 10000, },
{ "item_drop_add_max", &battle_config.item_drop_adddrop_max, 10000, 1, 10000, },
{ "item_drop_treasure_min", &battle_config.item_drop_treasure_min, 1, 0, 10000, },
{ "item_drop_treasure_max", &battle_config.item_drop_treasure_max, 10000, 1, 10000, },
{ "item_rate_mvp", &battle_config.item_rate_mvp, 100, 0, 1000000, },
{ "item_rate_common", &battle_config.item_rate_common, 100, 0, 1000000, },
{ "item_rate_common_boss", &battle_config.item_rate_common_boss, 100, 0, 1000000, },
{ "item_rate_common_mvp", &battle_config.item_rate_common_mvp, 100, 0, 1000000, },
{ "item_rate_equip", &battle_config.item_rate_equip, 100, 0, 1000000, },
{ "item_rate_equip_boss", &battle_config.item_rate_equip_boss, 100, 0, 1000000, },
{ "item_rate_equip_mvp", &battle_config.item_rate_equip_mvp, 100, 0, 1000000, },
{ "item_rate_card", &battle_config.item_rate_card, 100, 0, 1000000, },
{ "item_rate_card_boss", &battle_config.item_rate_card_boss, 100, 0, 1000000, },
{ "item_rate_card_mvp", &battle_config.item_rate_card_mvp, 100, 0, 1000000, },
{ "item_rate_heal", &battle_config.item_rate_heal, 100, 0, 1000000, },
{ "item_rate_heal_boss", &battle_config.item_rate_heal_boss, 100, 0, 1000000, },
{ "item_rate_heal_mvp", &battle_config.item_rate_heal_mvp, 100, 0, 1000000, },
{ "item_rate_use", &battle_config.item_rate_use, 100, 0, 1000000, },
{ "item_rate_use_boss", &battle_config.item_rate_use_boss, 100, 0, 1000000, },
{ "item_rate_use_mvp", &battle_config.item_rate_use_mvp, 100, 0, 1000000, },
{ "item_rate_adddrop", &battle_config.item_rate_adddrop, 100, 0, 1000000, },
{ "item_rate_treasure", &battle_config.item_rate_treasure, 100, 0, 1000000, },
{ "prevent_logout", &battle_config.prevent_logout, 10000, 0, 60000, },
{ "prevent_logout_trigger", &battle_config.prevent_logout_trigger, 0xE, 0, 0xF, },
{ "alchemist_summon_reward", &battle_config.alchemist_summon_reward, 1, 0, 2, },
{ "drops_by_luk", &battle_config.drops_by_luk, 0, 0, INT_MAX, },
{ "drops_by_luk2", &battle_config.drops_by_luk2, 0, 0, INT_MAX, },
{ "equip_natural_break_rate", &battle_config.equip_natural_break_rate, 0, 0, INT_MAX, },
{ "equip_self_break_rate", &battle_config.equip_self_break_rate, 100, 0, INT_MAX, },
{ "equip_skill_break_rate", &battle_config.equip_skill_break_rate, 100, 0, INT_MAX, },
{ "pk_mode", &battle_config.pk_mode, 0, 0, 2, },
{ "pk_mode_mes", &battle_config.pk_mode_mes, 1, 0, 1, },
{ "pk_level_range", &battle_config.pk_level_range, 0, 0, INT_MAX, },
{ "manner_system", &battle_config.manner_system, 0xFFF, 0, 0xFFF, },
{ "pet_equip_required", &battle_config.pet_equip_required, 0, 0, 1, },
{ "multi_level_up", &battle_config.multi_level_up, 0, 0, 1, },
{ "multi_level_up_base", &battle_config.multi_level_up_base, 0, 0, MAX_LEVEL, },
{ "multi_level_up_job", &battle_config.multi_level_up_job, 0, 0, MAX_LEVEL, },
{ "max_exp_gain_rate", &battle_config.max_exp_gain_rate, 0, 0, INT_MAX, },
{ "backstab_bow_penalty", &battle_config.backstab_bow_penalty, 0, 0, 1, },
{ "night_at_start", &battle_config.night_at_start, 0, 0, 1, },
{ "show_mob_info", &battle_config.show_mob_info, 0, 0, 1|2|4, },
{ "ban_hack_trade", &battle_config.ban_hack_trade, 0, 0, INT_MAX, },
{ "min_hair_style", &battle_config.min_hair_style, 0, 0, INT_MAX, },
{ "max_hair_style", &battle_config.max_hair_style, 23, 0, INT_MAX, },
{ "min_hair_color", &battle_config.min_hair_color, 0, 0, INT_MAX, },
{ "max_hair_color", &battle_config.max_hair_color, 9, 0, INT_MAX, },
{ "min_cloth_color", &battle_config.min_cloth_color, 0, 0, INT_MAX, },
{ "max_cloth_color", &battle_config.max_cloth_color, 4, 0, INT_MAX, },
{ "pet_hair_style", &battle_config.pet_hair_style, 100, 0, INT_MAX, },
{ "castrate_dex_scale", &battle_config.castrate_dex_scale, 150, 1, INT_MAX, },
{ "vcast_stat_scale", &battle_config.vcast_stat_scale, 530, 1, INT_MAX, },
{ "area_size", &battle_config.area_size, 14, 0, INT_MAX, },
{ "zeny_from_mobs", &battle_config.zeny_from_mobs, 0, 0, 1, },
{ "mobs_level_up", &battle_config.mobs_level_up, 0, 0, 1, },
{ "mobs_level_up_exp_rate", &battle_config.mobs_level_up_exp_rate, 1, 1, INT_MAX, },
{ "pk_min_level", &battle_config.pk_min_level, 55, 1, INT_MAX, },
{ "skill_steal_max_tries", &battle_config.skill_steal_max_tries, 0, 0, UCHAR_MAX, },
{ "motd_type", &battle_config.motd_type, 0, 0, 1, },
{ "finding_ore_rate", &battle_config.finding_ore_rate, 100, 0, INT_MAX, },
{ "exp_calc_type", &battle_config.exp_calc_type, 0, 0, 1, },
{ "exp_bonus_attacker", &battle_config.exp_bonus_attacker, 25, 0, INT_MAX, },
{ "exp_bonus_max_attacker", &battle_config.exp_bonus_max_attacker, 12, 2, INT_MAX, },
{ "min_skill_delay_limit", &battle_config.min_skill_delay_limit, 100, 10, INT_MAX, },
{ "default_walk_delay", &battle_config.default_walk_delay, 300, 0, INT_MAX, },
{ "no_skill_delay", &battle_config.no_skill_delay, BL_MOB, BL_NUL, BL_ALL, },
{ "attack_walk_delay", &battle_config.attack_walk_delay, BL_ALL, BL_NUL, BL_ALL, },
{ "require_glory_guild", &battle_config.require_glory_guild, 0, 0, 1, },
{ "idle_no_share", &battle_config.idle_no_share, 0, 0, INT_MAX, },
{ "party_even_share_bonus", &battle_config.party_even_share_bonus, 0, 0, INT_MAX, },
{ "delay_battle_damage", &battle_config.delay_battle_damage, 1, 0, 1, },
{ "hide_woe_damage", &battle_config.hide_woe_damage, 0, 0, 1, },
{ "display_version", &battle_config.display_version, 1, 0, 1, },
{ "display_hallucination", &battle_config.display_hallucination, 1, 0, 1, },
{ "use_statpoint_table", &battle_config.use_statpoint_table, 1, 0, 1, },
{ "debuff_on_logout", &battle_config.debuff_on_logout, 0, 0, 1|2, },
{ "monster_ai", &battle_config.mob_ai, 0x000, 0x000, 0xFFF, },
{ "hom_setting", &battle_config.hom_setting, 0xFFFF, 0x0000, 0xFFFF, },
{ "dynamic_mobs", &battle_config.dynamic_mobs, 1, 0, 1, },
{ "mob_remove_damaged", &battle_config.mob_remove_damaged, 1, 0, 1, },
{ "show_hp_sp_drain", &battle_config.show_hp_sp_drain, 0, 0, 1, },
{ "show_hp_sp_gain", &battle_config.show_hp_sp_gain, 1, 0, 1, },
{ "mob_npc_event_type", &battle_config.mob_npc_event_type, 1, 0, 1, },
{ "character_size", &battle_config.character_size, 1|2, 0, 1|2, },
{ "mob_max_skilllvl", &battle_config.mob_max_skilllvl, MAX_MOBSKILL_LEVEL, 1, MAX_MOBSKILL_LEVEL, },
{ "retaliate_to_master", &battle_config.retaliate_to_master, 1, 0, 1, },
{ "rare_drop_announce", &battle_config.rare_drop_announce, 0, 0, 10000, },
{ "drop_rate_cap", &battle_config.drop_rate_cap, 9000, 0, 10000, },
{ "drop_rate_cap_vip", &battle_config.drop_rate_cap_vip, 9000, 0, 10000, },
{ "duel_allow_pvp", &battle_config.duel_allow_pvp, 0, 0, 1, },
{ "duel_allow_gvg", &battle_config.duel_allow_gvg, 0, 0, 1, },
{ "duel_allow_teleport", &battle_config.duel_allow_teleport, 0, 0, 1, },
{ "duel_autoleave_when_die", &battle_config.duel_autoleave_when_die, 1, 0, 1, },
{ "duel_time_interval", &battle_config.duel_time_interval, 60, 0, INT_MAX, },
{ "duel_only_on_same_map", &battle_config.duel_only_on_same_map, 0, 0, 1, },
{ "skip_teleport_lv1_menu", &battle_config.skip_teleport_lv1_menu, 0, 0, 1, },
{ "allow_skill_without_day", &battle_config.allow_skill_without_day, 0, 0, 1, },
{ "allow_es_magic_player", &battle_config.allow_es_magic_pc, 0, 0, 1, },
{ "skill_caster_check", &battle_config.skill_caster_check, 1, 0, 1, },
{ "status_cast_cancel", &battle_config.sc_castcancel, BL_NUL, BL_NUL, BL_ALL, },
{ "pc_status_def_rate", &battle_config.pc_sc_def_rate, 100, 0, INT_MAX, },
{ "mob_status_def_rate", &battle_config.mob_sc_def_rate, 100, 0, INT_MAX, },
{ "pc_max_status_def", &battle_config.pc_max_sc_def, 100, 0, INT_MAX, },
{ "mob_max_status_def", &battle_config.mob_max_sc_def, 100, 0, INT_MAX, },
{ "sg_miracle_skill_ratio", &battle_config.sg_miracle_skill_ratio, 1, 0, 10000, },
{ "sg_angel_skill_ratio", &battle_config.sg_angel_skill_ratio, 10, 0, 10000, },
{ "autospell_stacking", &battle_config.autospell_stacking, 0, 0, 1, },
{ "override_mob_names", &battle_config.override_mob_names, 0, 0, 2, },
{ "min_chat_delay", &battle_config.min_chat_delay, 0, 0, INT_MAX, },
{ "friend_auto_add", &battle_config.friend_auto_add, 1, 0, 1, },
{ "hom_rename", &battle_config.hom_rename, 0, 0, 1, },
{ "homunculus_show_growth", &battle_config.homunculus_show_growth, 0, 0, 1, },
{ "homunculus_friendly_rate", &battle_config.homunculus_friendly_rate, 100, 0, INT_MAX, },
{ "vending_tax", &battle_config.vending_tax, 0, 0, 10000, },
{ "vending_tax_min", &battle_config.vending_tax_min, 0, 0, MAX_ZENY, },
{ "day_duration", &battle_config.day_duration, 0, 0, INT_MAX, },
{ "night_duration", &battle_config.night_duration, 0, 0, INT_MAX, },
{ "mob_remove_delay", &battle_config.mob_remove_delay, 60000, 1000, INT_MAX, },
{ "mob_active_time", &battle_config.mob_active_time, 0, 0, INT_MAX, },
{ "boss_active_time", &battle_config.boss_active_time, 0, 0, INT_MAX, },
{ "sg_miracle_skill_duration", &battle_config.sg_miracle_skill_duration, 3600000, 0, INT_MAX, },
{ "hvan_explosion_intimate", &battle_config.hvan_explosion_intimate, 45000, 0, 100000, },
{ "quest_exp_rate", &battle_config.quest_exp_rate, 100, 0, INT_MAX, },
{ "at_mapflag", &battle_config.autotrade_mapflag, 0, 0, 1, },
{ "at_timeout", &battle_config.at_timeout, 0, 0, INT_MAX, },
{ "homunculus_autoloot", &battle_config.homunculus_autoloot, 0, 0, 1, },
{ "idle_no_autoloot", &battle_config.idle_no_autoloot, 0, 0, INT_MAX, },
{ "max_guild_alliance", &battle_config.max_guild_alliance, 3, 0, 3, },
{ "ksprotection", &battle_config.ksprotection, 5000, 0, INT_MAX, },
{ "auction_feeperhour", &battle_config.auction_feeperhour, 12000, 0, INT_MAX, },
{ "auction_maximumprice", &battle_config.auction_maximumprice, 500000000, 0, MAX_ZENY, },
{ "homunculus_auto_vapor", &battle_config.homunculus_auto_vapor, 80, 0, 100, },
{ "display_status_timers", &battle_config.display_status_timers, 1, 0, 1, },
{ "skill_add_heal_rate", &battle_config.skill_add_heal_rate, 487, 0, INT_MAX, },
{ "eq_single_target_reflectable", &battle_config.eq_single_target_reflectable, 1, 0, 1, },
{ "invincible.nodamage", &battle_config.invincible_nodamage, 0, 0, 1, },
{ "mob_slave_keep_target", &battle_config.mob_slave_keep_target, 0, 0, 1, },
{ "autospell_check_range", &battle_config.autospell_check_range, 0, 0, 1, },
{ "knockback_left", &battle_config.knockback_left, 1, 0, 1, },
{ "client_reshuffle_dice", &battle_config.client_reshuffle_dice, 0, 0, 1, },
{ "client_sort_storage", &battle_config.client_sort_storage, 0, 0, 1, },
{ "feature.buying_store", &battle_config.feature_buying_store, 1, 0, 1, },
{ "feature.search_stores", &battle_config.feature_search_stores, 1, 0, 1, },
{ "searchstore_querydelay", &battle_config.searchstore_querydelay, 10, 0, INT_MAX, },
{ "searchstore_maxresults", &battle_config.searchstore_maxresults, 30, 1, INT_MAX, },
{ "display_party_name", &battle_config.display_party_name, 0, 0, 1, },
{ "cashshop_show_points", &battle_config.cashshop_show_points, 0, 0, 1, },
{ "mail_show_status", &battle_config.mail_show_status, 0, 0, 2, },
{ "client_limit_unit_lv", &battle_config.client_limit_unit_lv, 0, 0, BL_ALL, },
{ "land_protector_behavior", &battle_config.land_protector_behavior, 0, 0, 1, },
{ "npc_emotion_behavior", &battle_config.npc_emotion_behavior, 0, 0, 1, },
// BattleGround Settings
{ "bg_update_interval", &battle_config.bg_update_interval, 1000, 100, INT_MAX, },
{ "bg_short_attack_damage_rate", &battle_config.bg_short_damage_rate, 80, 0, INT_MAX, },
{ "bg_long_attack_damage_rate", &battle_config.bg_long_damage_rate, 80, 0, INT_MAX, },
{ "bg_weapon_attack_damage_rate", &battle_config.bg_weapon_damage_rate, 60, 0, INT_MAX, },
{ "bg_magic_attack_damage_rate", &battle_config.bg_magic_damage_rate, 60, 0, INT_MAX, },
{ "bg_misc_attack_damage_rate", &battle_config.bg_misc_damage_rate, 60, 0, INT_MAX, },
{ "bg_flee_penalty", &battle_config.bg_flee_penalty, 20, 0, INT_MAX, },
// rAthena
{ "max_third_parameter", &battle_config.max_third_parameter, 135, 10, SHRT_MAX, },
{ "max_baby_third_parameter", &battle_config.max_baby_third_parameter, 108, 10, SHRT_MAX, },
{ "max_trans_parameter", &battle_config.max_trans_parameter, 99, 10, SHRT_MAX, },
{ "max_third_trans_parameter", &battle_config.max_third_trans_parameter, 135, 10, SHRT_MAX, },
{ "max_extended_parameter", &battle_config.max_extended_parameter, 125, 10, SHRT_MAX, },
{ "max_summoner_parameter", &battle_config.max_summoner_parameter, 120, 10, SHRT_MAX, },
{ "max_fourth_parameter", &battle_config.max_fourth_parameter, 135, 10, SHRT_MAX, },
{ "skill_amotion_leniency", &battle_config.skill_amotion_leniency, 0, 0, 300 },
{ "mvp_tomb_enabled", &battle_config.mvp_tomb_enabled, 1, 0, 1 },
{ "mvp_tomb_delay", &battle_config.mvp_tomb_delay, 9000, 0, INT_MAX, },
{ "feature.atcommand_suggestions", &battle_config.atcommand_suggestions_enabled, 0, 0, 1 },
{ "min_npc_vendchat_distance", &battle_config.min_npc_vendchat_distance, 3, 0, 100 },
{ "atcommand_mobinfo_type", &battle_config.atcommand_mobinfo_type, 0, 0, 1 },
{ "homunculus_max_level", &battle_config.hom_max_level, 99, 0, MAX_LEVEL, },
{ "homunculus_S_max_level", &battle_config.hom_S_max_level, 150, 0, MAX_LEVEL, },
{ "mob_size_influence", &battle_config.mob_size_influence, 0, 0, 1, },
{ "skill_trap_type", &battle_config.skill_trap_type, 0, 0, 3, },
{ "allow_consume_restricted_item", &battle_config.allow_consume_restricted_item, 1, 0, 1, },
{ "allow_equip_restricted_item", &battle_config.allow_equip_restricted_item, 1, 0, 1, },
{ "max_walk_path", &battle_config.max_walk_path, 17, 1, MAX_WALKPATH, },
#ifdef RENEWAL
{ "item_enabled_npc", &battle_config.item_enabled_npc, 0, 0, 1, },
#else
{ "item_enabled_npc", &battle_config.item_enabled_npc, 1, 0, 1, },
#endif
{ "item_flooritem_check", &battle_config.item_onfloor, 1, 0, 1, },
{ "bowling_bash_area", &battle_config.bowling_bash_area, 0, 0, 20, },
{ "drop_rateincrease", &battle_config.drop_rateincrease, 0, 0, 1, },
{ "feature.auction", &battle_config.feature_auction, 0, 0, 2, },
{ "feature.banking", &battle_config.feature_banking, 1, 0, 1, },
#ifdef VIP_ENABLE
{ "vip_storage_increase", &battle_config.vip_storage_increase, 300, 0, MAX_STORAGE-MIN_STORAGE, },
#else
{ "vip_storage_increase", &battle_config.vip_storage_increase, 300, 0, MAX_STORAGE, },
#endif
{ "vip_base_exp_increase", &battle_config.vip_base_exp_increase, 50, 0, INT_MAX, },
{ "vip_job_exp_increase", &battle_config.vip_job_exp_increase, 50, 0, INT_MAX, },
{ "vip_exp_penalty_base", &battle_config.vip_exp_penalty_base, 100, 0, INT_MAX, },
{ "vip_exp_penalty_job", &battle_config.vip_exp_penalty_job, 100, 0, INT_MAX, },
{ "vip_zeny_penalty", &battle_config.vip_zeny_penalty, 0, 0, INT_MAX, },
{ "vip_bm_increase", &battle_config.vip_bm_increase, 2, 0, INT_MAX, },
{ "vip_drop_increase", &battle_config.vip_drop_increase, 50, 0, INT_MAX, },
{ "vip_gemstone", &battle_config.vip_gemstone, 2, 0, 2, },
{ "vip_disp_rate", &battle_config.vip_disp_rate, 1, 0, 1, },
{ "mon_trans_disable_in_gvg", &battle_config.mon_trans_disable_in_gvg, 0, 0, 1, },
{ "homunculus_S_growth_level", &battle_config.hom_S_growth_level, 99, 0, MAX_LEVEL, },
{ "emblem_woe_change", &battle_config.emblem_woe_change, 0, 0, 1, },
{ "emblem_transparency_limit", &battle_config.emblem_transparency_limit, 80, 0, 100, },
{ "discount_item_point_shop", &battle_config.discount_item_point_shop, 0, 0, 3, },
{ "update_enemy_position", &battle_config.update_enemy_position, 0, 0, 1, },
{ "devotion_rdamage", &battle_config.devotion_rdamage, 0, 0, 100, },
{ "feature.autotrade", &battle_config.feature_autotrade, 1, 0, 1, },
{ "feature.autotrade_direction", &battle_config.feature_autotrade_direction, 4, -1, 7, },
{ "feature.autotrade_head_direction", &battle_config.feature_autotrade_head_direction,0, -1, 2, },
{ "feature.autotrade_sit", &battle_config.feature_autotrade_sit, 1, -1, 1, },
{ "feature.autotrade_open_delay", &battle_config.feature_autotrade_open_delay, 5000, 1000, INT_MAX, },
{ "disp_servervip_msg", &battle_config.disp_servervip_msg, 0, 0, 1, },
{ "warg_can_falcon", &battle_config.warg_can_falcon, 0, 0, 1, },
{ "path_blown_halt", &battle_config.path_blown_halt, 1, 0, 1, },
{ "rental_mount_speed_boost", &battle_config.rental_mount_speed_boost, 25, 0, 100, },
{ "feature.warp_suggestions", &battle_config.warp_suggestions_enabled, 0, 0, 1, },
{ "taekwon_mission_mobname", &battle_config.taekwon_mission_mobname, 0, 0, 2, },
{ "teleport_on_portal", &battle_config.teleport_on_portal, 0, 0, 1, },
{ "cart_revo_knockback", &battle_config.cart_revo_knockback, 1, 0, 1, },
{ "guild_notice_changemap", &battle_config.guild_notice_changemap, 2, 0, 2, },
{ "transcendent_status_points", &battle_config.transcendent_status_points, 52, 1, INT_MAX, },
{ "taekwon_ranker_min_lv", &battle_config.taekwon_ranker_min_lv, 90, 1, MAX_LEVEL, },
{ "revive_onwarp", &battle_config.revive_onwarp, 1, 0, 1, },
{ "fame_taekwon_mission", &battle_config.fame_taekwon_mission, 1, 0, INT_MAX, },
{ "fame_refine_lv1", &battle_config.fame_refine_lv1, 1, 0, INT_MAX, },
{ "fame_refine_lv1", &battle_config.fame_refine_lv1, 1, 0, INT_MAX, },
{ "fame_refine_lv2", &battle_config.fame_refine_lv2, 25, 0, INT_MAX, },
{ "fame_refine_lv3", &battle_config.fame_refine_lv3, 1000, 0, INT_MAX, },
{ "fame_forge", &battle_config.fame_forge, 10, 0, INT_MAX, },
{ "fame_pharmacy_3", &battle_config.fame_pharmacy_3, 1, 0, INT_MAX, },
{ "fame_pharmacy_5", &battle_config.fame_pharmacy_5, 3, 0, INT_MAX, },
{ "fame_pharmacy_7", &battle_config.fame_pharmacy_7, 10, 0, INT_MAX, },
{ "fame_pharmacy_10", &battle_config.fame_pharmacy_10, 50, 0, INT_MAX, },
{ "mail_delay", &battle_config.mail_delay, 1000, 1000, INT_MAX, },
{ "at_monsterignore", &battle_config.autotrade_monsterignore, 0, 0, 1, },
{ "idletime_option", &battle_config.idletime_option, 0x7C1F, 1, 0xFFFF, },
{ "spawn_direction", &battle_config.spawn_direction, 0, 0, 1, },
{ "arrow_shower_knockback", &battle_config.arrow_shower_knockback, 1, 0, 1, },
{ "devotion_rdamage_skill_only", &battle_config.devotion_rdamage_skill_only, 1, 0, 1, },
{ "max_extended_aspd", &battle_config.max_extended_aspd, 193, 100, 199, },
{ "monster_chase_refresh", &battle_config.mob_chase_refresh, 3, 0, 30, },
{ "mob_icewall_walk_block", &battle_config.mob_icewall_walk_block, 75, 0, 255, },
{ "boss_icewall_walk_block", &battle_config.boss_icewall_walk_block, 0, 0, 255, },
{ "snap_dodge", &battle_config.snap_dodge, 0, 0, 1, },
{ "stormgust_knockback", &battle_config.stormgust_knockback, 1, 0, 1, },
{ "default_fixed_castrate", &battle_config.default_fixed_castrate, 20, 0, 100, },
{ "default_bind_on_equip", &battle_config.default_bind_on_equip, BOUND_CHAR, BOUND_NONE, BOUND_MAX-1, },
{ "pet_ignore_infinite_def", &battle_config.pet_ignore_infinite_def, 0, 0, 1, },
{ "homunculus_evo_intimacy_need", &battle_config.homunculus_evo_intimacy_need, 91100, 0, INT_MAX, },
{ "homunculus_evo_intimacy_reset", &battle_config.homunculus_evo_intimacy_reset, 1000, 0, INT_MAX, },
{ "monster_loot_search_type", &battle_config.monster_loot_search_type, 1, 0, 1, },
{ "feature.roulette", &battle_config.feature_roulette, 1, 0, 1, },
{ "monster_hp_bars_info", &battle_config.monster_hp_bars_info, 1, 0, 1, },
{ "min_body_style", &battle_config.min_body_style, 0, 0, SHRT_MAX, },
{ "max_body_style", &battle_config.max_body_style, 1, 0, SHRT_MAX, },
{ "save_body_style", &battle_config.save_body_style, 1, 0, 1, },
{ "monster_eye_range_bonus", &battle_config.mob_eye_range_bonus, 0, 0, 10, },
{ "monster_stuck_warning", &battle_config.mob_stuck_warning, 0, 0, 1, },
{ "skill_eightpath_algorithm", &battle_config.skill_eightpath_algorithm, 1, 0, 1, },
{ "skill_eightpath_same_cell", &battle_config.skill_eightpath_same_cell, 1, 0, 1, },
{ "death_penalty_maxlv", &battle_config.death_penalty_maxlv, 0, 0, 3, },
{ "exp_cost_redemptio", &battle_config.exp_cost_redemptio, 1, 0, 100, },
{ "exp_cost_redemptio_limit", &battle_config.exp_cost_redemptio_limit, 5, 0, MAX_PARTY, },
{ "mvp_exp_reward_message", &battle_config.mvp_exp_reward_message, 0, 0, 1, },
{ "can_damage_skill", &battle_config.can_damage_skill, 1, 0, BL_ALL, },
{ "atcommand_levelup_events", &battle_config.atcommand_levelup_events, 0, 0, 1, },
{ "atcommand_disable_npc", &battle_config.atcommand_disable_npc, 1, 0, 1, },
{ "block_account_in_same_party", &battle_config.block_account_in_same_party, 1, 0, 1, },
{ "tarotcard_equal_chance", &battle_config.tarotcard_equal_chance, 0, 0, 1, },
{ "change_party_leader_samemap", &battle_config.change_party_leader_samemap, 1, 0, 1, },
{ "dispel_song", &battle_config.dispel_song, 0, 0, 1, },
{ "guild_maprespawn_clones", &battle_config.guild_maprespawn_clones, 0, 0, 1, },
{ "hide_fav_sell", &battle_config.hide_fav_sell, 0, 0, 1, },
{ "mail_daily_count", &battle_config.mail_daily_count, 100, 0, INT32_MAX, },
{ "mail_zeny_fee", &battle_config.mail_zeny_fee, 2, 0, 100, },
{ "mail_attachment_price", &battle_config.mail_attachment_price, 2500, 0, INT32_MAX, },
{ "mail_attachment_weight", &battle_config.mail_attachment_weight, 2000, 0, INT32_MAX, },
{ "banana_bomb_duration", &battle_config.banana_bomb_duration, 0, 0, UINT16_MAX, },
{ "guild_leaderchange_delay", &battle_config.guild_leaderchange_delay, 1440, 0, INT32_MAX, },
{ "guild_leaderchange_woe", &battle_config.guild_leaderchange_woe, 0, 0, 1, },
{ "guild_alliance_onlygm", &battle_config.guild_alliance_onlygm, 0, 0, 1, },
{ "feature.achievement", &battle_config.feature_achievement, 1, 0, 1, },
{ "allow_bound_sell", &battle_config.allow_bound_sell, 0, 0, 0xF, },
{ "autoloot_adjust", &battle_config.autoloot_adjust, 0, 0, 1, },
{ "feature.petevolution", &battle_config.feature_petevolution, 1, 0, 1, },
{ "feature.petautofeed", &battle_config.feature_pet_autofeed, 1, 0, 1, },
{ "feature.pet_autofeed_rate", &battle_config.feature_pet_autofeed_rate, 89, 0, 100, },
{ "pet_autofeed_always", &battle_config.pet_autofeed_always, 1, 0, 1, },
{ "broadcast_hide_name", &battle_config.broadcast_hide_name, 2, 0, NAME_LENGTH, },
{ "skill_drop_items_full", &battle_config.skill_drop_items_full, 0, 0, 1, },
{ "switch_remove_edp", &battle_config.switch_remove_edp, 2, 0, 3, },
{ "feature.homunculus_autofeed", &battle_config.feature_homunculus_autofeed, 1, 0, 1, },
{ "feature.homunculus_autofeed_rate", &battle_config.feature_homunculus_autofeed_rate,30, 0, 100, },
{ "summoner_race", &battle_config.summoner_race, RC_PLAYER_DORAM, RC_FORMLESS, RC_PLAYER_DORAM, },
{ "summoner_size", &battle_config.summoner_size, SZ_SMALL, SZ_SMALL, SZ_BIG, },
{ "homunculus_autofeed_always", &battle_config.homunculus_autofeed_always, 1, 0, 1, },
{ "feature.attendance", &battle_config.feature_attendance, 1, 0, 1, },
{ "feature.privateairship", &battle_config.feature_privateairship, 1, 0, 1, },
{ "rental_transaction", &battle_config.rental_transaction, 1, 0, 1, },
{ "min_shop_buy", &battle_config.min_shop_buy, 1, 0, INT_MAX, },
{ "min_shop_sell", &battle_config.min_shop_sell, 0, 0, INT_MAX, },
{ "feature.equipswitch", &battle_config.feature_equipswitch, 1, 0, 1, },
{ "pet_walk_speed", &battle_config.pet_walk_speed, 1, 1, 3, },
{ "blacksmith_fame_refine_threshold", &battle_config.blacksmith_fame_refine_threshold,10, 1, MAX_REFINE, },
{ "mob_nopc_idleskill_rate", &battle_config.mob_nopc_idleskill_rate, 100, 0, 100, },
{ "mob_nopc_move_rate", &battle_config.mob_nopc_move_rate, 100, 0, 100, },
{ "boss_nopc_idleskill_rate", &battle_config.boss_nopc_idleskill_rate, 100, 0, 100, },
{ "boss_nopc_move_rate", &battle_config.boss_nopc_move_rate, 100, 0, 100, },
{ "hom_idle_no_share", &battle_config.hom_idle_no_share, 0, 0, INT_MAX, },
{ "idletime_hom_option", &battle_config.idletime_hom_option, 0x1F, 0x1, 0xFFF, },
{ "devotion_standup_fix", &battle_config.devotion_standup_fix, 1, 0, 1, },
{ "feature.bgqueue", &battle_config.feature_bgqueue, 1, 0, 1, },
{ "bgqueue_nowarp_mapflag", &battle_config.bgqueue_nowarp_mapflag, 0, 0, 1, },
{ "homunculus_exp_gain", &battle_config.homunculus_exp_gain, 10, 0, 100, },
{ "rental_item_novalue", &battle_config.rental_item_novalue, 1, 0, 1, },
{ "ping_timer_inverval", &battle_config.ping_timer_interval, 30, 0, 99999999, },
{ "ping_time", &battle_config.ping_time, 20, 0, 99999999, },
{ "show_skill_scale", &battle_config.show_skill_scale, 1, 0, 1, },
{ "achievement_mob_share", &battle_config.achievement_mob_share, 0, 0, 1, },
{ "slave_stick_with_master", &battle_config.slave_stick_with_master, 0, 0, 1, },
{ "at_logout_event", &battle_config.at_logout_event, 1, 0, 1, },
{ "homunculus_starving_rate", &battle_config.homunculus_starving_rate, 10, 0, 100, },
{ "homunculus_starving_delay", &battle_config.homunculus_starving_delay, 20000, 0, INT_MAX, },
{ "drop_connection_on_quit", &battle_config.drop_connection_on_quit, 0, 0, 1, },
{ "mob_spawn_variance", &battle_config.mob_spawn_variance, 1, 0, 3, },
{ "mercenary_autoloot", &battle_config.mercenary_autoloot, 0, 0, 1, },
{ "mer_idle_no_share" , &battle_config.mer_idle_no_share, 0, 0, INT_MAX, },
{ "idletime_mer_option", &battle_config.idletime_mer_option, 0x1F, 0x1, 0xFFF, },
{ "feature.refineui", &battle_config.feature_refineui, 1, 0, 1, },
{ "rndopt_drop_pillar", &battle_config.rndopt_drop_pillar, 1, 0, 1, },
{ "pet_legacy_formula", &battle_config.pet_legacy_formula, 0, 0, 1, },
{ "pet_distance_check", &battle_config.pet_distance_check, 5, 0, 50, },
{ "pet_hide_check", &battle_config.pet_hide_check, 1, 0, 1, },
// 4th Job Stuff
{ "use_traitpoint_table", &battle_config.use_traitpoint_table, 1, 0, 1, },
{ "trait_points_job_change", &battle_config.trait_points_job_change, 7, 1, 1000, },
{ "max_trait_parameter", &battle_config.max_trait_parameter, 100, 10, SHRT_MAX, },
{ "max_res_mres_reduction", &battle_config.max_res_mres_reduction, 625, 1, SHRT_MAX, },
{ "max_ap", &battle_config.max_ap, 200, 100, 1000000000, },
{ "ap_rate", &battle_config.ap_rate, 100, 1, INT_MAX, },
{ "restart_ap_rate", &battle_config.restart_ap_rate, 0, 0, 100, },
{ "loose_ap_on_death", &battle_config.loose_ap_on_death, 1, 0, 1, },
{ "loose_ap_on_map", &battle_config.loose_ap_on_map, 1, 0, 1, },
{ "keep_ap_on_logout", &battle_config.keep_ap_on_logout, 1, 0, 1, },
{ "attack_machine_level_difference", &battle_config.attack_machine_level_difference, 15, 0, INT_MAX, },
{ "feature.barter", &battle_config.feature_barter, 1, 0, 1, },
{ "feature.barter_extended", &battle_config.feature_barter_extended, 1, 0, 1, },
#include "../custom/battle_config_init.inc"
};
/*==========================
* Set battle settings
*--------------------------*/
int battle_set_value(const char* w1, const char* w2)
{
int val = config_switch(w2);
int i;
ARR_FIND(0, ARRAYLENGTH(battle_data), i, strcmpi(w1, battle_data[i].str) == 0);
if (i == ARRAYLENGTH(battle_data))
return 0; // not found
if (val < battle_data[i].min || val > battle_data[i].max) {
ShowWarning("Value for setting '%s': %s is invalid (min:%i max:%i)! Defaulting to %i...\n", w1, w2, battle_data[i].min, battle_data[i].max, battle_data[i].defval);
val = battle_data[i].defval;
}
*battle_data[i].val = val;
return 1;
}
/*===========================
* Get battle settings
*---------------------------*/
int battle_get_value(const char* w1)
{
int i;
ARR_FIND(0, ARRAYLENGTH(battle_data), i, strcmpi(w1, battle_data[i].str) == 0);
if (i == ARRAYLENGTH(battle_data))
return 0; // not found
else
return *battle_data[i].val;
}
/*======================
* Set default settings
*----------------------*/
void battle_set_defaults()
{
int i;
for (i = 0; i < ARRAYLENGTH(battle_data); i++)
*battle_data[i].val = battle_data[i].defval;
}
/*==================================
* Cap certain battle.conf settings
*----------------------------------*/
void battle_adjust_conf()
{
battle_config.monster_max_aspd = 2000 - battle_config.monster_max_aspd * 10;
battle_config.max_aspd = 2000 - battle_config.max_aspd * 10;
battle_config.max_third_aspd = 2000 - battle_config.max_third_aspd * 10;
battle_config.max_summoner_aspd = 2000 - battle_config.max_summoner_aspd * 10;
battle_config.max_extended_aspd = 2000 - battle_config.max_extended_aspd * 10;
battle_config.max_walk_speed = 100 * DEFAULT_WALK_SPEED / battle_config.max_walk_speed;
battle_config.max_cart_weight *= 10;
if (battle_config.max_def > 100 && !battle_config.weapon_defense_type) // added by [Skotlex]
battle_config.max_def = 100;
if (battle_config.min_hitrate > battle_config.max_hitrate)
battle_config.min_hitrate = battle_config.max_hitrate;
if (battle_config.pet_max_atk1 > battle_config.pet_max_atk2) //Skotlex
battle_config.pet_max_atk1 = battle_config.pet_max_atk2;
if (battle_config.day_duration && battle_config.day_duration < 60000) // added by [Yor]
battle_config.day_duration = 60000;
if (battle_config.night_duration && battle_config.night_duration < 60000) // added by [Yor]
battle_config.night_duration = 60000;
#if PACKETVER < 20100427
if (battle_config.feature_buying_store) {
ShowWarning("conf/battle/feature.conf:buying_store is enabled but it requires PACKETVER 2010-04-27 or newer, disabling...\n");
battle_config.feature_buying_store = 0;
}
#endif
#if PACKETVER < 20100803
if (battle_config.feature_search_stores) {
ShowWarning("conf/battle/feature.conf:search_stores is enabled but it requires PACKETVER 2010-08-03 or newer, disabling...\n");
battle_config.feature_search_stores = 0;
}
#endif
#if PACKETVER < 20120101
if (battle_config.feature_bgqueue) {
ShowWarning("conf/battle/feature.conf:bgqueue is enabled but it requires PACKETVER 2012-01-01 or newer, disabling...\n");
battle_config.feature_bgqueue = 0;
}
#endif
#if PACKETVER > 20120000 && PACKETVER < 20130515 /* Exact date (when it started) not known */
if (battle_config.feature_auction) {
ShowWarning("conf/battle/feature.conf:feature.auction is enabled but it is not stable on PACKETVER " EXPAND_AND_QUOTE(PACKETVER) ", disabling...\n");
ShowWarning("conf/battle/feature.conf:feature.auction change value to '2' to silence this warning and maintain it enabled\n");
battle_config.feature_auction = 0;
}
#elif PACKETVER >= 20141112
if (battle_config.feature_auction) {
ShowWarning("conf/battle/feature.conf:feature.auction is enabled but it is not available for clients from 2014-11-12 on, disabling...\n");
ShowWarning("conf/battle/feature.conf:feature.auction change value to '2' to silence this warning and maintain it enabled\n");
battle_config.feature_auction = 0;
}
#endif
#if PACKETVER < 20130724
if (battle_config.feature_banking) {
ShowWarning("conf/battle/feature.conf banking is enabled but it requires PACKETVER 2013-07-24 or newer, disabling...\n");
battle_config.feature_banking = 0;
}
#endif
#if PACKETVER < 20131223
if (battle_config.mvp_exp_reward_message) {
ShowWarning("conf/battle/client.conf MVP EXP reward message is enabled but it requires PACKETVER 2013-12-23 or newer, disabling...\n");
battle_config.mvp_exp_reward_message = 0;
}
#endif
#if PACKETVER < 20141022
if (battle_config.feature_roulette) {
ShowWarning("conf/battle/feature.conf roulette is enabled but it requires PACKETVER 2014-10-22 or newer, disabling...\n");
battle_config.feature_roulette = 0;
}
#endif
#if PACKETVER < 20150513
if (battle_config.feature_achievement) {
ShowWarning("conf/battle/feature.conf achievement is enabled but it requires PACKETVER 2015-05-13 or newer, disabling...\n");
battle_config.feature_achievement = 0;
}
#endif
#if PACKETVER < 20141008
if (battle_config.feature_petevolution) {
ShowWarning("conf/battle/feature.conf petevolution is enabled but it requires PACKETVER 2014-10-08 or newer, disabling...\n");
battle_config.feature_petevolution = 0;
}
if (battle_config.feature_pet_autofeed) {
ShowWarning("conf/battle/feature.conf pet auto feed is enabled but it requires PACKETVER 2014-10-08 or newer, disabling...\n");
battle_config.feature_pet_autofeed = 0;
}
#endif
#if PACKETVER < 20161012
if (battle_config.feature_refineui) {
ShowWarning("conf/battle/feature.conf refine UI is enabled but it requires PACKETVER 2016-10-12 or newer, disabling...\n");
battle_config.feature_refineui = 0;
}
#endif
#if PACKETVER < 20170208
if (battle_config.feature_equipswitch) {
ShowWarning("conf/battle/feature.conf equip switch is enabled but it requires PACKETVER 2017-02-08 or newer, disabling...\n");
battle_config.feature_equipswitch = 0;
}
#endif
#if PACKETVER < 20170920
if( battle_config.feature_homunculus_autofeed ){
ShowWarning("conf/battle/feature.conf homunculus autofeeding is enabled but it requires PACKETVER 2017-09-20 or newer, disabling...\n");
battle_config.feature_homunculus_autofeed = 0;
}
#endif
#if PACKETVER < 20180307
if( battle_config.feature_attendance ){
ShowWarning("conf/battle/feature.conf attendance system is enabled but it requires PACKETVER 2018-03-07 or newer, disabling...\n");
battle_config.feature_attendance = 0;
}
#endif
#if PACKETVER < 20180321
if( battle_config.feature_privateairship ){
ShowWarning("conf/battle/feature.conf private airship system is enabled but it requires PACKETVER 2018-03-21 or newer, disabling...\n");
battle_config.feature_privateairship = 0;
}
#endif
#if !( PACKETVER_MAIN_NUM >= 20190116 || PACKETVER_RE_NUM >= 20190116 || PACKETVER_ZERO_NUM >= 20181226 )
if( battle_config.feature_barter ){
ShowWarning("conf/battle/feature.conf barter shop system is enabled but it requires PACKETVER 2019-01-16 or newer, disabling...\n");
battle_config.feature_barter = 0;
}
#endif
#if !( PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127 )
if( battle_config.feature_barter_extended ){
ShowWarning("conf/battle/feature.conf extended barter shop system is enabled but it requires PACKETVER 2019-11-06 or newer, disabling...\n");
battle_config.feature_barter_extended = 0;
}
#endif
#ifndef CELL_NOSTACK
if (battle_config.custom_cell_stack_limit != 1)
ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n");
#endif
}
/*=====================================
* Read battle.conf settings from file
*-------------------------------------*/
int battle_config_read(const char* cfgName)
{
FILE* fp;
static int count = 0;
if (count == 0)
battle_set_defaults();
count++;
fp = fopen(cfgName,"r");
if (fp == NULL)
ShowError("File not found: %s\n", cfgName);
else {
char line[1024], w1[1024], w2[1024];
while(fgets(line, sizeof(line), fp)) {
if (line[0] == '/' && line[1] == '/')
continue;
if (sscanf(line, "%1023[^:]:%1023s", w1, w2) != 2)
continue;
if (strcmpi(w1, "import") == 0)
battle_config_read(w2);
else if( strcmpi( w1, "atcommand_symbol" ) == 0 ){
const char* symbol = &w2[0];
if (ISPRINT(*symbol) && // no control characters
*symbol != '/' && // symbol of client commands
*symbol != '%' && // symbol of party chat
*symbol != '$' && // symbol of guild chat
*symbol != charcommand_symbol)
atcommand_symbol = *symbol;
}else if( strcmpi( w1, "charcommand_symbol" ) == 0 ){
const char* symbol = &w2[0];
if (ISPRINT(*symbol) && // no control characters
*symbol != '/' && // symbol of client commands
*symbol != '%' && // symbol of party chat
*symbol != '$' && // symbol of guild chat
*symbol != atcommand_symbol)
charcommand_symbol = *symbol;
}else if( battle_set_value(w1, w2) == 0 )
ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName);
}
fclose(fp);
}
count--;
if (count == 0)
battle_adjust_conf();
return 0;
}
/*==========================
* initialize battle timer
*--------------------------*/
void do_init_battle(void)
{
delay_damage_ers = ers_new(sizeof(struct delay_damage),"battle.cpp::delay_damage_ers",ERS_OPT_CLEAR);
add_timer_func_list(battle_delay_damage_sub, "battle_delay_damage_sub");
}
/*==================
* end battle timer
*------------------*/
void do_final_battle(void)
{
ers_destroy(delay_damage_ers);
}