#include "battle.hpp"
// battle.cpp - Not so scary code.
//
// Copyright © ????-2004 Athena Dev Teams
// Copyright © 2004-2011 The Mana World Development Team
// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
// Copyright © 2012 Vincent Petithory
//
// This file is part of The Mana World (Athena server)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <algorithm>
#include "../compat/nullpo.hpp"
#include "../strings/astring.hpp"
#include "../strings/zstring.hpp"
#include "../strings/xstring.hpp"
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
#include "../io/read.hpp"
#include "../io/span.hpp"
#include "../mmo/config_parse.hpp"
#include "../mmo/cxxstdio_enums.hpp"
#include "../high/utils.hpp"
#include "battle_conf.hpp"
#include "clif.hpp"
#include "globals.hpp"
#include "itemdb.hpp"
#include "map.hpp"
#include "mob.hpp"
#include "path.hpp"
#include "pc.hpp"
#include "skill.hpp"
#include "../poison.hpp"
namespace tmwa
{
namespace map
{
/*==========================================
* 自分をロックしている対象の数を返す(汎用)
* 戻りは整数で0以上
* Returns the number of targets that have locked me (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
static
int battle_counttargeted(dumb_ptr<block_list> bl, dumb_ptr<block_list> src,
ATK target_lv)
{
nullpo_retz(bl);
if (bl->bl_type == BL::PC)
return pc_counttargeted(bl->is_player(), src,
target_lv);
else if (bl->bl_type == BL::MOB)
return mob_counttargeted(bl->is_mob(), src, target_lv);
return 0;
}
/*==========================================
* 対象のClassを返す(汎用)
* 戻りは整数で0以上
* Returns the target Class (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
Species battle_get_class(dumb_ptr<block_list> bl)
{
nullpo_retr(Species(), bl);
if (bl->bl_type == BL::MOB)
return bl->is_mob()->mob_class;
else if (bl->bl_type == BL::NPC)
return bl->is_npc()->npc_class;
else if (bl->bl_type == BL::PC)
return bl->is_player()->status.species;
else
return Species();
}
/*==========================================
* 対象の方向を返す(汎用)
* 戻りは整数で0以上
* Returns the direction of the target (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
DIR battle_get_dir(dumb_ptr<block_list> bl)
{
nullpo_retr(DIR::S, bl);
if (bl->bl_type == BL::MOB)
return bl->is_mob()->dir;
else if (bl->bl_type == BL::PC)
return bl->is_player()->dir;
else
return DIR::S;
}
/*==========================================
* 対象のレベルを返す(汎用)
* 戻りは整数で0以上
* Returns target level (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_lv(dumb_ptr<block_list> bl)
{
nullpo_retz(bl);
if (bl->bl_type == BL::MOB)
return bl->is_mob()->stats[mob_stat::LV];
else if (bl->bl_type == BL::PC)
return bl->is_player()->status.base_level;
else
return 0;
}
/*==========================================
* 対象の射程を返す(汎用)
* 戻りは整数で0以上
* Returns target's range (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_range(dumb_ptr<block_list> bl)
{
nullpo_retz(bl);
if (bl->bl_type == BL::MOB)
return get_mob_db(bl->is_mob()->mob_class).range;
else if (bl->bl_type == BL::PC)
return (bl->is_player()->attack_spell_override
? bl->is_player()->attack_spell_range
: bl->is_player()->attackrange);
else
return 0;
}
/*==========================================
* 対象のHPを返す(汎用)
* 戻りは整数で0以上
* Returns target's HP (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_hp(dumb_ptr<block_list> bl)
{
nullpo_retr(1, bl);
if (bl->bl_type == BL::MOB)
return bl->is_mob()->hp;
else if (bl->bl_type == BL::PC)
return bl->is_player()->status.hp;
else
return 1;
}
/*==========================================
* 対象のMHPを返す(汎用)
* 戻りは整数で0以上
* Return the target MHP (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_max_hp(dumb_ptr<block_list> bl)
{
nullpo_retr(1, bl);
if (bl->bl_type == BL::PC)
return bl->is_player()->status.max_hp;
else
{
int max_hp = 1;
if (bl->bl_type == BL::MOB)
{
max_hp = bl->is_mob()->stats[mob_stat::MAX_HP];
{
if (battle_config.monster_hp_rate != 100)
max_hp = (max_hp * battle_config.monster_hp_rate) / 100;
}
}
if (max_hp < 1)
max_hp = 1;
return max_hp;
}
}
/*==========================================
* Return the name of the target
* Returns an string
*------------------------------------------
*/
VString<23> battle_get_name(dumb_ptr<block_list> bl)
{
VString<23> name;
nullpo_retr(name, bl);
switch (bl->bl_type)
{
case BL::PC:
name = bl->is_player()->status_key.name.to__actual();
break;
case BL::NPC:
{
name = bl->is_npc()->name;
// [fate] elim hashed out/invisible names for the client
auto it = std::find(name.begin(), name.end(), '#');
name = name.xislice_h(it);
}
break;
case BL::MOB:
name = bl->is_mob()->name;
break;
}
return name;
}
/*==========================================
* 対象のStrを返す(汎用)
* 戻りは整数で0以上
* Returns the target Str (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_str(dumb_ptr<block_list> bl)
{
int str = 0;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
str = bl->is_mob()->stats[mob_stat::STR];
else if (bl->bl_type == BL::PC)
return bl->is_player()->paramc[ATTR::STR];
if (str < 0)
str = 0;
return str;
}
/*==========================================
* 対象のAgiを返す(汎用)
* 戻りは整数で0以上
* Returns target Agi (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_agi(dumb_ptr<block_list> bl)
{
int agi = 0;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
agi = bl->is_mob()->stats[mob_stat::AGI];
else if (bl->bl_type == BL::PC)
agi = bl->is_player()->paramc[ATTR::AGI];
if (agi < 0)
agi = 0;
return agi;
}
/*==========================================
* 対象のVitを返す(汎用)
* 戻りは整数で0以上
* Returns target Vit (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_vit(dumb_ptr<block_list> bl)
{
int vit = 0;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
vit = bl->is_mob()->stats[mob_stat::VIT];
else if (bl->bl_type == BL::PC)
vit = bl->is_player()->paramc[ATTR::VIT];
if (vit < 0)
vit = 0;
return vit;
}
/*==========================================
* 対象のIntを返す(汎用)
* 戻りは整数で0以上
* Returns target Int (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_int(dumb_ptr<block_list> bl)
{
int int_ = 0;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
int_ = bl->is_mob()->stats[mob_stat::INT];
else if (bl->bl_type == BL::PC)
int_ = bl->is_player()->paramc[ATTR::INT];
if (int_ < 0)
int_ = 0;
return int_;
}
/*==========================================
* 対象のDexを返す(汎用)
* 戻りは整数で0以上
* Returns the target Dex (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_dex(dumb_ptr<block_list> bl)
{
int dex = 0;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
dex = bl->is_mob()->stats[mob_stat::DEX];
else if (bl->bl_type == BL::PC)
dex = bl->is_player()->paramc[ATTR::DEX];
if (dex < 0)
dex = 0;
return dex;
}
/*==========================================
* 対象のLukを返す(汎用)
* 戻りは整数で0以上
* Returns target Luk (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_luk(dumb_ptr<block_list> bl)
{
int luk = 0;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
luk = bl->is_mob()->stats[mob_stat::LUK];
else if (bl->bl_type == BL::PC)
luk = bl->is_player()->paramc[ATTR::LUK];
if (luk < 0)
luk = 0;
return luk;
}
/*==========================================
* 対象のFleeを返す(汎用)
* 戻りは整数で1以上
* Returns target Flee (general purpose)
* Returns an integer greater than or equal to 1
*------------------------------------------
*/
int battle_get_flee(dumb_ptr<block_list> bl)
{
int flee = 1;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retr(1, bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
flee = bl->is_player()->flee;
else
flee = battle_get_agi(bl) + battle_get_lv(bl);
if (sc_data)
{
if (battle_is_unarmed(bl))
flee += (skill_power_bl(bl, SkillID::TMW_BRAWLING) >> 3); // +25 for 200
flee += skill_power_bl(bl, SkillID::TMW_SPEED) >> 3;
}
if (flee < 1)
flee = 1;
return flee;
}
/*==========================================
* 対象のHitを返す(汎用)
* 戻りは整数で1以上
* Returns target hit (general purpose)
* Returns an integer greater than or equal to 1
*------------------------------------------
*/
int battle_get_hit(dumb_ptr<block_list> bl)
{
int hit = 1;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retr(1, bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
hit = bl->is_player()->hit;
else
hit = battle_get_dex(bl) + battle_get_lv(bl);
if (sc_data)
{
if (battle_is_unarmed(bl))
hit += (skill_power_bl(bl, SkillID::TMW_BRAWLING) >> 4); // +12 for 200
}
if (hit < 1)
hit = 1;
return hit;
}
/*==========================================
* 対象の完全回避を返す(汎用)
* 戻りは整数で1以上
* Returns full avoidance of the target (general purpose)
* Returns an integer greater than or equal to 1
*------------------------------------------
*/
int battle_get_flee2(dumb_ptr<block_list> bl)
{
int flee2 = 1;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retr(1, bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd = bl->is_player();
flee2 = battle_get_luk(bl) + 10;
flee2 += sd->flee2 - (sd->paramc[ATTR::LUK] + 10);
}
else
flee2 = battle_get_luk(bl) + 1;
{
if (battle_is_unarmed(bl))
flee2 += (skill_power_bl(bl, SkillID::TMW_BRAWLING) >> 3); // +25 for 200
flee2 += skill_power_bl(bl, SkillID::TMW_SPEED) >> 3;
}
if (flee2 < 1)
flee2 = 1;
return flee2;
}
/*==========================================
* 対象のクリティカルを返す(汎用)
* 戻りは整数で1以上
* Returns target critical (general purpose)
* Returns an integer greater than or equal to 1
*------------------------------------------
*/
int battle_get_critical(dumb_ptr<block_list> bl)
{
int critical = 1;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retr(1, bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd = bl->is_player();
critical = battle_get_luk(bl) * 2 + 10;
critical += sd->critical - ((sd->paramc[ATTR::LUK] * 3) + 10);
}
else
critical = battle_get_luk(bl) * 3 + 1;
if (critical < 1)
critical = 1;
return critical;
}
/*==========================================
* base_atkの取得
* 戻りは整数で1以上
* get base_atk
* Returns an integer greater than or equal to 1
*------------------------------------------
*/
int battle_get_baseatk(dumb_ptr<block_list> bl)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
int batk = 1;
nullpo_retr(1, bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
batk = bl->is_player()->base_atk; // 設定されているbase_atk | set base_atk
else
{ // それ以外なら | otherwise
int str, dstr;
str = battle_get_str(bl); // STR
dstr = str / 10;
batk = dstr * dstr + str; // base_atkを計算する | Calculate base_atk
}
if (batk < 1)
batk = 1; // base_atkは最低でも1 | base_atk is at least 1
return batk;
}
/*==========================================
* 対象のAtkを返す(汎用)
* 戻りは整数で0以上
* Returns target Atk (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_atk(dumb_ptr<block_list> bl)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
int atk = 0;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
atk = bl->is_player()->watk;
else if (bl->bl_type == BL::MOB)
atk = bl->is_mob()->stats[mob_stat::ATK1];
if (atk < 0)
atk = 0;
return atk;
}
/*==========================================
* 対象のAtk2を返す(汎用)
* 戻りは整数で0以上
* Returns target Atk2 (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_atk2(dumb_ptr<block_list> bl)
{
nullpo_retz(bl);
if (bl->bl_type == BL::PC)
return bl->is_player()->watk2;
else
{
int atk2 = 0;
if (bl->bl_type == BL::MOB)
atk2 = bl->is_mob()->stats[mob_stat::ATK2];
if (atk2 < 0)
atk2 = 0;
return atk2;
}
}
/*==========================================
* 対象のMAtk1を返す(汎用)
* 戻りは整数で0以上
* Returns target MAtk1 (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_matk1(dumb_ptr<block_list> bl)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::MOB)
{
int matk, int_ = battle_get_int(bl);
matk = int_ + (int_ / 5) * (int_ / 5);
return matk;
}
else if (bl->bl_type == BL::PC)
return bl->is_player()->matk1;
else
return 0;
}
/*==========================================
* 対象のMAtk2を返す(汎用)
* 戻りは整数で0以上
* Returns target MAtk2 (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_matk2(dumb_ptr<block_list> bl)
{
nullpo_retz(bl);
if (bl->bl_type == BL::MOB)
{
int matk, int_ = battle_get_int(bl);
matk = int_ + (int_ / 7) * (int_ / 7);
return matk;
}
else if (bl->bl_type == BL::PC)
return bl->is_player()->matk2;
else
return 0;
}
/*==========================================
* 対象のDefを返す(汎用)
* 戻りは整数で0以上
* Returns the target Def (general purpose)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_def(dumb_ptr<block_list> bl)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
int def = 0;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
{
def = bl->is_player()->def;
}
else if (bl->bl_type == BL::MOB)
{
def = bl->is_mob()->stats[mob_stat::DEF];
}
if (def < 1000000)
{
if (sc_data)
{
// 毒にかかっている時は減算 | Subtract when poisoned
if (sc_data[StatusChange::SC_POISON].timer
&& bl->bl_type != BL::PC)
def = def * 75 / 100;
}
}
if (def < 0)
def = 0;
return def;
}
/*==========================================
* 対象のMDefを返す(汎用)
* 戻りは整数で0以上
* Return target MDef (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_mdef(dumb_ptr<block_list> bl)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
int mdef = 0;
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
mdef = bl->is_player()->mdef;
else if (bl->bl_type == BL::MOB)
mdef = bl->is_mob()->stats[mob_stat::MDEF];
if (mdef < 1000000)
{
if (sc_data)
{
// バリアー状態時はMDEF100 | MDEF100 when in barrier state
if (mdef < 90 && sc_data[StatusChange::SC_MBARRIER].timer)
{
mdef += sc_data[StatusChange::SC_MBARRIER].val1;
if (mdef > 90)
mdef = 90;
}
}
}
if (mdef < 0)
mdef = 0;
return mdef;
}
/*==========================================
* 対象のDef2を返す(汎用)
* 戻りは整数で1以上
* Returns target Def2 (generic)
* Returns an integer greater than or equal to 1
*------------------------------------------
*/
int battle_get_def2(dumb_ptr<block_list> bl)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
int def2 = 1;
nullpo_retr(1, bl);
sc_data = battle_get_sc_data(bl);
if (bl->bl_type == BL::PC)
def2 = bl->is_player()->def2;
else if (bl->bl_type == BL::MOB)
def2 = bl->is_mob()->stats[mob_stat::VIT];
if (sc_data)
{
if (sc_data[StatusChange::SC_POISON].timer
&& bl->bl_type != BL::PC)
def2 = def2 * 75 / 100;
}
if (def2 < 1)
def2 = 1;
return def2;
}
/*==========================================
* 対象のMDef2を返す(汎用)
* 戻りは整数で0以上
* Returns target MDef2 (generic)
* Returns an integer greater than or equal to 0
*------------------------------------------
*/
int battle_get_mdef2(dumb_ptr<block_list> bl)
{
int mdef2 = 0;
nullpo_retz(bl);
if (bl->bl_type == BL::MOB)
{
dumb_ptr<mob_data> md = bl->is_mob();
mdef2 = md->stats[mob_stat::INT] + (md->stats[mob_stat::VIT] >> 1);
}
else if (bl->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd = bl->is_player();
mdef2 = sd->mdef2 + (sd->paramc[ATTR::VIT] >> 1);
}
if (mdef2 < 0)
mdef2 = 0;
return mdef2;
}
/*==========================================
* 対象のSpeed(移動速度)を返す(汎用)
* 戻りは整数で1以上
* Speedは小さいほうが移動速度が速い
* Returns the target Speed (moving speed) (general purpose)
* Returns an integer greater than or equal to 1
* The smaller the Speed, the faster the movement speed.
*------------------------------------------
*/
interval_t battle_get_speed(dumb_ptr<block_list> bl)
{
nullpo_retr(1_s, bl);
if (bl->bl_type == BL::PC)
return bl->is_player()->speed;
else
{
interval_t speed = 1_s;
if (bl->bl_type == BL::MOB)
speed = static_cast<interval_t>(bl->is_mob()->stats[mob_stat::SPEED]);
return std::max(speed, 1_ms);
}
}
/*==========================================
* 対象のaDelay(攻撃時ディレイ)を返す(汎用)
* aDelayは小さいほうが攻撃速度が速い
* Returns target's aDelay (delay when attacking) (general purpose)
* Smaller aDelay means faster attack speed
*------------------------------------------
*/
// TODO figure out what all the doubling is about
interval_t battle_get_adelay(dumb_ptr<block_list> bl)
{
nullpo_retr(4_s, bl);
if (bl->bl_type == BL::PC)
return bl->is_player()->aspd * 2;
else
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data = battle_get_sc_data(bl);
interval_t adelay = 4_s;
int aspd_rate = 100;
if (bl->bl_type == BL::MOB)
adelay = static_cast<interval_t>(bl->is_mob()->stats[mob_stat::ADELAY]);
if (sc_data)
{
if (sc_data[StatusChange::SC_SPEEDPOTION0].timer)
aspd_rate -= sc_data[StatusChange::SC_SPEEDPOTION0].val1;
// Fate's `haste' spell works the same as the above
if (sc_data[StatusChange::SC_HASTE].timer)
aspd_rate -= sc_data[StatusChange::SC_HASTE].val1;
}
if (aspd_rate != 100)
adelay = adelay * aspd_rate / 100;
return std::max(adelay, battle_config.monster_max_aspd * 2);
}
}
/*==========================================
*
*------------------------------------------
*/
interval_t battle_get_amotion(dumb_ptr<block_list> bl)
{
nullpo_retr(2_s, bl);
if (bl->bl_type == BL::PC)
return bl->is_player()->amotion;
else
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data = battle_get_sc_data(bl);
interval_t amotion = 2_s;
int aspd_rate = 100;
if (bl->bl_type == BL::MOB)
amotion = get_mob_db(bl->is_mob()->mob_class).amotion;
if (sc_data)
{
if (sc_data[StatusChange::SC_SPEEDPOTION0].timer)
aspd_rate -= sc_data[StatusChange::SC_SPEEDPOTION0].val1;
if (sc_data[StatusChange::SC_HASTE].timer)
aspd_rate -= sc_data[StatusChange::SC_HASTE].val1;
}
if (aspd_rate != 100)
amotion = amotion * aspd_rate / 100;
return std::max(amotion, battle_config.monster_max_aspd);
}
}
/*==========================================
*
*------------------------------------------
*/
interval_t battle_get_dmotion(dumb_ptr<block_list> bl)
{
nullpo_retr(interval_t::zero(), bl);
if (bl->bl_type == BL::MOB)
{
return get_mob_db(bl->is_mob()->mob_class).dmotion;
}
else if (bl->bl_type == BL::PC)
{
return bl->is_player()->dmotion;
}
else
return 2_s;
}
/*==========================================
*
*------------------------------------------
*/
LevelElement battle_get_element(dumb_ptr<block_list> bl)
{
LevelElement ret = {2, Element::neutral};
nullpo_retr(ret, bl);
if (bl->bl_type == BL::MOB) // 10の位=Lv*2、1の位=属性 | Ten's place = Lv*2, One's place = Attribute
ret = bl->is_mob()->def_ele;
return ret;
}
/*==========================================
*
*------------------------------------------
*/
PartyId battle_get_party_id(dumb_ptr<block_list> bl)
{
nullpo_retr(PartyId(), bl);
if (bl->bl_type == BL::PC)
return bl->is_player()->status.party_id;
else if (bl->bl_type == BL::MOB)
{
dumb_ptr<mob_data> md = bl->is_mob();
if (md->master_id)
return wrap<PartyId>(-unwrap<BlockId>(md->master_id));
return wrap<PartyId>(-unwrap<BlockId>(md->bl_id));
}
return PartyId();
}
/*==========================================
*
*------------------------------------------
*/
Race battle_get_race(dumb_ptr<block_list> bl)
{
nullpo_retr(Race::formless, bl);
if (bl->bl_type == BL::MOB)
return get_mob_db(bl->is_mob()->mob_class).race;
else if (bl->bl_type == BL::PC)
return Race::demihuman;
else
return Race::formless;
}
/*==========================================
*
*------------------------------------------
*/
MobMode battle_get_mode(dumb_ptr<block_list> bl)
{
nullpo_retr(MobMode::CAN_MOVE, bl);
if (bl->bl_type == BL::MOB)
return get_mob_db(bl->is_mob()->mob_class).mode;
// とりあえず動くということで1 | For the time being, it will move 1
return MobMode::CAN_MOVE;
}
/*==========================================
*
*------------------------------------------
*/
int battle_get_stat(SP stat_id, dumb_ptr<block_list> bl)
{
switch (stat_id)
{
case SP::STR:
return battle_get_str(bl);
case SP::AGI:
return battle_get_agi(bl);
case SP::DEX:
return battle_get_dex(bl);
case SP::VIT:
return battle_get_vit(bl);
case SP::INT:
return battle_get_int(bl);
case SP::LUK:
return battle_get_luk(bl);
default:
return 0;
}
}
/*==========================================
*
*------------------------------------------
*/
// StatusChange系の所得 | StatusChange Income
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> battle_get_sc_data(dumb_ptr<block_list> bl)
{
nullpo_retr(nullptr, bl);
switch (bl->bl_type)
{
case BL::MOB:
return bl->is_mob()->sc_data;
case BL::PC:
return bl->is_player()->sc_data;
}
return nullptr;
}
/*==========================================
*
*------------------------------------------
*/
Opt1 *battle_get_opt1(dumb_ptr<block_list> bl)
{
nullpo_retn(bl);
if (bl->bl_type == BL::MOB)
return &bl->is_mob()->opt1;
else if (bl->bl_type == BL::PC)
return &bl->is_player()->opt1;
else if (bl->bl_type == BL::NPC)
return &bl->is_npc()->opt1;
return nullptr;
}
/*==========================================
*
*------------------------------------------
*/
Opt2 *battle_get_opt2(dumb_ptr<block_list> bl)
{
nullpo_retn(bl);
if (bl->bl_type == BL::MOB)
return &bl->is_mob()->opt2;
else if (bl->bl_type == BL::PC)
return &bl->is_player()->opt2;
else if (bl->bl_type == BL::NPC)
return &bl->is_npc()->opt2;
return nullptr;
}
/*==========================================
*
*------------------------------------------
*/
Opt3 *battle_get_opt3(dumb_ptr<block_list> bl)
{
nullpo_retn(bl);
if (bl->bl_type == BL::MOB)
return &bl->is_mob()->opt3;
else if (bl->bl_type == BL::PC)
return &bl->is_player()->opt3;
else if (bl->bl_type == BL::NPC)
return &bl->is_npc()->opt3;
return nullptr;
}
/*==========================================
*
*------------------------------------------
*/
Opt0 *battle_get_option(dumb_ptr<block_list> bl)
{
nullpo_retn(bl);
if (bl->bl_type == BL::MOB)
return &bl->is_mob()->option;
else if (bl->bl_type == BL::PC)
return &bl->is_player()->status.option;
else if (bl->bl_type == BL::NPC)
return &bl->is_npc()->option;
return nullptr;
}
//-------------------------------------------------------------------
/*==========================================
*
*------------------------------------------
*/
// ダメージの遅延 | damage delay
struct battle_delay_damage_
{
dumb_ptr<block_list> src, *target;
int damage;
int flag;
};
/*==========================================
*
*------------------------------------------
*/
// 実際にHPを操作 | Actually operate HP
int battle_damage(dumb_ptr<block_list> bl, dumb_ptr<block_list> target,
int damage, int flag)
{
nullpo_retz(target); // blはNULLで呼ばれることがあるので他でチェック | bl may be called with NULL, so check elsewhere
if (damage == 0)
return 0;
if (target->bl_prev == nullptr)
return 0;
if (bl)
{
if (bl->bl_prev == nullptr)
return 0;
}
if (damage < 0)
return battle_heal(bl, target, -damage, 0, flag);
if (target->bl_type == BL::MOB)
{ // MOB
dumb_ptr<mob_data> md = target->is_mob();
if (md && md->skilltimer && md->state.skillcastcancel) // 詠唱妨害 | chant obstruction
skill_castcancel(target, 0);
return mob_damage(bl, md, damage, 0);
}
else if (target->bl_type == BL::PC)
{ // PC
dumb_ptr<map_session_data> tsd = target->is_player();
return pc_damage(bl, tsd, damage);
}
return 0;
}
/*==========================================
*
*------------------------------------------
*/
int battle_heal(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int hp,
int sp, int flag)
{
nullpo_retz(target); // blはNULLで呼ばれることがあるので他でチェック | bl may be called with NULL, so check elsewhere
if (target->bl_type == BL::PC
&& pc_isdead(target->is_player()))
return 0;
if (hp == 0 && sp == 0)
return 0;
if (hp < 0)
return battle_damage(bl, target, -hp, flag);
if (target->bl_type == BL::MOB)
return mob_heal(target->is_mob(), hp);
else if (target->bl_type == BL::PC)
return pc_heal(target->is_player(), hp, sp);
return 0;
}
/*==========================================
*
*------------------------------------------
*/
// 攻撃停止 | stop attack
int battle_stopattack(dumb_ptr<block_list> bl)
{
nullpo_retz(bl);
if (bl->bl_type == BL::MOB)
return mob_stopattack(bl->is_mob());
else if (bl->bl_type == BL::PC)
return pc_stopattack(bl->is_player());
return 0;
}
/*==========================================
* ダメージ最終計算
* Final damage calculation
*------------------------------------------
*/
static
int battle_calc_damage(dumb_ptr<block_list>, dumb_ptr<block_list> bl,
int damage, int div_,
SkillID, int, BF flag)
{
dumb_ptr<mob_data> md = nullptr;
nullpo_retz(bl);
if (bl->bl_type == BL::MOB)
md = bl->is_mob();
if (battle_config.skill_min_damage
|| bool(flag & BF::MISC))
{
if (div_ < 255)
{
if (damage > 0 && damage < div_)
damage = div_;
}
else if (damage > 0 && damage < 3)
damage = 3;
}
if (md != nullptr && md->hp > 0 && damage > 0) // 反撃などのMOBスキル判定 | MOB skill judgment such as counterattack
mobskill_event(md, flag);
return damage;
}
/*==========================================
*
*------------------------------------------
*/
static
struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src,
dumb_ptr<block_list> target,
SkillID skill_num,
int skill_lv, int)
{
dumb_ptr<map_session_data> tsd = nullptr;
dumb_ptr<mob_data> md = src->is_mob(), tmd = nullptr;
int hitrate, flee, cri = 0, atkmin, atkmax;
int target_count = 1;
int def1 = battle_get_def(target);
int def2 = battle_get_def2(target);
int t_vit = battle_get_vit(target);
struct Damage wd {};
int damage;
DamageType type;
int div_;
BF flag;
int ac_flag = 0;
ATK dmg_lv = ATK::ZERO;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data, t_sc_data;
nullpo_retr(wd, src);
nullpo_retr(wd, target);
nullpo_retr(wd, md);
sc_data = battle_get_sc_data(src);
// ターゲット
if (target->bl_type == BL::PC)
tsd = target->is_player();
else if (target->bl_type == BL::MOB)
tmd = target->is_mob();
MobMode t_mode = battle_get_mode(target);
t_sc_data = battle_get_sc_data(target);
flag = BF::SHORT | BF::WEAPON | BF::NORMAL; // 攻撃の種類の設定 | attack type settings
// 回避率計算、回避判定は後で | Evasion rate calculation, avoidance judgment later
flee = battle_get_flee(target);
if (battle_config.agi_penaly_type > 0
|| battle_config.vit_penaly_type > 0)
target_count +=
battle_counttargeted(target, src,
battle_config.agi_penaly_count_lv);
if (battle_config.agi_penaly_type > 0)
{
if (target_count >= battle_config.agi_penaly_count)
{
if (battle_config.agi_penaly_type == 1)
flee =
(flee *
(100 -
(target_count -
(battle_config.agi_penaly_count -
1)) * battle_config.agi_penaly_num)) / 100;
else if (battle_config.agi_penaly_type == 2)
flee -=
(target_count -
(battle_config.agi_penaly_count -
1)) * battle_config.agi_penaly_num;
if (flee < 1)
flee = 1;
}
}
hitrate = battle_get_hit(src) - flee + 80;
type = DamageType::NORMAL;
div_ = 1; // single attack
if (battle_config.enemy_str)
damage = battle_get_baseatk(src);
else
damage = 0;
{
atkmin = battle_get_atk(src);
atkmax = battle_get_atk2(src);
}
if (get_mob_db(md->mob_class).range > 3)
flag = (flag & ~BF::RANGEMASK) | BF::LONG;
if (atkmin > atkmax)
atkmin = atkmax;
cri = battle_get_critical(src);
cri -= battle_get_luk(target) * 3;
if (battle_config.enemy_critical_rate != 100)
{
cri = cri * battle_config.enemy_critical_rate / 100;
if (cri < 1)
cri = 1;
}
if (ac_flag)
cri = 1000;
if (tsd && tsd->critical_def)
cri = cri * (100 - tsd->critical_def) / 100;
if ((skill_num == SkillID::ZERO)
&& skill_lv >= 0 && battle_config.enemy_critical
&& random_::chance({cri, 1000}))
// 判定(スキルの場合は無視) | Judgment (ignored for skills)
// 敵の判定 | judgment of the enemy
{
damage += atkmax;
type = DamageType::CRITICAL;
}
else
{
int vitbonusmax;
if (atkmax > atkmin)
damage += random_::in(atkmin, atkmax);
else
damage += atkmin;
if (skill_num != SkillID::ZERO && skill_num != SkillID::NEGATIVE)
{
flag = (flag & ~BF::SKILLMASK) | BF::SKILL;
}
{
// 対 象の防御力によるダメージの減少 | Decreased damage due to target's defense
// ディバインプロテクション(ここでいいのかな?) | Divine Protection (maybe here?)
if (def1 < 1000000)
{ // DEF, VIT無視 | DEF, VIT ignore
int t_def;
target_count =
1 + battle_counttargeted(target, src,
battle_config.vit_penaly_count_lv);
if (battle_config.vit_penaly_type > 0)
{
if (target_count >= battle_config.vit_penaly_count)
{
if (battle_config.vit_penaly_type == 1)
{
def1 =
(def1 *
(100 -
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num)) /
100;
def2 =
(def2 *
(100 -
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num)) /
100;
t_vit =
(t_vit *
(100 -
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num)) /
100;
}
else if (battle_config.vit_penaly_type == 2)
{
def1 -=
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num;
def2 -=
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num;
t_vit -=
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num;
}
if (def1 < 0)
def1 = 0;
if (def2 < 1)
def2 = 1;
if (t_vit < 1)
t_vit = 1;
}
}
t_def = def2 * 8 / 10;
vitbonusmax = (t_vit / 20) * (t_vit / 20) - 1;
{
damage = damage * (100 - def1) / 100;
damage -= t_def;
if (vitbonusmax > 0)
damage -= random_::in(0, vitbonusmax);
}
}
}
}
// 0未満だった場合1に補正 | Corrected to 1 if less than 0
if (damage < 1)
damage = 1;
// 回避修正 | avoidance fix
if (hitrate < 1000000)
hitrate = ((hitrate > 95) ? 95 : ((hitrate < 5) ? 5 : hitrate));
if (type == DamageType::NORMAL && !random_::chance({hitrate, 100}))
{
damage = 0;
dmg_lv = ATK::FLEE;
}
else
{
dmg_lv = ATK::DEF;
}
if (damage < 0)
damage = 0;
// 完全回避の判定 | Judgment of complete avoidance
if (skill_num == SkillID::ZERO && skill_lv >= 0 && tsd != nullptr
&& random_::chance({battle_get_flee2(target), 1000}))
{
damage = 0;
type = DamageType::FLEE2;
dmg_lv = ATK::LUCKY;
}
if (battle_config.enemy_perfect_flee)
{
if (skill_num == SkillID::ZERO && skill_lv >= 0 && tmd != nullptr
&& random_::chance({battle_get_flee2(target), 1000}))
{
damage = 0;
type = DamageType::FLEE2;
dmg_lv = ATK::LUCKY;
}
}
// if(def1 >= 1000000 && damage > 0)
if (bool(t_mode & MobMode::PLANT) && damage > 0)
damage = 1;
damage = battle_calc_damage(src, target, damage, div_,
skill_num, skill_lv, flag);
wd.damage = damage;
wd.type = type;
wd.div_ = div_;
wd.amotion = battle_get_amotion(src);
wd.dmotion = battle_get_dmotion(target);
wd.flag = flag;
wd.dmg_lv = dmg_lv;
return wd;
}
/*==========================================
*
*------------------------------------------
*/
int battle_is_unarmed(dumb_ptr<block_list> bl)
{
if (!bl)
return 0;
if (bl->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd = bl->is_player();
IOff0 sidx = sd->equip_index_maybe[EQUIP::SHIELD];
IOff0 widx = sd->equip_index_maybe[EQUIP::WEAPON];
return !sidx.ok() && !widx.ok();
}
else
return 0;
}
/*
* =========================================================================
* PCの武器による攻撃
* Attack with PC weapons
*-------------------------------------------------------------------------
*/
static
struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
dumb_ptr<block_list> target,
SkillID skill_num,
int skill_lv, int)
{
dumb_ptr<map_session_data> sd = src->is_player(), tsd = nullptr;
dumb_ptr<mob_data> tmd = nullptr;
int hitrate, flee, cri = 0, atkmin, atkmax;
int dex, target_count = 1;
int def1 = battle_get_def(target);
int def2 = battle_get_def2(target);
int t_vit = battle_get_vit(target);
struct Damage wd {};
int damage;
DamageType type;
int div_;
BF flag;
ATK dmg_lv = ATK::ZERO;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data, t_sc_data;
int watk;
bool da = false, ds = false;
int ac_flag = 0;
int target_distance;
nullpo_retr(wd, src);
nullpo_retr(wd, target);
nullpo_retr(wd, sd);
// アタッカー | attacker
sc_data = battle_get_sc_data(src); // ステータス異常 | Abnormal status
sd->state.attack_type = BF::WEAPON; // 攻撃タイプは武器攻撃 | Attack type is weapon attack
// ターゲット | target
if (target->bl_type == BL::PC) // 対象がPCなら | If the target is a PC
tsd = target->is_player(); // tsdに代入(tmdはNULL) | Assign to tsd (tmd is NULL)
else if (target->bl_type == BL::MOB) // 対象がMobなら | If the target is a mob
tmd = target->is_mob(); // tmdに代入(tsdはNULL) | Assign to tmd (tsd is NULL)
MobMode t_mode = battle_get_mode(target); // 対象のMode | Target Mode
t_sc_data = battle_get_sc_data(target); // 対象のステータス異常 | Target status ailment
flag = BF::SHORT | BF::WEAPON | BF::NORMAL; // 攻撃の種類の設定 | attack type settings
// 回避率計算、回避判定は後で | Evasion rate calculation, avoidance judgment later
flee = battle_get_flee(target);
if (battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) // AGI、VITペナルティ設定が有効 | AGI and VIT penalty settings are enabled
target_count += battle_counttargeted(target, src,
battle_config.agi_penaly_count_lv); // 対象の数を算出 | Calculate the number of targets
if (battle_config.agi_penaly_type > 0)
{
if (target_count >= battle_config.agi_penaly_count)
{ // ペナルティ設定より対象が多い | More targets than penalty setting
if (battle_config.agi_penaly_type == 1) // 回避率がagi_penaly_num%ずつ減少 | Evasion rate decreased by agi_penaly_num%
flee =
(flee *
(100 -
(target_count -
(battle_config.agi_penaly_count -
1)) * battle_config.agi_penaly_num)) / 100;
else if (battle_config.agi_penaly_type == 2) // 回避率がagi_penaly_num分減少 | Avoidance rate is reduced
flee -=
(target_count -
(battle_config.agi_penaly_count -
1)) * battle_config.agi_penaly_num;
if (flee < 1)
flee = 1; // 回避率は最低でも1 | Evasion rate is at least 1
}
}
hitrate = battle_get_hit(src) - flee + 80; // 命中率計算 | hit rate calculation
{ // [fate] Reduce hit chance by distance
int dx = abs(src->bl_x - target->bl_x);
int dy = abs(src->bl_y - target->bl_y);
int malus_dist;
target_distance = std::max(dx, dy);
malus_dist =
std::max(0, target_distance - (skill_power(sd, SkillID::AC_OWL) / 75));
hitrate -= (malus_dist * (malus_dist + 1));
}
dex = battle_get_dex(src); //DEX
watk = battle_get_atk(src); //ATK
type = DamageType::NORMAL;
div_ = 1; // single attack
{
damage = battle_get_baseatk(sd); // damega,damega2初登場、base_atkの取得 | First appearance of damega and damega2, acquisition of base_atk
}
if (sd->attackrange > 2)
{ // [fate] ranged weapon?
const int range_damage_bonus = 80; // up to 31.25% bonus for long-range hit
damage =
damage * (256 +
((range_damage_bonus * target_distance) /
sd->attackrange)) >> 8;
}
atkmin = dex; // 最低ATKはDEXで初期化? | Minimum ATK initialized with DEX?
sd->state.arrow_atk = 0; // arrow_atk初期化 | arrow_atk initialization
IOff0 widx = sd->equip_index_maybe[EQUIP::WEAPON];
if (widx.ok())
{
OMATCH_BEGIN_SOME (sdidw, sd->inventory_data[widx])
{
atkmin = atkmin * (80 + sdidw->wlv * 20) / 100;
}
OMATCH_END ();
}
if (sd->status.weapon == ItemLook::BOW)
{ // 武器が弓矢の場合 | If the weapon is a bow and arrow
atkmin = watk * ((atkmin < watk) ? atkmin : watk) / 100; // 弓用最低ATK計算 | Bows are calculated with minimum ATK
flag = (flag & ~BF::RANGEMASK) | BF::LONG; // 遠距離攻撃フラグを有効 | Enable ranged attack flag
sd->state.arrow_atk = 1; // arrow_atk有効化 | arrow_atk enabled
}
{
atkmax = watk;
}
if (atkmin > atkmax && !(sd->state.arrow_atk))
atkmin = atkmax; // 弓は最低が上回る場合あり | Bow may exceed minimum
if (tmd && !bool(t_mode & MobMode::BOSS)) // deadly strike works only on mobs but not on mobs with boss flag
if (sd->deadly_strike > 0)
ds = random_::chance({sd->deadly_strike, 100});
if (!ds) // no double, crit and normal damage calculation needed if it is a deadly strike
{
if (sd->double_rate > 0 && skill_num == SkillID::ZERO && skill_lv >= 0)
da = random_::chance({sd->double_rate, 100});
if (!da)
{
// ダブルアタックが発動していない | Double Attack is not activated
// クリティカル計算 | critical calculation
cri = battle_get_critical(src);
if (sd->state.arrow_atk)
cri += sd->arrow_cri;
cri -= battle_get_luk(target) * 3;
if (ac_flag)
cri = 1000;
}
if (tsd && tsd->critical_def)
cri = cri * (100 - tsd->critical_def) / 100;
else if (tmd && tmd->stats[mob_stat::CRITICAL_DEF])
cri = cri * (100 - tmd->stats[mob_stat::CRITICAL_DEF]) / 100;
// ダブルアタックが発動していない | Double Attack is not activated
// 判定(スキルの場合は無視) | Judgment (ignored for skills)
if (!da && skill_num == SkillID::ZERO && skill_lv >= 0
&& random_::chance({cri, 1000}))
{
damage += atkmax;
if (sd->atk_rate != 100)
{
damage = (damage * sd->atk_rate) / 100;
}
if (sd->state.arrow_atk)
damage += sd->arrow_atk;
type = DamageType::CRITICAL;
}
else
{
int vitbonusmax;
if (atkmax > atkmin)
damage += random_::in(atkmin, atkmax);
else
damage += atkmin;
if (sd->atk_rate != 100)
{
damage = (damage * sd->atk_rate) / 100;
}
if (sd->state.arrow_atk)
{
if (sd->arrow_atk > 0)
damage += random_::in(0, sd->arrow_atk);
hitrate += sd->arrow_hit;
}
if (skill_num != SkillID::ZERO && skill_num != SkillID::NEGATIVE)
{
flag = (flag & ~BF::SKILLMASK) | BF::SKILL;
}
{
// 対 象の防御力によるダメージの減少 | Decreased damage due to target's defense
// ディバインプロテクション(ここでいいのかな?) | Divine Protection (maybe here?)
if (def1 < 1000000)
{ // DEF, VIT無視 | DEF, VIT ignore
int t_def;
target_count =
1 + battle_counttargeted(target, src,
battle_config.vit_penaly_count_lv);
if (battle_config.vit_penaly_type > 0)
{
if (target_count >= battle_config.vit_penaly_count)
{
if (battle_config.vit_penaly_type == 1)
{
def1 =
(def1 *
(100 -
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num)) /
100;
def2 =
(def2 *
(100 -
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num)) /
100;
t_vit =
(t_vit *
(100 -
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num)) /
100;
}
else if (battle_config.vit_penaly_type == 2)
{
def1 -=
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num;
def2 -=
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num;
t_vit -=
(target_count -
(battle_config.vit_penaly_count -
1)) * battle_config.vit_penaly_num;
}
if (def1 < 0)
def1 = 0;
if (def2 < 1)
def2 = 1;
if (t_vit < 1)
t_vit = 1;
}
}
t_def = def2 * 8 / 10;
vitbonusmax = (t_vit / 20) * (t_vit / 20) - 1;
{
{
damage = damage * (100 - def1) / 100;
damage -= t_def;
if (vitbonusmax > 0)
damage -= random_::in(0, vitbonusmax);
}
}
}
}
}
// 精錬ダメージの追加 | Add refining damage
{ // DEF, VIT無視 | DEF, VIT ignore
damage += battle_get_atk2(src);
}
}
else
if (sd->state.arrow_atk)
hitrate += sd->arrow_hit;
// 0未満だった場合1に補正 | Corrected to 1 if less than 0
if (damage < 1)
damage = 1;
// スキル修正2(修練系) | Skill Modification 2 (Training)
// 修練ダメージ(右手のみ) ソニックブロー時は別処理(1撃に付き1/8適応) | Training damage (right hand only) Separate processing during sonic blow (1/8 adaptation per hit)
{ //修練ダメージ無視 | Ignores training damage
}
if (sd->perfect_hit > 0)
{
if (random_::chance({sd->perfect_hit, 100}))
hitrate = 1000000;
}
// 回避修正 | avoidance fix
hitrate = (hitrate < 5) ? 5 : hitrate;
if (type == DamageType::NORMAL && !random_::chance({hitrate, 100}))
{
damage = 0;
dmg_lv = ATK::FLEE;
}
else
{
dmg_lv = ATK::DEF;
}
if (damage < 0)
damage = 0;
if (ds)
{
damage = tmd->hp;
//type = DamageType::DEADLY;
// M+ does not support this value on package 0x008a it lags a bit and displays an error message.
// This can be implemented if ManaVerse is the only supported client so long DamageType::CRITICAL will do
type = DamageType::CRITICAL;
}
// 右手,短剣のみ | right hand, dagger only
if (da)
{ // ダブルアタックが発動しているか | Is double attack activated?
div_ = 2;
damage += damage;
type = DamageType::DOUBLED;
}
// 完全回避の判定 | Judgment of complete avoidance
if (skill_num == SkillID::ZERO && skill_lv >= 0 && tsd != nullptr && div_ < 255
&& random_::chance({battle_get_flee2(target), 1000}))
{
damage = 0;
type = DamageType::FLEE2;
dmg_lv = ATK::LUCKY;
}
// 対象が完全回避をする設定がONなら | If the setting to completely avoid the target is ON
if (battle_config.enemy_perfect_flee)
{
if (skill_num == SkillID::ZERO && skill_lv >= 0 && tmd != nullptr && div_ < 255
&& random_::chance({battle_get_flee2(target), 1000}))
{
damage = 0;
type = DamageType::FLEE2;
dmg_lv = ATK::LUCKY;
}
}
// MobのModeに頑強フラグが立っているときの処理 | Processing when the stubborn flag is set in the mob's mode
if (bool(t_mode & MobMode::PLANT))
{
if (damage > 0)
damage = 1;
}
if (damage > 0)
{
{
damage =
battle_calc_damage(src, target, damage, div_, skill_num,
skill_lv, flag);
}
}
wd.damage = damage;
wd.type = type;
wd.div_ = div_;
wd.amotion = battle_get_amotion(src);
wd.dmotion = battle_get_dmotion(target);
wd.flag = flag;
wd.dmg_lv = dmg_lv;
return wd;
}
/*==========================================
* 武器ダメージ計算
* Weapon damage calculator
*------------------------------------------
*/
static
struct Damage battle_calc_weapon_attack(dumb_ptr<block_list> src,
dumb_ptr<block_list> target,
SkillID skill_num, int skill_lv,
int wflag)
{
struct Damage wd {};
nullpo_retr(wd, src);
nullpo_retr(wd, target);
if (src->bl_type == BL::PC)
wd = battle_calc_pc_weapon_attack(src, target, skill_num, skill_lv, wflag); // weapon breaking [Valaris]
else if (src->bl_type == BL::MOB)
wd = battle_calc_mob_weapon_attack(src, target, skill_num, skill_lv, wflag);
return wd;
}
/*==========================================
* 魔法ダメージ計算
* magic damage calculation
*------------------------------------------
*/
static
struct Damage battle_calc_magic_attack(dumb_ptr<block_list> bl,
dumb_ptr<block_list> target,
SkillID skill_num, int skill_lv, int)
{
int mdef1 = battle_get_mdef(target);
int mdef2 = battle_get_mdef2(target);
int matk1, matk2, damage = 0, div_ = 1;
struct Damage md {};
int normalmagic_flag = 1;
dumb_ptr<map_session_data> sd = nullptr;
nullpo_retr(md, bl);
nullpo_retr(md, target);
matk1 = battle_get_matk1(bl);
matk2 = battle_get_matk2(bl);
MobMode t_mode = battle_get_mode(target);
if (bl->bl_type == BL::PC)
{
sd = bl->is_player();
sd->state.attack_type = BF::MAGIC;
if (sd->matk_rate != 100)
{
matk1 = matk1 * sd->matk_rate / 100;
matk2 = matk2 * sd->matk_rate / 100;
}
sd->state.arrow_atk = 0;
}
BF aflag = BF::MAGIC | BF::LONG | BF::SKILL;
if (normalmagic_flag)
{
// 一般魔法ダメージ計算 | General magic damage calculation
if (matk1 > matk2)
damage = random_::in(matk2, matk1);
else
damage = matk2;
{
{
damage = (damage * (100 - mdef1)) / 100;
damage -= mdef2;
}
}
if (damage < 1)
damage = 1;
}
if (damage < 0)
damage = 0;
div_ = skill_get_num(skill_num, skill_lv);
if (div_ > 1)
damage *= div_;
// if(mdef1 >= 1000000 && damage > 0)
if (bool(t_mode & MobMode::PLANT) && damage > 0)
damage = 1;
damage = battle_calc_damage(bl, target, damage, div_, skill_num, skill_lv, aflag); // 最終修正 | final revision
md.damage = damage;
md.div_ = div_;
md.amotion = battle_get_amotion(bl);
md.dmotion = battle_get_dmotion(target);
md.type = DamageType::NORMAL;
md.flag = aflag;
return md;
}
/*==========================================
* その他ダメージ計算
* Other damage calculation
*------------------------------------------
*/
static
struct Damage battle_calc_misc_attack(dumb_ptr<block_list> bl,
dumb_ptr<block_list> target,
SkillID skill_num, int skill_lv, int)
{
dumb_ptr<map_session_data> sd = nullptr;
int damage = 0, div_ = 1;
struct Damage md {};
int damagefix = 1;
BF aflag = BF::MISC | BF::LONG | BF::SKILL;
nullpo_retr(md, bl);
nullpo_retr(md, target);
if (bl->bl_type == BL::PC)
{
sd = bl->is_player();
sd->state.attack_type = BF::MISC;
sd->state.arrow_atk = 0;
}
switch (skill_num)
{
case SkillID::NPC_SELFDESTRUCTION: // 自爆 | blew up
damage = battle_get_hp(bl) - (bl == target ? 1 : 0);
damagefix = 0;
break;
}
if (damagefix)
{
if (damage < 1)
damage = 1;
}
div_ = skill_get_num(skill_num, skill_lv);
if (div_ > 1)
damage *= div_;
if (damage > 0
&& (damage < div_
|| (battle_get_def(target) >= 1000000
&& battle_get_mdef(target) >= 1000000)))
{
damage = div_;
}
damage = battle_calc_damage(bl, target, damage, div_, skill_num, skill_lv, aflag); // 最終修正 | final revision
md.damage = damage;
md.div_ = div_;
md.amotion = battle_get_amotion(bl);
md.dmotion = battle_get_dmotion(target);
md.type = DamageType::NORMAL;
md.flag = aflag;
return md;
}
/*==========================================
* ダメージ計算一括処理用
* For damage calculation batch processing
*------------------------------------------
*/
struct Damage battle_calc_attack(BF attack_type,
dumb_ptr<block_list> bl,
dumb_ptr<block_list> target, SkillID skill_num,
int skill_lv, int flag)
{
struct Damage d {};
switch (attack_type)
{
case BF::WEAPON:
return battle_calc_weapon_attack(bl, target, skill_num, skill_lv,
flag);
case BF::MAGIC:
return battle_calc_magic_attack(bl, target, skill_num, skill_lv,
flag);
case BF::MISC:
return battle_calc_misc_attack(bl, target, skill_num, skill_lv,
flag);
default:
if (battle_config.error_log)
PRINTF("battle_calc_attack: unknwon attack type ! %d\n"_fmt,
attack_type);
break;
}
return d;
}
/*==========================================
* 通常攻撃処理まとめ
* Normal attack processing summary
*------------------------------------------
*/
ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
tick_t tick)
{
dumb_ptr<map_session_data> sd = nullptr;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> t_sc_data = battle_get_sc_data(target);
struct Damage wd;
nullpo_retr(ATK::ZERO, src);
nullpo_retr(ATK::ZERO, target);
if (src->bl_type == BL::PC)
sd = src->is_player();
if (src->bl_prev == nullptr || target->bl_prev == nullptr)
return ATK::ZERO;
if (src->bl_type == BL::PC && pc_isdead(sd))
return ATK::ZERO;
if (target->bl_type == BL::PC
&& pc_isdead(target->is_player()))
return ATK::ZERO;
Opt1 *opt1 = battle_get_opt1(src);
if (opt1 != nullptr && bool(*opt1))
{
battle_stopattack(src);
return ATK::ZERO;
}
if (battle_check_target(src, target, BCT_ENEMY) > 0 &&
battle_check_range(src, target, 0))
{
// 攻撃対象となりうるので攻撃 | Attack because it can be attacked
if (sd && sd->status.weapon == ItemLook::BOW)
{
IOff0 aidx = sd->equip_index_maybe[EQUIP::ARROW];
if (aidx.ok())
{
if (battle_config.arrow_decrement)
pc_delitem(sd, aidx, 1, 0);
}
else
{
clif_arrow_fail(sd, 0);
return ATK::ZERO;
}
}
wd = battle_calc_weapon_attack(src, target, SkillID::ZERO, 0, 0);
// significantly increase injuries for hasted characters
if (wd.damage > 0 && t_sc_data[StatusChange::SC_HASTE].timer)
{
wd.damage = (wd.damage * (16 + t_sc_data[StatusChange::SC_HASTE].val1)) >> 4;
}
if (wd.damage > 0
&& t_sc_data[StatusChange::SC_PHYS_SHIELD].timer
&& target->bl_type == BL::PC)
{
int reduction = t_sc_data[StatusChange::SC_PHYS_SHIELD].val1;
if (reduction > wd.damage)
reduction = wd.damage;
wd.damage -= reduction;
MAP_LOG_PC(target->is_player(),
"MAGIC-ABSORB-DMG %d"_fmt, reduction);
}
{
clif_damage(src, target, tick, wd.amotion, wd.dmotion,
wd.damage, wd.div_, wd.type);
}
MapBlockLock lock;
if (src->bl_type == BL::PC)
{
IOff0 weapon_index = sd->equip_index_maybe[EQUIP::WEAPON];
ItemNameId weapon;
if (weapon_index.ok())
{
OMATCH_BEGIN_SOME (sdidw, sd->inventory_data[weapon_index])
{
if (bool(sd->status.inventory[weapon_index].equip & EPOS::WEAPON))
{
weapon = sdidw->nameid;
}
}
OMATCH_END ();
}
MAP_LOG("PC%d %s:%d,%d WPNDMG %s%d %d FOR %d WPN %d"_fmt,
sd->status_key.char_id, src->bl_m->name_, src->bl_x, src->bl_y,
(target->bl_type == BL::PC) ? "PC"_s : "MOB"_s,
(target->bl_type == BL::PC)
? unwrap<CharId>(target->is_player()->status_key.char_id)
: unwrap<BlockId>(target->bl_id),
battle_get_class(target),
wd.damage, weapon);
}
if (target->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd2 = target->is_player();
MAP_LOG("PC%d %s:%d,%d WPNINJURY %s%d %d FOR %d"_fmt,
sd2->status_key.char_id, target->bl_m->name_, target->bl_x, target->bl_y,
(src->bl_type == BL::PC) ? "PC"_s : "MOB"_s,
(src->bl_type == BL::PC)
? unwrap<CharId>(src->is_player()->status_key.char_id)
: unwrap<BlockId>(src->bl_id),
battle_get_class(src),
wd.damage);
}
battle_damage(src, target, (wd.damage), 0);
if (target->bl_prev != nullptr &&
(target->bl_type != BL::PC
|| (target->bl_type == BL::PC
&& !pc_isdead(target->is_player()))))
{
if (wd.damage > 0)
{
skill_additional_effect(src, target, SkillID::ZERO, 0);
}
}
if (sd)
{
if (bool(wd.flag & BF::WEAPON)
&& src != target
&& (wd.damage > 0))
{
int hp = 0, sp = 0;
if (sd->hp_drain_rate && wd.damage > 0
&& random_::chance({sd->hp_drain_rate, 100}))
{
hp += (wd.damage * sd->hp_drain_per) / 100;
}
if (sd->sp_drain_rate && wd.damage > 0
&& random_::chance({sd->sp_drain_rate, 100}))
{
sp += (wd.damage * sd->sp_drain_per) / 100;
}
if (hp || sp)
pc_heal(sd, hp, sp);
}
}
}
return wd.dmg_lv;
}
/*==========================================
*
*------------------------------------------
*/
bool battle_check_undead(Race race, Element element)
{
if (battle_config.undead_detect_type == 0)
{
return element == Element::undead;
}
else if (battle_config.undead_detect_type == 1)
{
return race == Race::undead;
}
else
{
return element == Element::undead || race == Race::undead;
}
}
/*==========================================
* 敵味方判定(1=肯定,0=否定,-1=エラー)
* flag&0xf0000 = 0x00000:敵じゃないか判定(ret:1=敵ではない)
* = 0x10000:パーティー判定(ret:1=パーティーメンバ)
* = 0x20000:全て(ret:1=敵味方両方)
* = 0x40000:敵か判定(ret:1=敵)
* = 0x50000:パーティーじゃないか判定(ret:1=パーティでない)
* Friend or foe judgment (1=positive, 0=negative, -1=error)
* flag&0xf0000 = 0x00000: determine if it is an enemy (ret: 1 = not an enemy)
* = 0x10000: party judgment (ret: 1 = party member)
* = 0x20000: all (ret: 1 = both friend and foe)
* = 0x40000: Enemy or not (ret: 1 = enemy)
* = 0x50000: Determine if it is not a party (ret: 1 = not a party)
*------------------------------------------
*/
int battle_check_target(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
BCT flag)
{
PartyId s_p, t_p;
dumb_ptr<block_list> ss = src;
nullpo_retz(src);
nullpo_retz(target);
if (flag & BCT_ENEMY)
{ // 反転フラグ | reversal flag
int ret = battle_check_target(src, target, flag & (BCT_PARTY | BCT_ALL));
if (ret != -1)
return !ret;
return -1;
}
if (flag & BCT_ALL)
{
if (target->bl_type == BL::MOB || target->bl_type == BL::PC)
return 1;
else
return -1;
}
if (target->bl_type == BL::PC
&& target->is_player()->invincible_timer)
return -1;
// Mobでmaster_idがあってspecial_mob_aiなら、召喚主を求める | If the mob has a master_id and a special_mob_ai, seek the summoner
if (src->bl_type == BL::MOB)
{
dumb_ptr<mob_data> md = src->is_mob();
if (md && md->master_id)
{
if (md->master_id == target->bl_id) // 主なら肯定 | Yes if the main
return 1;
if (md->state.special_mob_ai)
{
if (target->bl_type == BL::MOB)
{ // special_mob_aiで対象がMob | special_mob_ai targets mobs
dumb_ptr<mob_data> tmd = target->is_mob();
if (tmd)
{
if (tmd->master_id != md->master_id) // 召喚主が一緒でなければ否定 | Deny if the summoner is not with you
return 0;
else
{ // 召喚主が一緒なので肯定したいけど自爆は否定 | I want to affirm because the summoner is the same, but I deny suicide bombing
if (md->state.special_mob_ai > 2)
return 0;
else
return 1;
}
}
}
}
if ((ss = map_id2bl(md->master_id)) == nullptr)
return -1;
}
}
if (src == target || ss == target) // 同じなら肯定 | Yes if same
return 1;
if (target->bl_type == BL::PC
&& pc_isinvisible(target->is_player()))
return -1;
if (src->bl_prev == nullptr || // 死んでるならエラー | error if dead
(src->bl_type == BL::PC && pc_isdead(src->is_player())))
return -1;
if ((ss->bl_type == BL::PC && target->bl_type == BL::MOB) ||
(ss->bl_type == BL::MOB && target->bl_type == BL::PC))
return 0; // PCvsMOBなら否定 | Negative if PC vs MOB
s_p = battle_get_party_id(ss);
t_p = battle_get_party_id(target);
if (flag & BCT_PARTY)
{
if (s_p && t_p && s_p == t_p) // 同じパーティなら肯定(味方) | Yes if same party (ally)
return 1;
else // パーティ検索なら同じパーティじゃない時点で否定 | If it's a party search, deny it when it's not the same party
return 0;
}
if (ss->bl_type == BL::PC && target->bl_type == BL::PC)
{ // 両方PVPモードなら否定(敵) | Deny if both are PVP mode (enemy)
if (ss->bl_m->flag.get(MapFlag::PVP)
|| pc_iskiller(ss->is_player(), target->is_player()))
{ // [MouseJstr]
if (battle_config.pk_mode)
return 1; // prevent novice engagement in pk_mode [Valaris]
else if (ss->bl_m->flag.get(MapFlag::PVP_NOPARTY) && s_p && t_p
&& s_p == t_p)
return 1;
return 0;
}
}
return 1; // 該当しないので無関係人物(まあ敵じゃないので味方) | Unrelated person because it does not apply (Well, not an enemy, so an ally)
}
/*==========================================
* 射程判定
* range judgment
*------------------------------------------
*/
int battle_check_range(dumb_ptr<block_list> src, dumb_ptr<block_list> bl,
int range)
{
int dx, dy, rangex, rangey;
struct walkpath_data wpd;
int arange;
nullpo_retz(src);
nullpo_retz(bl);
dx = (bl->bl_x - src->bl_x);
dy = (bl->bl_y - src->bl_y);
rangex = abs(dx);
rangey = abs(dy);
arange = ((rangex > rangey) ? rangex : rangey);
if (src->bl_m != bl->bl_m) // 違うマップ | different map
return 0;
if (range > 0 && range < arange) // 遠すぎる | too far
return 0;
if (arange < 2) // 同じマスか隣接 | same square or adjacent
return 1;
// if(bl->bl_type == BL_SKILL && ((struct skill_unit *)bl)->group->unit_id == 0x8d)
// return 1;
// 障害物判定 | Obstacle judgment
wpd.path_len = 0;
wpd.path_pos = 0;
wpd.path_half = 0;
if (path_search(&wpd, src->bl_m, src->bl_x, src->bl_y, bl->bl_x, bl->bl_y, 0x10001) !=
-1)
return 1;
dx = (dx > 0) ? 1 : ((dx < 0) ? -1 : 0);
dy = (dy > 0) ? 1 : ((dy < 0) ? -1 : 0);
return (path_search(&wpd, src->bl_m, src->bl_x + dx, src->bl_y + dy,
bl->bl_x - dx, bl->bl_y - dy, 0x10001) != -1) ? 1 : 0;
}
} // namespace map
} // namespace tmwa