Skip to content

Instantly share code, notes, and snippets.

@Singe-Horizontal
Last active February 12, 2024 02:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Singe-Horizontal/c6e3bb57f7bfdb16e99d748faec30926 to your computer and use it in GitHub Desktop.
Save Singe-Horizontal/c6e3bb57f7bfdb16e99d748faec30926 to your computer and use it in GitHub Desktop.
Expanded AI Conditions Full Mod
Copyright © 2022 Singe-Horizontal
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/db/import-tmpl/mob_expanded_ai_conditions_db.yml b/db/import-tmpl/mob_expanded_ai_conditions_db.yml
new file mode 100755
index 000000000..26aa641b4
--- /dev/null
+++ b/db/import-tmpl/mob_expanded_ai_conditions_db.yml
@@ -0,0 +1,85 @@
+#Format : - [not] enemy|self|friend|master status [[[><]=]valeur]]
+#For fields see doc/expanded_conditions.txt
+
+Header:
+ Type: MOB_EXPANDED_AI_CONDITIONS_DB
+ Version: 1
+
+Body:
+
+ - friendrecoverable:
+ - or:
+ - friend sleep
+ - friend stone
+ - friend stun
+ - friend freeze
+
+
+ - friendcurable:
+ - or:
+ - friend silence
+ - friend confusion
+ - friend blind
+
+ - friend_need_aspersio:
+ - not friend aspersio
+ - or:
+ - enemy undead >0
+ - enemy dark >0
+ - enemy ghost >0
+
+ - selfcellempty:
+ - not self safetywall
+ - not self pneuma
+ - not self landprotector
+ - not self icewall
+
+ - friendcellempty:
+ - not friend safetywall
+ - not friend pneuma
+ - not friend landprotector
+ - not friend icewall
+
+ - ignore_neutral:
+ - or:
+ - enemy ghostarmor >1
+ - enemy neutralresist >=100%
+
+ - asura:
+# - self spiritball >=5 # to be implemented
+ - self explosionspirits
+ - not enemy safetywall
+ - not ignore_neutral
+ - nand:
+ - enemy neutralresist >=50%
+ - enemy ghostarmor >0
+ - nand:
+ - enemy reflectphy >=10%
+ - nxor :
+ - enemy neutralresist >=50%
+ - enemy ghostarmor >0
+
+ - explosion:
+ - not self explosionspirits
+ # - self spiritball <5 #same
+
+ - selfcloserangecellempty:
+ - or:
+ - enemy distance_from_self <=5 # range is 3 but to have time to cast it
+ - not enemy range >3
+ - selfcellempty
+
+ - friendcloserangecellempty:
+ - enemy distance_from_friend <=5
+ - not enemy range >3
+ - friendcellempty
+
+ - offensive_pneuma:
+ - enemy job 4010 #offensive pneuma against wiz
+ - not enemy safetywall
+ - not enemy pneuma
+
+ - safety_wall_pushable:
+ - enemy safetywall
+ - not enemy knockbackimmune
+
diff --git a/db/mob_expanded_ai_conditions_db.yml b/db/mob_expanded_ai_conditions_db.yml
new file mode 100755
index 000000000..f6f2cf319
--- /dev/null
+++ b/db/mob_expanded_ai_conditions_db.yml
@@ -0,0 +1,7 @@
+Header:
+ Type: MOB_EXPANDED_AI_CONDITIONS_DB
+ Version: 1
+
+Footer:
+ Imports:
+ - Path: db/import/mob_expanded_ai_conditions_db.yml
diff --git a/doc/expanded_conditions.txt b/doc/expanded_conditions.txt
new file mode 100644
index 000000000..3a22ecb12
--- /dev/null
+++ b/doc/expanded_conditions.txt
@@ -0,0 +1,149 @@
+Advanced conditions list
+
+--Status ailment--
+anybad
+stone
+freeze
+stun
+sleep
+poison
+curse
+silence
+confusion
+blind
+decreaseagi
+hiding
+cloaking
+edp
+ruwach
+sight
+blessing
+increaseagi
+assumptio
+kyrie
+magnificat
+safetywall
+pneuma
+explosionspirits
+doublecasting
+volcano
+amplify
+quagmire
+spiderweb
+walloffog
+energycoat
+aspersio
+suffragium
+autoguard
+reflectshield
+endure
+defender
+kaizel
+kaahi
+kaupe
+eswoo
+eska
+parry
+concentration
+aurablade
+berserk
+eske
+soul
+cicada
+bragi
+lexaeterna
+union
+breakfall
+
+--Cell conditions--
+casewater
+basilica
+landprotector
+maelstrom
+icewall
+
+--Elemental conditions--
+neutralattack,neutralarmor,neutralresist
+waterattack,waterarmor, etc...
+earth
+fire
+wind
+poison
+holy
+dark
+ghost
+undead
+
+
+--Special Condition--
+exists
+percenthealth
+health
+job
+range
+distance_from_self
+distance_from_friend
+distance_from_master
+reflectphy
+attack
+mode
+
+--Logic Gates--
+and all must be true
+or any is enough
+nand any false is enough
+nor none must be true
+xor exactly one condition
+nxor either none or all
+
+
+------------------------------------------------------------------------------------------------------------------
+Hi. This is an expanded ai mod. It allows to define complex conditions for skill decision with a yml config file.
+It was designed to be accessible to everyone, designers, scripters, developpers, even players.
+I hope you will have a great deal of fun giving ai a new life.
+
+Installation : copy mob_expanded_ai_conditions_db.yml from import-tmpl onto import folder
+There are already some pre-defined entries.
+
+example : inside mob_expanded_ai_conditions_db.yml
+
+ - friendrecoverable:
+ - or:
+ - friend sleep
+ - friend stone
+ - friend stun
+ - friend freeze
+
+ - selfcellempty:
+ - not self safetywall
+ - not self pneuma
+ - not self landprotector
+ - not self icewall
+
+ - friendcellempty:
+ - not friend safetywall
+ - not friend pneuma
+ - not friend landprotector
+ - not friend icewall
+
+Then you have to add skill condition in the usual mob_skill_db.yml, with the label "expanded"
+
+1639,Kathryne Keyron@MG_SAFETYWALL,anytarget,12,10,3000,0,10000,yes,self,expanded,selfcellempty,,,,,,,
+1639,Kathryne Keyron@MG_SAFETYWALL,anytarget,12,10,3000,0,10000,yes,friend,expanded,friendcellempty,,,,,,,
+1637,Margaretha Sorin@PR_STRECOVERY,any,72,1,10000,1000,10000,no,friend,expanded,friendrecoverable,,,,,,,
+
+Bonus : a crazy asura condition complex, try to figure out what it does ;-)
+
+ - asura:
+# - self spiritball >=5 # to be implemented
+ - self explosionspirits
+ - not enemy safetywall
+ - not ignore_neutral
+ - nand:
+ - enemy neutralresist >=50%
+ - enemy ghostarmor >0
+ - nand:
+ - enemy reflectphy >=10%
+ - nxor :
+ - enemy neutralresist >=50%
+ - enemy ghostarmor >0
diff --git a/src/common/mmo.hpp b/src/common/mmo.hpp
index 5a2414dc2..c4c072393 100644
--- a/src/common/mmo.hpp
+++ b/src/common/mmo.hpp
@@ -229,7 +229,7 @@ enum e_mode {
MD_NORANDOMWALK = 0x0000020,
MD_NOCAST = 0x0000040,
MD_CANATTACK = 0x0000080,
- //FREE = 0x0000100,
+ MD_SKILLONLY = 0x0000100,
MD_CASTSENSORCHASE = 0x0000200,
MD_CHANGECHASE = 0x0000400,
MD_ANGRY = 0x0000800,
@@ -244,11 +244,12 @@ enum e_mode {
MD_IGNOREMISC = 0x0100000,
MD_KNOCKBACKIMMUNE = 0x0200000,
MD_TELEPORTBLOCK = 0x0400000,
- //FREE = 0x0800000,
+ MD_PCSKILLBEHAVIOR = 0x0800000,
MD_FIXEDITEMDROP = 0x1000000,
MD_DETECTOR = 0x2000000,
MD_STATUSIMMUNE = 0x4000000,
MD_SKILLIMMUNE = 0x8000000,
+
};
#define MD_MASK 0x000FFFF
diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index c4b307e81..c4eed38d4 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -2452,18 +2452,31 @@ static int battle_range_type(struct block_list *src, struct block_list *target,
}
//Skill Range Criteria
- if (battle_config.skillrange_by_distance &&
+ if (!(status_get_mode(src) & MD_PCSKILLBEHAVIOR) && 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;
+ switch (skill_id) {
+ case NPC_PULSESTRIKE:
+ case NPC_HELLJUDGEMENT:
+ case NPC_ICEBREATH:
+ case NPC_THUNDERBREATH:
+ case NPC_FIREBREATH:
+ case NPC_ACIDBREATH:
+ if (check_distance_bl(src, target, 3))
+ return BF_SHORT;
+ return BF_LONG;
+ default:
+ 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)
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
old mode 100644
new mode 100755
index bd12630d5..27280e217
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -2,14 +2,13 @@
// For more information, see LICENCE in the main folder
#include "mob.hpp"
-
+#include "mob_inlines.hpp"
#include <algorithm>
#include <map>
#include <math.h>
#include <stdlib.h>
#include <unordered_map>
-#include <vector>
-
+#include <functional>
#include "../common/cbasetypes.hpp"
#include "../common/db.hpp"
#include "../common/ers.hpp"
@@ -19,10 +18,9 @@
#include "../common/showmsg.hpp"
#include "../common/socket.hpp"
#include "../common/strlib.hpp"
-#include "../common/timer.hpp"
#include "../common/utilities.hpp"
#include "../common/utils.hpp"
-
+#include <stdexcept>
#include "achievement.hpp"
#include "battle.hpp"
#include "clif.hpp"
@@ -88,6 +86,166 @@ static struct eri *item_drop_list_ers;
MobSummonDatabase mob_summon_db;
MobChatDatabase mob_chat_db;
+MobExpandedAiConditionsDatabase mob_expanded_ai_conditions_db;
+
+
+//Globals used by ai functions
+const std::unordered_map<std::string, sc_type> um_statusname2id{ // Subset used for mob ai
+ { "anybad", SC_NONE },
+ { "stone", SC_STONE },
+ { "freeze", SC_FREEZE },
+ { "stun", SC_STUN },
+ { "sleep", SC_SLEEP },
+ { "poison", SC_POISON },
+ { "curse", SC_CURSE },
+ { "silence", SC_SILENCE },
+ { "confusion", SC_CONFUSION },
+ { "blind", SC_BLIND },
+ { "decreaseagi", SC_DECREASEAGI },
+ { "hiding", SC_HIDING },
+ { "cloaking", SC_CLOAKING },
+ { "edp", SC_EDP },
+ { "ruwach", SC_RUWACH },
+ { "sight", SC_SIGHT },
+ { "blessing", SC_BLESSING },
+ { "increaseagi", SC_INCREASEAGI },
+ { "assumptio", SC_ASSUMPTIO },
+ { "kyrie", SC_KYRIE, },
+ { "magnificat", SC_MAGNIFICAT, },
+ { "safetywall", SC_SAFETYWALL },
+ { "pneuma", SC_PNEUMA },
+ { "explosionspirits", SC_EXPLOSIONSPIRITS },
+ { "doublecasting", SC_DOUBLECAST },
+ { "volcano", SC_VOLCANO },
+ { "amplify", SC_MAGICPOWER },
+ { "quagmire", SC_QUAGMIRE },
+ { "spiderweb", SC_SPIDERWEB },
+ { "walloffog", SC_FOGWALL },
+ { "energycoat", SC_ENERGYCOAT },
+ { "aspersio", SC_ASPERSIO },
+ { "suffragium", SC_SUFFRAGIUM },
+ { "autoguard", SC_AUTOGUARD },
+ { "reflectshield", SC_REFLECTSHIELD },
+ { "endure", SC_ENDURE },
+ { "defender", SC_DEFENDER },
+ { "kaizel", SC_KAIZEL },
+ { "kaahi", SC_KAAHI },
+ { "kaupe", SC_KAUPE },
+ { "eswoo", SC_SWOO },
+ { "eska", SC_SKA },
+ { "parry", SC_PARRYING },
+ { "concentration", SC_CONCENTRATION },
+ { "aurablade", SC_AURABLADE },
+ { "berserk", SC_ASPDPOTION3 },
+ { "eske", SC_SKE },
+ { "soul", SC_NEN },
+ { "cicada", SC_UTSUSEMI },
+ { "bragi", SC_POEMBRAGI },
+ { "lexaeterna", SC_AETERNA },
+ { "union", SC_FUSION },
+ { "breakfall", SC_DODGE },
+ { "counterkickstance", SC_READYCOUNTER },
+};
+
+const std::unordered_map<std::string, cell_chk> um_cellname2cellid{ // Subset used for mob ai
+ { "cellwater", CELL_CHKWATER},
+ { "basilica", CELL_CHKBASILICA},
+ { "landprotector", CELL_CHKLANDPROTECTOR},
+ { "maelstrom", CELL_CHKMAELSTROM},
+ { "icewall", CELL_CHKICEWALL},
+};
+
+const std::unordered_map<std::string, e_mode> um_modename2modeid{ // Subset used for mob ai. Maybe not all of them are useful.
+ {"none",MD_NONE},
+ {"canmove",MD_CANMOVE},
+ {"looter",MD_LOOTER},
+ {"aggressive",MD_AGGRESSIVE},
+ {"assist",MD_ASSIST},
+ {"castsensoridle",MD_CASTSENSORIDLE},
+ {"norandomwalk",MD_NORANDOMWALK},
+ {"nocast",MD_NOCAST},
+ {"canattack",MD_CANATTACK},
+ {"castsensorchase",MD_CASTSENSORCHASE},
+ {"changechase",MD_CHANGECHASE},
+ {"angry",MD_ANGRY},
+ {"changetargetmelee",MD_CHANGETARGETMELEE},
+ {"changetargetchase",MD_CHANGETARGETCHASE},
+ {"targetweak",MD_TARGETWEAK},
+ {"randomtarget",MD_RANDOMTARGET},
+ {"ignoremelee",MD_IGNOREMELEE},
+ {"ignoremagic",MD_IGNOREMAGIC},
+ {"ignoreranged",MD_IGNORERANGED},
+ {"mvp",MD_MVP},
+ {"ignoremisc",MD_IGNOREMISC},
+ {"knockbackimmune",MD_KNOCKBACKIMMUNE},
+ {"teleportblock",MD_TELEPORTBLOCK},
+ {"fixeditemdrop",MD_FIXEDITEMDROP},
+ {"detector",MD_DETECTOR},
+ {"statusimmune",MD_STATUSIMMUNE},
+ {"skillimmune",MD_SKILLIMMUNE},
+};
+
+const std::unordered_map<std::string, e_mob_skill_target> skill_target_name2enumid{
+ { "target", MST_TARGET },
+ { "randomtarget", MST_RANDOM },
+ { "self", MST_SELF },
+ { "friend", MST_FRIEND },
+ { "master", MST_MASTER },
+ { "around5", MST_AROUND5 },
+ { "around6", MST_AROUND6 },
+ { "around7", MST_AROUND7 },
+ { "around8", MST_AROUND8 },
+ { "around1", MST_AROUND1 },
+ { "around2", MST_AROUND2 },
+ { "around3", MST_AROUND3 },
+ { "around4", MST_AROUND4 },
+ { "around", MST_AROUND },
+};
+
+const std::unordered_map<std::string, e_mob_skill_condition> mob_skill_condition2name{
+ // enum e_mob_skill_condition
+ { "always", MSC_ALWAYS },
+ { "myhpltmaxrate", MSC_MYHPLTMAXRATE },
+ { "myhpinrate", MSC_MYHPINRATE },
+ { "friendhpltmaxrate", MSC_FRIENDHPLTMAXRATE },
+ { "friendhpinrate", MSC_FRIENDHPINRATE },
+ { "mystatuson", MSC_MYSTATUSON },
+ { "mystatusoff", MSC_MYSTATUSOFF },
+ { "masterstatuson", MSC_MASTERSTATUSON },
+ { "masterstatusoff", MSC_MASTERSTATUSOFF },
+ { "friendstatuson", MSC_FRIENDSTATUSON },
+ { "friendstatusoff", MSC_FRIENDSTATUSOFF },
+ { "enemystatuson", MSC_ENEMYSTATUSON },
+ { "enemystatusoff", MSC_ENEMYSTATUSOFF },
+ { "attackpcgt", MSC_ATTACKPCGT },
+ { "attackpcge", MSC_ATTACKPCGE },
+ { "slavelt", MSC_SLAVELT },
+ { "slavele", MSC_SLAVELE },
+ { "closedattacked", MSC_CLOSEDATTACKED },
+ { "longrangeattacked", MSC_LONGRANGEATTACKED },
+ { "skillused", MSC_SKILLUSED },
+ { "afterskill", MSC_AFTERSKILL },
+ { "casttargeted", MSC_CASTTARGETED },
+ { "rudeattacked", MSC_RUDEATTACKED },
+ { "masterhpltmaxrate", MSC_MASTERHPLTMAXRATE },
+ { "masterattacked", MSC_MASTERATTACKED },
+ { "alchemist", MSC_ALCHEMIST },
+ { "onspawn", MSC_SPAWN },
+ { "expanded", MSC_EXPANDED }
+};
+
+const std::unordered_map<std::string, MobSkillState> um_mobskillstatename2id{
+ { "any", MSS_ANY }, //All states except Dead
+ { "idle", MSS_IDLE },
+ { "walk", MSS_WALK },
+ { "loot", MSS_LOOT },
+ { "dead", MSS_DEAD },
+ { "attack", MSS_BERSERK }, //Retaliating attack
+ { "angry", MSS_ANGRY }, //Preemptive attack (aggressive mobs)
+ { "chase", MSS_RUSH }, //Chase escaping target
+ { "follow", MSS_FOLLOW }, //Preemptive chase (aggressive mobs)
+ { "anytarget",MSS_ANYTARGET }, //Berserk+Angry+Rush+Follow
+};
/*==========================================
* Local prototype declaration (only required thing)
@@ -3576,7 +3734,6 @@ int mob_getfriendhprate_sub(struct block_list *bl,va_list ap)
int min_rate, max_rate,rate;
struct block_list **fr;
struct mob_data *md;
-
md = va_arg(ap,struct mob_data *);
min_rate=va_arg(ap,int);
max_rate=va_arg(ap,int);
@@ -3588,26 +3745,19 @@ int mob_getfriendhprate_sub(struct block_list *bl,va_list ap)
if ((*fr) != NULL) //A friend was already found.
return 0;
- if (battle_check_target(&md->bl,bl,BCT_ENEMY)>0)
+ if (battle_check_target(&md->bl,bl,BCT_FRIEND) < 0) // BCT_ENEMY) > 0 can make hostile mobs use heal on friendly revived and still immune characters
return 0;
rate = get_percentage(status_get_hp(bl), status_get_max_hp(bl));
-
if (rate >= min_rate && rate <= max_rate)
(*fr) = bl;
return 1;
}
static struct block_list *mob_getfriendhprate(struct mob_data *md,int min_rate,int max_rate)
{
- struct block_list *fr=NULL;
- int type = BL_MOB;
-
+ struct block_list* fr = NULL;
nullpo_retr(NULL, md);
-
- if (md->special_state.ai) //Summoned creatures. [Skotlex]
- type = BL_PC;
-
- map_foreachinallrange(mob_getfriendhprate_sub, &md->bl, 8, type,md,min_rate,max_rate,&fr);
+ map_foreachinallrange(mob_getfriendhprate_sub, &md->bl, 8, BL_PC | BL_MOB | BL_MER, md, min_rate, max_rate, &fr);
return fr;
}
/*==========================================
@@ -3624,50 +3774,102 @@ struct block_list *mob_getmasterhpltmaxrate(struct mob_data *md,int rate)
return NULL;
}
+
+
+
/*==========================================
* What a status state suits by nearby MOB is looked for.
*------------------------------------------*/
int mob_getfriendstatus_sub(struct block_list *bl,va_list ap)
{
- int cond1,cond2;
- struct mob_data **fr, *md, *mmd;
- int flag=0;
-
+ struct mob_data *md;
+ struct block_list **fr;
nullpo_ret(bl);
- nullpo_ret(md=(struct mob_data *)bl);
- nullpo_ret(mmd=va_arg(ap,struct mob_data *));
-
- if( mmd->bl.id == bl->id && !(battle_config.mob_ai&0x10) )
+ nullpo_ret(md = va_arg(ap, struct mob_data *));
+ bool flag = 0;
+ int cond1, cond2;
+ cond1 = va_arg(ap, int);
+ cond2 = va_arg(ap, int);
+ if (md->bl.id == bl->id && !(battle_config.mob_ai & 0x10))
return 0;
-
- if (battle_check_target(&mmd->bl,bl,BCT_ENEMY)>0)
+ if (status_isdead(bl))
+ return 0;
+ if (battle_check_target(&md->bl, bl, BCT_FRIEND) < 0) // &md->bl is the src, bl is the target, *fr will be the selected ally.
return 0;
- cond1=va_arg(ap,int);
- cond2=va_arg(ap,int);
- fr=va_arg(ap,struct mob_data **);
- if( cond2==-1 ){
+ nullpo_ret(fr = va_arg(ap, struct block_list **));
+ if (cond2 == -1) {
int j;
- for(j=SC_COMMON_MIN;j<=SC_COMMON_MAX && !flag;j++){
- if ((flag=(md->sc.data[j] != NULL))) //Once an effect was found, break out. [Skotlex]
+ for (j = SC_COMMON_MIN; j <= SC_COMMON_MAX && !flag; j++) {
+ if ((flag = (status_get_sc(bl)->data[j] != NULL))) //Once an effect was found, break out. [Skotlex]
break;
}
- }else
- flag=( md->sc.data[cond2] != NULL );
- if( flag^( cond1==MSC_FRIENDSTATUSOFF ) )
- (*fr)=md;
-
+ } else {
+ flag = (status_get_sc(bl)->data[cond2] != NULL);
+ }
+ if (flag ^ (cond1 == MSC_FRIENDSTATUSOFF))
+ (*fr) = bl;
return 0;
}
-
-struct mob_data *mob_getfriendstatus(struct mob_data *md,int cond1,int cond2)
+struct block_list *mob_getfriendstatus(struct mob_data *md, short cond1, short cond2)
{
- struct mob_data* fr = NULL;
+
+ struct block_list* fr = NULL;
nullpo_ret(md);
- map_foreachinallrange(mob_getfriendstatus_sub, &md->bl, 8,BL_MOB, md,cond1,cond2,&fr);
+ map_foreachinallrange(mob_getfriendstatus_sub, &md->bl, 8, BL_PC | BL_MOB | BL_MER, md, cond1,cond2, &fr); //vararg at md,cond1,cond2,&fr giving it to sub
return fr;
}
+int mob_get_under_condition_sub(struct block_list* bl, va_list ap) {
+ nullpo_ret(bl);
+
+ struct block_list** bl_list;
+ nullpo_ret(bl_list = va_arg(ap, struct block_list**));
+ int* c;
+ nullpo_ret(c = va_arg(ap, int*));
+ struct mob_data* md;
+ nullpo_ret(md = va_arg(ap, struct mob_data*));
+
+ std::shared_ptr<expanded_ai::ExpandedCondition> expanded_condition = va_arg(ap, std::shared_ptr<expanded_ai::ExpandedCondition>);
+ if (*c >= 24)
+ return 0;
+ if (md->bl.id == bl->id && !(battle_config.mob_ai & 0x10))
+ return 0;
+ if (status_isdead(bl))
+ return 0;
+ using map_t = ::std::map<e_mob_skill_target, block_list*>;
+ std::map<e_mob_skill_target, block_list*> targets = va_arg(ap,map_t);
+ e_battle_check_target btarget = static_cast<enum e_battle_check_target>(va_arg(ap, int));
+
+ if (battle_check_target(&md->bl, bl, btarget) < 0) // &md->bl is the src, bl is the target
+ return 0;
+ if(btarget== BCT_ENEMY)
+ targets[MST_TARGET] = bl;
+ if (btarget == BCT_FRIEND)
+ targets[MST_FRIEND] = bl;
+ if ((*expanded_condition)(targets)) // Starts the loop amongst expanded conditions
+ bl_list[(*c)++] = bl;
+
+ return 0;
+}
+
+
+bool mob_get_all_under_condition(struct mob_data* md, int range, std::shared_ptr<expanded_ai::ExpandedCondition> expanded_condition,
+ std::map<e_mob_skill_target, block_list*> targets, e_battle_check_target target_faction, block_list** bl_list, int* c,int min, int max) {
+
+ map_foreachinallrange(mob_get_under_condition_sub, &md->bl, range, BL_PC | BL_MOB | BL_MER, bl_list,c, md, expanded_condition, targets, target_faction);
+ if (*c == 0)
+ return false;
+ if (*c > 24)
+ *c = 24;
+
+ return true;
+
+}
+
+
+
+
// Display message from mob_chat_db.yml
bool mob_chat_display_message(mob_data &md, uint16 msg_id) {
std::shared_ptr<s_mob_chat> mc = mob_chat_db.find(msg_id);
@@ -3691,16 +3893,18 @@ bool mob_chat_display_message(mob_data &md, uint16 msg_id) {
*------------------------------------------*/
int mobskill_use(struct mob_data *md, t_tick tick, int event)
{
- struct block_list *fbl = NULL; //Friend bl, which can either be a BL_PC or BL_MOB depending on the situation. [Skotlex]
- struct block_list *bl;
- struct mob_data *fmd = NULL;
- int i,j,n;
- short skill_target;
+ struct block_list *fbl = nullptr; //Friend bl, which can either be a BL_PC or BL_MOB depending on the situation. [Skotlex]
+ struct block_list *mbl= nullptr; // Master bl //
+ struct block_list* tbl = nullptr; // Mob's current target
+ struct block_list* bl = nullptr; // Skill final target
+
+ e_mob_skill_target skill_target;
nullpo_ret(md);
std::vector<std::shared_ptr<s_mob_skill>> &ms = md->db->skill;
+
if (!battle_config.mob_skill_rate || md->ud.skilltimer != INVALID_TIMER || ms.empty() || status_has_mode(&md->status,MD_NOCAST))
return 0;
@@ -3708,17 +3912,23 @@ int mobskill_use(struct mob_data *md, t_tick tick, int event)
return 0; //Skill act delay only affects non-event skills.
//Pick a starting position and loop from that.
- i = battle_config.mob_ai&0x100?rnd()%ms.size():0;
- for (n = 0; n < ms.size(); i++, n++) {
- int c2, flag = 0;
-
+ int i = battle_config.mob_ai&0x100?rnd()%ms.size():0;
+ for (int n = 0; n < ms.size(); i++, n++) {
+ bl = nullptr;
+ tbl = nullptr;
+ fbl = nullptr;
+ mbl = nullptr;
+ int flag=0;
+ int c2 =ms[i]->cond2;
+
if (i == ms.size())
i = 0;
+ skill_target= status_has_mode(&md->db->status, MD_RANDOMTARGET) ? MST_RANDOM:ms[i]->target;
if (DIFF_TICK(tick, md->skilldelay[i]) < ms[i]->delay)
continue;
- c2 = ms[i]->cond2;
+
if (ms[i]->state != md->state.skillstate) {
if (md->state.skillstate != MSS_DEAD && (ms[i]->state == MSS_ANY ||
@@ -3735,108 +3945,203 @@ int mobskill_use(struct mob_data *md, t_tick tick, int event)
flag = 1; //Trigger skill.
else if (ms[i]->cond1 == MSC_SKILLUSED)
flag = ((event & 0xffff) == MSC_SKILLUSED && ((event >> 16) == c2 || c2 == 0));
- else if(event == -1){
+ else if(event == -1) {
//Avoid entering on defined events to avoid "hyper-active skill use" due to the overflow of calls to this function in battle.
+
switch (ms[i]->cond1)
{
- case MSC_ALWAYS:
- flag = 1; break;
- case MSC_MYHPLTMAXRATE: // HP< maxhp%
- flag = get_percentage(md->status.hp, md->status.max_hp);
- flag = (flag <= c2);
- break;
- case MSC_MYHPINRATE:
- flag = get_percentage(md->status.hp, md->status.max_hp);
- flag = (flag >= c2 && flag <= ms[i]->val[0]);
- break;
- case MSC_MYSTATUSON: // status[num] on
- case MSC_MYSTATUSOFF: // status[num] off
- if (!md->sc.count) {
+ case MSC_ALWAYS:
+ flag = 1; break;
+ case MSC_MYHPLTMAXRATE: // HP< maxhp%
+ flag = get_percentage(md->status.hp, md->status.max_hp);
+ flag = (flag <= c2);
+ break;
+ case MSC_MYHPINRATE:
+ flag = get_percentage(md->status.hp, md->status.max_hp);
+ flag = (flag >= c2 && flag <= ms[i]->val[0]);
+ break;
+ case MSC_MYSTATUSON: // status[num] on
+ case MSC_MYSTATUSOFF: // status[num] off
+ if (!md->sc.count) {
+ flag = 0;
+ } else if (ms[i]->cond2 == -1) {
+ for (int j = SC_COMMON_MIN; j <= SC_COMMON_MAX; j++)
+ if ((flag = (md->sc.data[j] != NULL)) != 0)
+ break;
+ } else {
+ flag = (md->sc.data[ms[i]->cond2] != NULL);
+ }
+ flag ^= (ms[i]->cond1 == MSC_MYSTATUSOFF); break;
+ break;
+ case MSC_FRIENDHPLTMAXRATE: // friend HP < maxhp%
+ flag = ((fbl = mob_getfriendhprate(md, 0, c2)) != NULL); break;
+ case MSC_FRIENDHPINRATE:
+ flag = ((fbl = mob_getfriendhprate(md, c2, ms[i]->val[0])) != NULL); break;
+ case MSC_FRIENDSTATUSON: // friend status[num] on
+ case MSC_FRIENDSTATUSOFF: // friend status[num] off
+ flag = ((fbl = mob_getfriendstatus(md, ms[i]->cond1, ms[i]->cond2)) != NULL); break;
+ break;
+ case MSC_ENEMYSTATUSON: // enemy status[num] on
+ case MSC_ENEMYSTATUSOFF: // enemy status[num] off
+ if (skill_target == MST_RANDOM) //Pick a random enemy within skill range.
+ bl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),
+ skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true));
+ else
+ bl = map_id2bl(md->target_id);
+ if (bl) {
+ flag = status_get_sc(bl)->data[ms[i]->cond2] != NULL;
+ flag ^= (ms[i]->cond1 == MSC_ENEMYSTATUSOFF); break;
+ } else
+ flag = 0;
+ break;
+ case MSC_SLAVELT: // slave < num
+ flag = (mob_countslave(&md->bl) < c2); break;
+ case MSC_ATTACKPCGT: // attack pc > num
+ flag = (unit_counttargeted(&md->bl) > c2); break;
+ case MSC_SLAVELE: // slave <= num
+ flag = (mob_countslave(&md->bl) <= c2); break;
+ case MSC_ATTACKPCGE: // attack pc >= num
+ flag = (unit_counttargeted(&md->bl) >= c2); break;
+ case MSC_AFTERSKILL:
+ flag = (md->ud.skill_id == c2); break;
+ case MSC_RUDEATTACKED:
+ flag = (md->state.attacked_count >= RUDE_ATTACKED_COUNT);
+ if (flag) md->state.attacked_count = 0; //Rude attacked count should be reset after the skill condition is met. Thanks to Komurka [Skotlex]
+ break;
+ case MSC_MASTERHPLTMAXRATE:
+ flag = ((mbl = mob_getmasterhpltmaxrate(md, c2)) != NULL); break;
+ case MSC_MASTERSTATUSON: // friend status[num] on
+ case MSC_MASTERSTATUSOFF: // friend status[num] off
+ if (mbl != nullptr) {
+ if (!status_get_sc(mbl)->count) {
flag = 0;
} else if (ms[i]->cond2 == -1) {
- for (j = SC_COMMON_MIN; j <= SC_COMMON_MAX; j++)
- if ((flag = (md->sc.data[j]!=NULL)) != 0)
+ for (int j = SC_COMMON_MIN; j <= SC_COMMON_MAX; j++)
+ if ((flag = (status_get_sc(mbl)->data[j] != NULL)) != 0)
break;
} else {
- flag = (md->sc.data[ms[i]->cond2]!=NULL);
+ flag = (status_get_sc(mbl)->data[ms[i]->cond2] != NULL);
}
- flag ^= (ms[i]->cond1 == MSC_MYSTATUSOFF); break;
- case MSC_FRIENDHPLTMAXRATE: // friend HP < maxhp%
- flag = ((fbl = mob_getfriendhprate(md, 0, ms[i]->cond2)) != NULL); break;
- case MSC_FRIENDHPINRATE :
- flag = ((fbl = mob_getfriendhprate(md, ms[i]->cond2, ms[i]->val[0])) != NULL); break;
- case MSC_FRIENDSTATUSON: // friend status[num] on
- case MSC_FRIENDSTATUSOFF: // friend status[num] off
- flag = ((fmd = mob_getfriendstatus(md, ms[i]->cond1, ms[i]->cond2)) != NULL); break;
- case MSC_SLAVELT: // slave < num
- flag = (mob_countslave(&md->bl) < c2 ); break;
- case MSC_ATTACKPCGT: // attack pc > num
- flag = (unit_counttargeted(&md->bl) > c2); break;
- case MSC_SLAVELE: // slave <= num
- flag = (mob_countslave(&md->bl) <= c2 ); break;
- case MSC_ATTACKPCGE: // attack pc >= num
- flag = (unit_counttargeted(&md->bl) >= c2); break;
- case MSC_AFTERSKILL:
- flag = (md->ud.skill_id == c2); break;
- case MSC_RUDEATTACKED:
- flag = (md->state.attacked_count >= RUDE_ATTACKED_COUNT);
- if (flag) md->state.attacked_count = 0; //Rude attacked count should be reset after the skill condition is met. Thanks to Komurka [Skotlex]
- break;
- case MSC_MASTERHPLTMAXRATE:
- flag = ((fbl = mob_getmasterhpltmaxrate(md, ms[i]->cond2)) != NULL); break;
- case MSC_MASTERATTACKED:
- flag = (md->master_id > 0 && (fbl=map_id2bl(md->master_id)) && unit_counttargeted(fbl) > 0); break;
- case MSC_ALCHEMIST:
- flag = (md->state.alchemist);
- break;
+ flag ^= (ms[i]->cond1 == MSC_MASTERSTATUSOFF);
+ } else
+ continue;
+ break;
+ case MSC_MASTERATTACKED:
+ flag = (md->master_id > 0 && (mbl = map_id2bl(md->master_id)) && unit_counttargeted(mbl) > 0); break;
+ case MSC_ALCHEMIST:
+ flag = (md->state.alchemist);
+ break;
+ case MSC_EXPANDED:
+ {
+ std::map<e_mob_skill_target, block_list*> targets;
+ if ((tbl = map_id2bl(md->target_id)) != NULL)
+ targets[MST_TARGET] = tbl;
+ if ((mbl = map_id2bl(md->master_id)) != NULL)
+ targets[MST_MASTER] = mbl;
+ targets[MST_SELF] = &md->bl;
+ std::shared_ptr<expanded_ai::ExpandedCondition> expanded_condition = mob_expanded_ai_conditions_db.find(ms[i]->expanded_cond);
+
+ if ((expanded_condition) != nullptr) {
+ int minmobs = ms[i]->val[0];
+ int maxmobs = ms[i]->val[1];
+ int qts = 0;
+ const int mob_limit = 24;
+ int skill_range = skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true);
+ struct block_list* bl_list[mob_limit];
+ memset(bl_list, 0, sizeof(bl_list));
+
+ e_battle_check_target e_battle = BCT_ENEMY;
+ switch (skill_target) {
+ case MST_FRIEND:
+ e_battle = BCT_FRIEND;
+ case MST_RANDOM:
+ minmobs = minmobs >= 1 ? minmobs : 1;
+ maxmobs = maxmobs >= 1 ? maxmobs : mob_limit;
+ if (maxmobs < minmobs)
+ maxmobs = minmobs = 1;
+
+ if (mob_get_all_under_condition(md, skill_range,expanded_condition, targets, e_battle, bl_list,&qts,minmobs,maxmobs)
+ && qts >= minmobs
+ && qts <= maxmobs)
+ {
+ bl = bl_list[rnd() % qts];
+ flag = 1;
+ } else
+ flag = 0;
+ break;
+ case MST_MASTER:
+ if (mbl == nullptr)
+ continue;
+ bl = mbl;
+ flag = (*expanded_condition)(targets);
+ break;
+ case MST_SELF:
+ bl = &md->bl;
+ flag = (*expanded_condition)(targets);
+ break;
+ default:
+ bl = tbl;
+ flag = (*expanded_condition)(targets);
+ break;
+ }
+ } else {
+ ShowError("mob_skill_db:Invalid expanded condition '%s' for mobid %d skill %d-removing from db\n", ms[i]->expanded_cond, md->mob_id, ms[i]->skill_id);
+ md->db->skill.erase(md->db->skill.begin() + i);
+ flag = 0;
+ }
+ break;
+ }
}
}
-
+
if (!flag)
continue; //Skill requisite failed to be fulfilled.
- //Execute skill
- skill_target = status_has_mode(&md->db->status,MD_RANDOMTARGET) ? MST_RANDOM : ms[i]->target;
- if (skill_get_casttype(ms[i]->skill_id) == CAST_GROUND)
- { //Ground skill.
- short x, y;
- switch (skill_target) {
- case MST_RANDOM: //Pick a random enemy within skill range.
- bl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),
- skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true));
- break;
- case MST_TARGET:
- case MST_AROUND5:
- case MST_AROUND6:
- case MST_AROUND7:
- case MST_AROUND8:
- bl = map_id2bl(md->target_id);
- break;
- case MST_MASTER:
- bl = &md->bl;
- if (md->master_id)
- bl = map_id2bl(md->master_id);
- if (bl) //Otherwise, fall through.
- break;
- case MST_FRIEND:
- bl = fbl?fbl:(fmd?&fmd->bl:&md->bl);
- break;
- default:
- bl = &md->bl;
- break;
- }
- if (!bl) continue;
+ //Execute skill
+ switch (skill_target) {
+ case MST_RANDOM://Pick a random enemy within skill range.
+
+ bl = bl ? bl : battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),
+ skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true));
+ break;
+ case MST_TARGET:
+ case MST_AROUND5:
+ case MST_AROUND6:
+ case MST_AROUND7:
+ case MST_AROUND8:
+ bl = bl ? bl : map_id2bl(md->target_id);
+ break;
+ case MST_MASTER:
+ if (md->master_id)
+ bl = bl ? bl : (mbl ? mbl : map_id2bl(md->master_id));
+ else
+ continue;
+ break;
+ case MST_FRIEND:
+ bl = bl ? bl : fbl;
+ break;
+ default:
+ bl = &md->bl;
+ break;
+ }
+
+ if (!bl)
+ continue;
+ md->skill_idx = i;
+ if (skill_get_casttype(ms[i]->skill_id) == CAST_GROUND) {
+ //Ground skill.
+ short x, y;
x = bl->x;
- y = bl->y;
+ y = bl->y;
// Look for an area to cast the spell around...
if (skill_target >= MST_AROUND5) {
- j = skill_target >= MST_AROUND1?
- (skill_target-MST_AROUND1) +1:
- (skill_target-MST_AROUND5) +1;
+ int j = skill_target >= MST_AROUND1 ?
+ (skill_target - MST_AROUND1) + 1 :
+ (skill_target - MST_AROUND5) + 1;
map_search_freecell(&md->bl, md->bl.m, &x, &y, j, j, 3);
}
- md->skill_idx = i;
+
map_freeblock_lock();
if (!battle_check_range(&md->bl, bl, skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true)) ||
!unit_skilluse_pos2(&md->bl, x, y, ms[i]->skill_id, ms[i]->skill_lv, ms[i]->casttime, ms[i]->cancel))
@@ -3844,37 +4149,9 @@ int mobskill_use(struct mob_data *md, t_tick tick, int event)
map_freeblock_unlock();
continue;
}
- } else {
- //Targetted skill
- switch (skill_target) {
- case MST_RANDOM: //Pick a random enemy within skill range.
- bl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md),
- skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true));
- break;
- case MST_TARGET:
- bl = map_id2bl(md->target_id);
- break;
- case MST_MASTER:
- bl = &md->bl;
- if (md->master_id)
- bl = map_id2bl(md->master_id);
- if (bl) //Otherwise, fall through.
- break;
- case MST_FRIEND:
- if (fbl) {
- bl = fbl;
- break;
- } else if (fmd) {
- bl = &fmd->bl;
- break;
- } // else fall through
- default:
- bl = &md->bl;
- break;
- }
- if (!bl) continue;
+ }
+ else { //Targetted skill
- md->skill_idx = i;
map_freeblock_lock();
if (!battle_check_range(&md->bl, bl, skill_get_range2(&md->bl, ms[i]->skill_id, ms[i]->skill_lv, true)) ||
!unit_skilluse_id2(&md->bl, bl->id, ms[i]->skill_id, ms[i]->skill_lv, ms[i]->casttime, ms[i]->cancel))
@@ -3882,13 +4159,15 @@ int mobskill_use(struct mob_data *md, t_tick tick, int event)
map_freeblock_unlock();
continue;
}
+
}
+
//Skill used. Post-setups...
- if ( ms[i]->msg_id ){ //Display color message [SnakeDrak]
+ if (ms[i]->msg_id) { //Display color message [SnakeDrak]
mob_chat_display_message(*md, ms[i]->msg_id);
}
- if(!(battle_config.mob_ai&0x200)) { //pass on delay to same skill.
- for (j = 0; j < ms.size(); j++)
+ if (!(battle_config.mob_ai & 0x200)) { //pass on delay to same skill.
+ for (int j = 0; j < ms.size(); j++)
if (ms[j]->skill_id == ms[i]->skill_id)
md->skilldelay[j]=tick;
} else
@@ -3969,7 +4248,7 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons
int inf, fd;
struct mob_data *md;
struct status_data *status;
-
+
nullpo_ret(sd);
if(pc_isdead(sd) && master_id && flag&1)
@@ -5094,6 +5373,8 @@ static bool mob_read_sqldb_sub(std::vector<std::string> str) {
modes["NoCast"] = std::stoi(str[index]) ? "true" : "false";
if (!str[++index].empty())
modes["CanAttack"] = std::stoi(str[index]) ? "true" : "false";
+ if (!str[++index].empty())
+ modes["SkillOnly"] = std::stoi(str[index]) ? "true" : "false";
if (!str[++index].empty())
modes["CastSensorChase"] = std::stoi(str[index]) ? "true" : "false";
if (!str[++index].empty())
@@ -5122,6 +5403,8 @@ static bool mob_read_sqldb_sub(std::vector<std::string> str) {
modes["KnockBackImmune"] = std::stoi(str[index]) ? "true" : "false";
if (!str[++index].empty())
modes["TeleportBlock"] = std::stoi(str[index]) ? "true" : "false";
+ if (!str[++index].empty())
+ modes["PcSkillBehavior"] = std::stoi(str[index]) ? "true" : "false";
if (!str[++index].empty())
modes["FixedItemDrop"] = std::stoi(str[index]) ? "true" : "false";
if (!str[++index].empty())
@@ -5643,6 +5926,383 @@ uint64 MobSummonDatabase::parseBodyNode(const YAML::Node &node) {
return 1;
}
+namespace expanded_ai {
+using YAML::Node;
+using namespace std;
+using namespace logic_gates;
+
+template <class TPredicate>
+bool SingleCondition<TPredicate>::operator()(const std::map<e_mob_skill_target, block_list*>& targets) const {
+ return inverter((*predicate)(targets));
+}
+
+bool ConditionContainer::operator()(const std::map<e_mob_skill_target, block_list*>& targets) const {
+ return (*logicGate)(nodes, targets);
+}
+
+namespace {
+
+bool fillWithStatus(string& status,string& suffix, const string& predicate) {
+ set<string> available_suffix = { "","resist"};
+ for(const auto& it: um_statusname2id) {
+ string status_str=it.first;
+ if((predicate.find(status_str)) != string::npos) {//ex: search for "stun"
+ suffix= predicate.substr(status_str.length());
+ if (!available_suffix.count(suffix))
+ return false;
+ status=it.first;
+ return true;
+ }
+ }
+ return false;
+}
+bool fillWithElement(string& element, string& suffix, const string& predicate) {
+ set<string> available_suffix = { "","resist","attack","armor"};
+ for (const auto& pair : um_eleid2elename) {
+ char firstLetter = tolower(pair.first[0]);
+ string ele_str = firstLetter + pair.first.substr(1); //ex: neutral,water
+ if ((predicate.find(ele_str)) != string::npos) { // is an element condition
+ suffix = predicate.substr(ele_str.length());
+ if (!available_suffix.count(suffix))
+ return false;
+ element = pair.first;
+ return true;
+ }
+ }
+ return false;
+}
+
+http://www.martinbroadhurst.com/how-to-split-a-string-in-c.html
+template <class Container>
+void split(const std::string& str, Container& cont, char delim) {
+ std::size_t current, previous = 0;
+ current = str.find(delim);
+ while (current != std::string::npos) {
+ cont.push_back(str.substr(previous, current - previous));
+ previous = current + 1;
+ current = str.find(delim, previous);
+ }
+ cont.push_back(str.substr(previous, current - previous));
+}
+
+bool splitComposedPredicates(const string& predicate, unordered_map<string,string>& parsed_fields) {
+ string prefix, suffix;
+
+ if (fillWithElement(prefix, suffix, predicate)) {
+ parsed_fields.insert({ "predicate", "element" + suffix }); // example: parsed_fields.at("predicate") == "elementattack"
+ parsed_fields.insert({ "element", prefix }); // example: parsed_fields.at("element") == "water"
+ return true;
+ }
+
+ if (fillWithStatus(prefix, suffix, predicate)) {
+ parsed_fields.insert({ "predicate", "status" + suffix }); // example:parsed_fields.at("predicate") == "statusresist"
+ parsed_fields.insert({ "status", prefix }); // example:parsed_fields.at("status") == "stun"
+ return true;
+ }
+ return false;
+}
+std::pair<string, int> parse_comparator_and_value(const string& s) {
+ int value;
+ std::pair<string, int> cvpair;
+ std::size_t valueEndPos = s.find('%');
+
+ if (s[0] == '>') {
+ if (s[1] == '=') {
+ value = std::stoi(s.substr(2, valueEndPos)); // offset 2
+ cvpair = std::pair<string, int>("greater_equal", value);
+ } else {
+ value = std::stoi(s.substr(1, valueEndPos));
+ cvpair = std::pair<string, int>("greater", value);
+ }
+ } else if (s[0] == '<') {
+ if (s[1] == '=') {
+ value = std::stoi(s.substr(2, valueEndPos));
+ cvpair = std::pair<string, int>("less_equal", value);
+ } else {
+ value = std::stoi(s.substr(1, valueEndPos));
+ cvpair = std::pair<string, int>("less", value);
+ }
+ } else if (s[0] == '=') {
+ value = std::stoi(s.substr(1, valueEndPos));
+ cvpair = std::pair<string, int>("equal_to", value);
+ } else {
+ value = std::stoi(s.substr(0, valueEndPos)); // offset 2
+ cvpair = std::pair<string, int>("equal_to", value);
+ }
+
+ return cvpair;
+
+}
+
+inverter_t createInverter(unordered_map<string, string>& parsedFields) {
+ if(parsedFields.at("inverter") == "not")
+ return [](bool invertedResult) {return !invertedResult; };
+ else
+ return [](bool keptResult) {return keptResult; };
+
+}
+
+shared_ptr<predicates::condition_predicate> createConditionPredicate(unordered_map<string, string>& parsed_fields) {
+ using comparator_t = std::function<bool(int, int)>;
+
+ const std::unordered_map<std::string, comparator_t> um_str2comparators{
+ {"greater_equal",std::greater_equal<int>()},
+ {"greater" ,std::greater<int>()},
+ { "less_equal",std::less_equal<int>()},
+ { "less" ,std::less<int>() },
+ { "equal_to" ,std::equal_to<int>() },
+ };
+
+ comparator_t comparator = um_str2comparators.at(parsed_fields.at("comparator"));
+ int value = stoi(parsed_fields.at("value"));
+ e_mob_skill_target target = skill_target_name2enumid.at(parsed_fields.at("target"));
+ string& predicate_str = parsed_fields.at("predicate");
+ if (predicate_str == "exists")
+ return make_shared<predicates::exists>(target);
+ else if (predicate_str == "percenthealth") {
+ return make_shared<predicates::percent_health<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "health") {
+ return make_shared<predicates::health<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "cell") {
+ return make_shared<predicates::cell>(target, um_cellname2cellid.at(parsed_fields.at("cell")));
+ } else if (predicate_str == "job") {
+ return make_shared<predicates::job<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "range") {
+ return make_shared<predicates::attack_range<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "distance_from_self") {
+ return make_shared<predicates::distance_from_self<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "distance_from_friend") {
+ return make_shared<predicates::distance_from_friend<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "distance_from_master") {
+ return make_shared<predicates::distance_from_master<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "status") { // status
+ return make_shared<predicates::status>(target, um_statusname2id.at(parsed_fields.at("status")));
+ } else if (predicate_str == "statusresist") { // status
+ return make_shared<predicates::status_resist<comparator_t>>(target, comparator, um_statusname2id.at(parsed_fields.at("status")), value);
+ } else if (predicate_str == "reflectphy") {
+ return make_shared<predicates::physical_reflect<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "elementarmor" || predicate_str == "element") {
+ return make_shared<predicates::element_armor<comparator_t>>(target, comparator, um_eleid2elename.at(parsed_fields.at("element")), value);
+ } else if (predicate_str == "elementattack") {
+ return make_shared<predicates::element_attack>(target, um_eleid2elename.at(parsed_fields.at("element")));
+ } else if (predicate_str == "elementresist") {
+ return make_shared<predicates::element_resist<comparator_t>>(target, comparator, um_eleid2elename.at(parsed_fields.at("element")), value);
+ } else if (predicate_str == "attack") {
+ return make_shared<predicates::attack<comparator_t>>(target, comparator, value);
+ } else if (predicate_str == "mode") {
+ e_mode mode = um_modename2modeid.at(parsed_fields.at("mode"));
+ if (mode == MD_KNOCKBACKIMMUNE)
+ return make_shared<predicates::knockback>(target);
+ return make_shared<predicates::mode>(target, mode);
+ }
+ throw ExpandedConditionParsingError("wrong target " + predicate_str);
+}
+shared_ptr<SingleCondition<predicates::condition_predicate>> createSingleCondition(unordered_map<string, string>& parsed_fields) {
+ inverter_t inverter = createInverter(parsed_fields);
+ shared_ptr<predicates::condition_predicate> predicate = createConditionPredicate(parsed_fields);
+ return make_shared<SingleCondition<predicates::condition_predicate>>(parsed_fields.at("predicate"), inverter, predicate);
+}
+
+shared_ptr<SingleCondition<ExpandedCondition>> findIfExistsInDatabase(unordered_map<string, string>& parsedFields) {
+ std::shared_ptr<ExpandedCondition> storedCondition;
+ shared_ptr<SingleCondition<ExpandedCondition>> wrappedCondition;
+ if (parsedFields.count("stored")) {
+ storedCondition = mob_expanded_ai_conditions_db.find(parsedFields.at("stored"));
+ wrappedCondition = make_shared<SingleCondition<ExpandedCondition>>(storedCondition->name, createInverter(parsedFields), storedCondition);
+ }
+ return wrappedCondition;
+}
+}// namespace
+
+unordered_map<string, string> parseFields(const string& line) {
+
+ unordered_map<string, string> parsed_fields;
+ vector<string> container;
+ split<vector<string>>(line, container, ' ');
+
+ if (container.size() > 4 || container.size() < 1) {
+ throw ExpandedConditionParsingError(line);
+ }
+ if (container.size() == 1) {
+ if (mob_expanded_ai_conditions_db.find(container[0]) == nullptr)
+ throw ExpandedConditionParsingError(container[0]);
+ parsed_fields.insert({ "stored",container[0] });
+ parsed_fields.insert({ "inverter","disabled"});
+ return parsed_fields;
+ } else if (container.size() == 2 && container[0] == "not") {
+ if (mob_expanded_ai_conditions_db.find(container[1]) == nullptr)
+ throw ExpandedConditionParsingError(container[1]);
+ parsed_fields.insert({ "stored",container[1] });
+ parsed_fields.insert({ "inverter","not"});
+ return parsed_fields;
+ }
+ if (container[0] == "not") {
+ parsed_fields.insert({ "inverter", "not" });
+ container.erase(container.begin());
+ } else if (container.size() >= 4) {
+ throw ExpandedConditionParsingError(line);
+ } else
+ parsed_fields.insert({ "inverter", "disabled" });
+
+ std::string& target_str = container[0];
+ if (target_str == "enemy")
+ target_str = "target"; // means current target
+ else
+ target_str = container[0];
+
+ std::pair<string, int> pairComparatorValue;
+ string value_str;
+ if (container.size() == 3) {
+ try {
+ pairComparatorValue = parse_comparator_and_value(container[2]);
+ }
+ catch (const std::invalid_argument&) {
+ throw ExpandedConditionParsingError(line);
+ }
+ parsed_fields.insert({ "value", to_string(pairComparatorValue.second) });
+ parsed_fields.insert({ "comparator", pairComparatorValue.first });
+ } else {
+ parsed_fields.insert({ "value", "0" });
+ parsed_fields.insert({ "comparator","greater" });
+ }
+ if (skill_target_name2enumid.count(target_str))
+ parsed_fields.insert({ "target", target_str });
+ else
+ throw ExpandedConditionParsingError("wrong target " + target_str + " '" + line + "'");
+
+ const std::set<string> simple_predicates
+ {
+ "exists",
+ "percenthealth",
+ "health",
+ "job",
+ "range",
+ "distance_from_self",
+ "distance_from_friend",
+ "distance_from_master",
+ "reflectphy",
+ "attack",
+ "mode"
+ };
+
+ std::string predicate = container[1];
+
+ if (simple_predicates.count(predicate))
+ parsed_fields.insert({ "predicate", predicate }); // example:parsed_fields.at("predicate") == "percenthealth"
+ else if (um_modename2modeid.count(predicate)) { // modifies predicate
+ parsed_fields.insert({ "predicate", "mode"});
+ parsed_fields.insert({ "mode", predicate });
+ } else if (um_cellname2cellid.count(predicate)) {
+ parsed_fields.insert({ "predicate", "cell" });
+ parsed_fields.insert({ "cell", predicate });
+ } else if (!splitComposedPredicates(predicate, parsed_fields))
+ throw ExpandedConditionParsingError("wrong field " + predicate + " '" + line + "'");
+ return parsed_fields;
+}
+
+shared_ptr<LogicGate> LogicGate::getLogicGate(const string& name) {
+ static const std::unordered_map<string, shared_ptr<LogicGate>> um_logic_gates{
+ {"and" ,std::make_shared<LogicalAND>()},
+ {"or" ,std::make_shared<LogicalOR>()},
+ {"nand" ,std::make_shared<LogicalNAND>()},
+ {"nor" ,std::make_shared<LogicalNOR>()},
+ {"xor" ,std::make_shared<LogicalXOR>()},
+ {"nxor" ,std::make_shared<LogicalNXOR>()},
+ };
+ return um_logic_gates.at(name);
+}
+
+shared_ptr<ExpandedCondition> ExpandedCondition::createExpandedCondition(const YAML::Node& node) {
+
+ if (node.IsMap()) {
+ // All maps should be logic gates so a new ConditionContainer is created
+ shared_ptr<ConditionContainer> conditionContainer;
+ const string& lGateName = node.begin()->first.as<string>(); // there is only one pair per map
+ try {
+
+ conditionContainer = make_shared<ConditionContainer>(lGateName, LogicGate::getLogicGate(lGateName));
+ const auto& sequenceOfExpandedConditions = node.begin()->second.as<Node>();
+ conditionContainer->parseAndPushBackExpandedConditions(sequenceOfExpandedConditions);
+ return conditionContainer;
+ }
+ catch (exception e) {
+ throw ExpandedConditionParsingError(lGateName);
+ };
+ } else {
+ unordered_map<string, string> parsedFields = parseFields(node.as<string>());
+
+ //First search for an existing container and wrap it inside a SingleCondition
+ shared_ptr<SingleCondition<ExpandedCondition>> storedCondition = findIfExistsInDatabase(parsedFields);
+ if (storedCondition != nullptr)
+ return storedCondition;
+
+ else {
+ // Try to parse the node into a single condition test;
+ shared_ptr<SingleCondition<predicates::condition_predicate>> singleCondition = createSingleCondition(parsedFields);
+ return singleCondition;
+ }
+ }
+}
+
+void ConditionContainer::parseAndPushBackExpandedConditions(const Node& node) {
+ for (size_t i = 0; i < node.size(); i++) { // loop through each node
+ try {
+ std::shared_ptr<ExpandedCondition> expandedCondition = createExpandedCondition(node[i]);
+ this->nodes.push_back(expandedCondition);
+ }
+ catch (const ExpandedConditionParsingError& ecpe) {
+ ShowError("%s for entry %s\n",ecpe.what(), name.c_str());
+ }
+ catch (const std::exception e) {
+ ShowError("%s for entry %s\n", e.what(), name.c_str());
+ }
+ }
+}
+
+const std::string ExpandedConditionParsingError::build_what(const std::string& msg) {
+
+ std::stringstream output;
+ output << "mob_expanded_conditions_db error: " << msg;
+ return output.str();
+}
+} // namespace expanded_ai
+/**
+*Reads and parses an entry from the mob_expanded_ai_conditions_db.
+ * @param node: YAML node containing the entry.
+ * @return count of successfully parsed entries
+ */
+uint64 MobExpandedAiConditionsDatabase::parseBodyNode(const YAML::Node &node) {
+ using namespace expanded_ai;
+
+ // All expanded conditions are single pair element maps
+ // so we just have to use begin() to get that only pair.
+ const auto entry = node.begin();
+
+
+ try {
+ //Each element of the sequence is either a string or a single element map<string,"sequence">
+ const auto& sequence_of_conditions = entry->second.as<YAML::Node>();
+
+ const string& container_name = entry->first.as<std::string>();
+ // Main conditions' testers container with an implicit "and" gate.
+ std::shared_ptr<ConditionContainer> main_container = std::make_shared<ConditionContainer>(container_name, LogicGate::getLogicGate("and"));
+
+ main_container->parseAndPushBackExpandedConditions(sequence_of_conditions);
+
+ mob_expanded_ai_conditions_db.put(main_container->name, main_container);
+ return 1;
+ }
+ catch (YAML::TypedBadConversion<YAML::Node> e) {
+ ShowError("Format error in mob_expanded_condition_db.yml. Should be ' - myentryname : '\n");
+ return 0;
+ }
+
+
+
+}
+
+const std::string MobExpandedAiConditionsDatabase::getDefaultLocation() {
+ return std::string(db_path)+"/mob_expanded_ai_conditions_db.yml";
+}
const std::string MobChatDatabase::getDefaultLocation() {
return std::string(db_path) + "/mob_chat_db.yml";
@@ -5707,79 +6367,7 @@ uint64 MobChatDatabase::parseBodyNode(const YAML::Node &node) {
*------------------------------------------*/
static bool mob_parse_row_mobskilldb(char** str, int columns, int current)
{
- static const struct {
- char str[32];
- enum MobSkillState id;
- } state[] = {
- { "any", MSS_ANY }, //All states except Dead
- { "idle", MSS_IDLE },
- { "walk", MSS_WALK },
- { "loot", MSS_LOOT },
- { "dead", MSS_DEAD },
- { "attack", MSS_BERSERK }, //Retaliating attack
- { "angry", MSS_ANGRY }, //Preemptive attack (aggressive mobs)
- { "chase", MSS_RUSH }, //Chase escaping target
- { "follow", MSS_FOLLOW }, //Preemptive chase (aggressive mobs)
- { "anytarget",MSS_ANYTARGET }, //Berserk+Angry+Rush+Follow
- };
- static const struct {
- char str[32];
- int id;
- } cond1[] = {
- // enum e_mob_skill_condition
- { "always", MSC_ALWAYS },
- { "myhpltmaxrate", MSC_MYHPLTMAXRATE },
- { "myhpinrate", MSC_MYHPINRATE },
- { "friendhpltmaxrate", MSC_FRIENDHPLTMAXRATE },
- { "friendhpinrate", MSC_FRIENDHPINRATE },
- { "mystatuson", MSC_MYSTATUSON },
- { "mystatusoff", MSC_MYSTATUSOFF },
- { "friendstatuson", MSC_FRIENDSTATUSON },
- { "friendstatusoff", MSC_FRIENDSTATUSOFF },
- { "attackpcgt", MSC_ATTACKPCGT },
- { "attackpcge", MSC_ATTACKPCGE },
- { "slavelt", MSC_SLAVELT },
- { "slavele", MSC_SLAVELE },
- { "closedattacked", MSC_CLOSEDATTACKED },
- { "longrangeattacked", MSC_LONGRANGEATTACKED },
- { "skillused", MSC_SKILLUSED },
- { "afterskill", MSC_AFTERSKILL },
- { "casttargeted", MSC_CASTTARGETED },
- { "rudeattacked", MSC_RUDEATTACKED },
- { "masterhpltmaxrate", MSC_MASTERHPLTMAXRATE },
- { "masterattacked", MSC_MASTERATTACKED },
- { "alchemist", MSC_ALCHEMIST },
- { "onspawn", MSC_SPAWN },
- }, cond2[] ={
- { "anybad", -1 },
- { "stone", SC_STONE },
- { "freeze", SC_FREEZE },
- { "stun", SC_STUN },
- { "sleep", SC_SLEEP },
- { "poison", SC_POISON },
- { "curse", SC_CURSE },
- { "silence", SC_SILENCE },
- { "confusion", SC_CONFUSION },
- { "blind", SC_BLIND },
- { "hiding", SC_HIDING },
- { "sight", SC_SIGHT },
- }, target[] = {
- // enum e_mob_skill_target
- { "target", MST_TARGET },
- { "randomtarget", MST_RANDOM },
- { "self", MST_SELF },
- { "friend", MST_FRIEND },
- { "master", MST_MASTER },
- { "around5", MST_AROUND5 },
- { "around6", MST_AROUND6 },
- { "around7", MST_AROUND7 },
- { "around8", MST_AROUND8 },
- { "around1", MST_AROUND1 },
- { "around2", MST_AROUND2 },
- { "around3", MST_AROUND3 },
- { "around4", MST_AROUND4 },
- { "around", MST_AROUND },
- };
+
static int last_mob_id = 0; // ensures that only one error message per mob id is printed
int mob_id;
int j, tmp;
@@ -5822,9 +6410,9 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current)
std::shared_ptr<s_mob_skill> ms = std::make_shared<s_mob_skill>();
//State
- ARR_FIND( 0, ARRAYLENGTH(state), j, strcmp(str[2],state[j].str) == 0 );
- if( j < ARRAYLENGTH(state) )
- ms->state = state[j].id;
+ const std::string& str_state = str[2];
+ if (um_mobskillstatename2id.count(str_state))
+ ms->state=um_mobskillstatename2id.at(str_state);
else {
ShowError("mob_parse_row_mobskilldb: Unrecognized state '%s' in line %d\n", str[2], current);
return false;
@@ -5867,11 +6455,11 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current)
ms->cancel=1;
//Target
- ARR_FIND( 0, ARRAYLENGTH(target), j, strcmp(str[9],target[j].str) == 0 );
- if( j < ARRAYLENGTH(target) )
- ms->target = target[j].id;
+ const std::string& target_str=str[9];
+ if (skill_target_name2enumid.count(target_str))
+ ms->target=skill_target_name2enumid.at(target_str);
else {
- ShowWarning("mob_parse_row_mobskilldb: Unrecognized target %s for %d\n", str[9], mob_id);
+ ShowWarning("mob_parse_row_mobskilldb: Unrecognized target %s for %d\n", target_str, mob_id);
ms->target = MST_TARGET;
}
@@ -5893,21 +6481,34 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current)
}
//Cond1
- ARR_FIND( 0, ARRAYLENGTH(cond1), j, strcmp(str[10],cond1[j].str) == 0 );
- if( j < ARRAYLENGTH(cond1) )
- ms->cond1 = cond1[j].id;
- else {
- ShowWarning("mob_parse_row_mobskilldb: Unrecognized condition 1 %s for %d\n", str[10], mob_id);
- ms->cond1 = -1;
+ const std::string& str_cond = str[10];
+ try {
+ ms->cond1 = mob_skill_condition2name.at(str_cond);
+ }
+ catch (std::out_of_range oor) {
+ ShowWarning("mob_parse_row_mobskilldb: Unrecognized condition 1 %s for %d\n", str_cond, mob_id);
+ return false;
}
//Cond2
- // numeric value
- ms->cond2 = atoi(str[11]);
- // or special constant
- ARR_FIND( 0, ARRAYLENGTH(cond2), j, strcmp(str[11],cond2[j].str) == 0 );
- if( j < ARRAYLENGTH(cond2) )
- ms->cond2 = cond2[j].id;
+ const std::string& str_cond2 = str[11];
+ //search special constant ;
+
+ if(str_cond2 == "")
+ ms->cond2=0;
+ else if (um_statusname2id.count(str_cond2)) {
+ ms->cond2=um_statusname2id.at(str_cond2);
+ } else if (mob_expanded_ai_conditions_db.find(str_cond2) != nullptr)
+ ms->expanded_cond=str_cond2;
+ else{
+ try{
+ ms->cond2=stoi(str_cond2);
+ } catch(std::invalid_argument ia){
+ ShowWarning("mob_parse_row_mobskilldb: Unrecognized condition2%s for %d\n", str_cond2, mob_id);
+ return false;
+ }
+ }
+ ms->expanded_cond=str_cond2; // For error message if calling ill-formed expanded condition
ms->val[0] = (int)strtol(str[12],NULL,0);
ms->val[1] = (int)strtol(str[13],NULL,0);
@@ -6354,7 +6955,7 @@ static void mob_load(void)
mob_db.load();
mob_chat_db.load(); // load before mob_skill_db
-
+ mob_expanded_ai_conditions_db.load(); // load before mob_skill_db
for(int i = 0; i < ARRAYLENGTH(dbsubpath); i++){
int n1 = strlen(db_path)+strlen(dbsubpath[i])+1;
int n2 = strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[i])+1;
@@ -6381,6 +6982,8 @@ static void mob_load(void)
aFree(dbsubpath2);
}
+
+
mob_item_drop_ratio.load();
mob_avail_db.load();
mob_summon_db.load();
@@ -6546,7 +7149,7 @@ void do_final_mob(bool is_reload){
mob_db.clear();
mob_chat_db.clear();
mob_skill_db.clear();
-
+ mob_expanded_ai_conditions_db.clear();
mob_item_drop_ratio.clear();
mob_summon_db.clear();
if( !is_reload ) {
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index 1fe48af06..0ae236da1 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -9,9 +9,11 @@
#include "../common/database.hpp"
#include "../common/mmo.hpp" // struct item
#include "../common/timer.hpp"
+#include <algorithm>
#include "status.hpp" // struct status data, struct status_change
#include "unit.hpp" // unit_stop_walking(), unit_stop_attack()
+#include <functional>
struct guardian_data;
@@ -185,19 +187,179 @@ enum e_aegis_monsterclass : int8 {
CLASS_MAX,
};
+enum e_mob_skill_target {
+ MST_TARGET = 0,
+ MST_RANDOM, //Random Target!
+ MST_SELF,
+ MST_FRIEND,
+ MST_MASTER,
+ MST_AROUND5,
+ MST_AROUND6,
+ MST_AROUND7,
+ MST_AROUND8,
+ MST_AROUND1,
+ MST_AROUND2,
+ MST_AROUND3,
+ MST_AROUND4,
+ MST_AROUND = MST_AROUND4,
+};
+enum e_mob_skill_condition {
+ MSC_ALWAYS = 0x0000,
+ MSC_MYHPLTMAXRATE,
+ MSC_MYHPINRATE,
+ MSC_FRIENDHPLTMAXRATE,
+ MSC_FRIENDHPINRATE,
+ MSC_MYSTATUSON,
+ MSC_MYSTATUSOFF,
+ MSC_FRIENDSTATUSON,
+ MSC_FRIENDSTATUSOFF,
+ MSC_ENEMYSTATUSON,
+ MSC_ENEMYSTATUSOFF,
+ MSC_ATTACKPCGT,
+ MSC_ATTACKPCGE,
+ MSC_SLAVELT,
+ MSC_SLAVELE,
+ MSC_CLOSEDATTACKED,
+ MSC_LONGRANGEATTACKED,
+ MSC_AFTERSKILL,
+ MSC_SKILLUSED,
+ MSC_CASTTARGETED,
+ MSC_RUDEATTACKED,
+ MSC_MASTERHPLTMAXRATE,
+ MSC_MASTERSTATUSON,
+ MSC_MASTERSTATUSOFF,
+ MSC_MASTERATTACKED,
+ MSC_ALCHEMIST,
+ MSC_SPAWN,
+ MSC_EXPANDED
+};
+
+
struct s_mob_skill {
enum MobSkillState state;
uint16 skill_id,skill_lv;
short permillage;
int casttime,delay;
short cancel;
- short cond1,cond2;
- short target;
+ e_mob_skill_condition cond1;
+ short cond2;
+ std::string expanded_cond; // For expanded conditions
+ e_mob_skill_target target;
int val[5];
short emotion;
unsigned short msg_id;
};
+
+namespace expanded_ai {
+/// This represent the type of either an active or disabled inverter.
+using inverter_t=std::function<bool(bool)>;
+class ExpandedCondition;
+
+
+/**
+ * @brief : logic gate function wrapper for increased readability
+*/
+class LogicGate {
+public:
+ LogicGate() {}
+ /**
+ * @brief Maps a name to a logic gate.
+ */
+ static std::shared_ptr<LogicGate> getLogicGate(const std::string& name);
+ /**
+ * @brief Applies the logical tests on the condition group
+ * @param conditions The conditions to put through the gate
+ * @param targets : targets of the condition tests
+ * @return result of the test
+ */
+ virtual bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> conditions, const std::map<e_mob_skill_target, block_list*>& targets) const = 0;
+};
+
+class ExpandedCondition{
+public:
+ const std::string name;
+ ExpandedCondition(std::string name) :name{ name }{}
+
+ /**
+ * @brief The main function that will try to parse the yaml line.
+ * @param node : the yaml line to parse into a condition
+ * @return Either a simple condition, a stored or newly made container
+ */
+ static std::shared_ptr<ExpandedCondition> createExpandedCondition(const YAML::Node& node);
+ /**
+ * @brief Calls the predicate(s) on the targets matching its/their stored targets ids
+ * @param targets map filled with surrounding potential targets for the predicate(s)
+ * @return the predicate test
+ */
+ virtual bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) const = 0;
+
+
+
+};
+template <class TPredicate>
+class SingleCondition: public ExpandedCondition {
+private:
+ /**
+ * @brief A test function object that is either a simple parsed line or a wrapper for a stored condition container which can be inverted
+ */
+ std::shared_ptr<TPredicate> predicate;
+ inverter_t inverter;
+
+public:
+ SingleCondition(std::string name, inverter_t inverter, std::shared_ptr<TPredicate> predicate) :ExpandedCondition(name), inverter{ inverter }, predicate{ predicate }{}
+ bool operator()(const std::map<e_mob_skill_target, block_list*>& targets)const override;
+
+};
+
+///Condition Container.
+class ConditionContainer : public ExpandedCondition {
+private:
+ std::vector<std::shared_ptr<ExpandedCondition>> nodes;
+ std::shared_ptr<LogicGate> logicGate;
+
+public:
+ ConditionContainer(std::string name, std::shared_ptr<LogicGate> logicGate) :ExpandedCondition(name), logicGate{ logicGate }{}
+ /**
+ * @brief Loops through the conditions. Some of them can be wrappers of already stored containers with an inverter set.
+ */
+ bool operator()(const std::map<e_mob_skill_target, block_list*>& targets)const override;
+
+ /**
+ * @brief Convert each element of the yaml sequence to a ExpandedCondition element
+ * @param node Sequence's element that will be parsed
+ * @param conditionContainer Container element.
+ * @param entry_name
+ */
+ void parseAndPushBackExpandedConditions(const YAML::Node& node);
+};
+/**
+ * @brief Parses a string that will be used to create the conditions. Depends on string to ids maps
+ * @param line : Node's fields to be parsed
+ * @return : uMap with parsed fields
+*/
+std::unordered_map<std::string, std::string> parseFields(const std::string& line);
+
+class ExpandedConditionParsingError : public std::runtime_error {
+private:
+ std::string msg;
+ static const std::string build_what(const std::string& msg);
+public:
+ ExpandedConditionParsingError(const std::string& msg_) : std::runtime_error(build_what(msg_)), msg(msg_) {}
+ ExpandedConditionParsingError(const ExpandedConditionParsingError&) = default;
+};
+}//namespace ai_expanded
+
+
+class MobExpandedAiConditionsDatabase: public TypesafeYamlDatabase<std::string,expanded_ai::ExpandedCondition> {
+public:
+ MobExpandedAiConditionsDatabase(): TypesafeYamlDatabase("MOB_EXPANDED_AI_CONDITIONS_DB",1) {
+
+ }
+ const std::string getDefaultLocation();
+ uint64 parseBodyNode(const YAML::Node &node);
+};
+
struct s_mob_chat {
uint16 msg_id;
uint32 color;
@@ -206,6 +368,7 @@ struct s_mob_chat {
class MobChatDatabase : public TypesafeYamlDatabase<uint16, s_mob_chat> {
public:
+
MobChatDatabase() : TypesafeYamlDatabase("MOB_CHAT_DB", 1) {
}
@@ -228,6 +391,7 @@ public:
const std::string getDefaultLocation() override;
uint64 parseBodyNode(const YAML::Node &node) override;
+
};
struct spawn_info {
@@ -387,48 +551,6 @@ public:
uint64 parseBodyNode(const YAML::Node &node) override;
};
-enum e_mob_skill_target {
- MST_TARGET = 0,
- MST_RANDOM, //Random Target!
- MST_SELF,
- MST_FRIEND,
- MST_MASTER,
- MST_AROUND5,
- MST_AROUND6,
- MST_AROUND7,
- MST_AROUND8,
- MST_AROUND1,
- MST_AROUND2,
- MST_AROUND3,
- MST_AROUND4,
- MST_AROUND = MST_AROUND4,
-};
-
-enum e_mob_skill_condition {
- MSC_ALWAYS = 0x0000,
- MSC_MYHPLTMAXRATE,
- MSC_MYHPINRATE,
- MSC_FRIENDHPLTMAXRATE,
- MSC_FRIENDHPINRATE,
- MSC_MYSTATUSON,
- MSC_MYSTATUSOFF,
- MSC_FRIENDSTATUSON,
- MSC_FRIENDSTATUSOFF,
- MSC_ATTACKPCGT,
- MSC_ATTACKPCGE,
- MSC_SLAVELT,
- MSC_SLAVELE,
- MSC_CLOSEDATTACKED,
- MSC_LONGRANGEATTACKED,
- MSC_AFTERSKILL,
- MSC_SKILLUSED,
- MSC_CASTTARGETED,
- MSC_RUDEATTACKED,
- MSC_MASTERHPLTMAXRATE,
- MSC_MASTERATTACKED,
- MSC_ALCHEMIST,
- MSC_SPAWN,
-};
// The data structures for storing delayed item drops
struct item_drop {
diff --git a/src/map/mob_inlines.hpp b/src/map/mob_inlines.hpp
new file mode 100755
index 000000000..7b687679d
--- /dev/null
+++ b/src/map/mob_inlines.hpp
@@ -0,0 +1,314 @@
+// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef MOB_INLINES_HPP
+#define MOB_INLINES_HPP
+#include "mob.hpp"
+#include "unit.hpp"
+
+namespace expanded_ai{
+namespace predicates {
+
+/**
+ * @brief Function object that stores the data to be tested when called
+*/
+class condition_predicate {
+protected:
+ e_mob_skill_target target_type_id;
+public:
+ condition_predicate(e_mob_skill_target target_type_id) : target_type_id{ target_type_id } {}
+ virtual bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) = 0;
+ inline bool condition_predicate::checkTarget(const std::map<e_mob_skill_target, block_list*>& targets) {
+ return targets.count(target_type_id);
+ }
+};
+
+struct exists : public condition_predicate {
+ exists(e_mob_skill_target target_type_id) : condition_predicate(target_type_id){}
+ inline bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) {
+ return checkTarget(targets);
+ }
+};
+
+template <class TComparator>
+struct percent_health: public condition_predicate {
+ TComparator comparator;
+ int threshold;
+ percent_health(e_mob_skill_target target_type_id,TComparator comparator,int threshold): condition_predicate(target_type_id),comparator{comparator},threshold{threshold}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ if (!checkTarget(targets))
+ return false;
+ block_list* target = targets.at(target_type_id);
+ return this->comparator(get_percentage(status_get_hp(target),status_get_max_hp(target)),this->threshold);
+ }
+};
+
+template <class TComparator>
+struct health: public condition_predicate {
+ TComparator comparator;
+ int threshold;
+ health(e_mob_skill_target target_type_id,TComparator comparator,int threshold): condition_predicate(target_type_id),comparator{comparator},threshold{threshold}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && this->comparator(status_get_hp(targets.at(target_type_id)),this->threshold);
+ }
+};
+
+template <class TComparator>
+struct element_armor : public condition_predicate {
+ TComparator comparator;
+ short element;
+ short level;
+ element_armor(e_mob_skill_target target_type_id, TComparator comparator, short element, short level) : condition_predicate(target_type_id), comparator{ comparator }, element{ element }, level{ level }{}
+ inline bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) {
+ if (!checkTarget(targets))
+ return false;
+ block_list* target = targets.at(target_type_id);
+ return status_get_element(target) == element && this->comparator(status_get_element_level(target), level);
+ }
+};
+struct element_attack: public condition_predicate {
+ short element;
+ element_attack(e_mob_skill_target target_type_id,short element): condition_predicate(target_type_id),element{element}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && status_get_attack_element(targets.at(target_type_id)) == this->element;
+ }
+};
+template <class TComparator>
+struct element_resist: public condition_predicate {
+ TComparator comparator;
+ short element;
+ int threshold;
+ element_resist(e_mob_skill_target target_type_id,TComparator comparator,short element,int threshold): condition_predicate(target_type_id),comparator{comparator},element{element},threshold{threshold}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ if (!checkTarget(targets))
+ return false;
+ map_session_data* sd;
+ if(sd = BL_CAST(BL_PC,targets.at(target_type_id)))
+ return comparator(sd->indexed_bonus.subele_script[element],threshold);
+ else
+ return comparator(0,threshold); // 0 until mob elemental resistance feature is added
+ }
+};
+struct cell: public condition_predicate {
+ cell_chk mCell;
+ cell(e_mob_skill_target target_type_id,cell_chk mCell): condition_predicate(target_type_id),mCell{mCell}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ if (!checkTarget(targets))
+ return false;
+ block_list* target = targets.at(target_type_id);
+ return map_getcell(target->m,target->x,target->y,mCell);
+ }
+};
+struct status: public condition_predicate {
+ int mStatus;
+ status(e_mob_skill_target target_type_id,int mStatus): condition_predicate(target_type_id),mStatus{mStatus}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && status_get_sc(targets.at(target_type_id))->data[mStatus] != NULL;
+ }
+};
+template <class TComparator>
+struct status_resist : public condition_predicate {
+ TComparator comparator;
+ sc_type mStatus;
+ int threshold;
+ status_resist(e_mob_skill_target target_type_id, TComparator comparator, sc_type mStatus, int threshold) : condition_predicate(target_type_id), comparator{ comparator }, mStatus{ mStatus }, threshold{ threshold }{}
+ inline bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) {
+ return checkTarget(targets) && comparator(status_get_sc_total_resist(targets.at(MST_SELF), targets.at(this->target_type_id), this->mStatus), 100 * this->threshold);
+ }
+};
+
+
+template <class TComparator>
+struct job: public condition_predicate {
+ TComparator comparator;
+ int mjob;
+ job(e_mob_skill_target target_type_id,TComparator comparator,int pjob): condition_predicate(target_type_id),comparator{comparator},mjob{pjob}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && status_get_class(targets.at(target_type_id)) == mjob;
+ }
+};
+
+template <class TComparator>
+struct attack_range: public condition_predicate {
+ TComparator comparator;
+ int range;
+ attack_range(e_mob_skill_target target_type_id,TComparator comparator,int range): condition_predicate(target_type_id),comparator{comparator},range{range}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && this->comparator(status_get_range(targets.at(target_type_id)),this->range);
+ }
+};
+template <class TComparator>
+struct distance_from_self: public condition_predicate {
+ TComparator comparator;
+ int mDistance; // Conflict with distance_bl macro if named distance
+ distance_from_self(e_mob_skill_target target_type_id,TComparator comparator,int mDistance): condition_predicate(target_type_id),comparator{comparator},mDistance{mDistance}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && comparator(distance_bl(targets.at(MST_SELF),targets.at(target_type_id)),this->mDistance);
+ }
+};
+template <class TComparator>
+struct distance_from_friend: public condition_predicate {
+ TComparator comparator;
+ int mDistance;
+ distance_from_friend(e_mob_skill_target target_type_id,TComparator comparator,int mDistance): condition_predicate(target_type_id),comparator{comparator},mDistance{mDistance}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && comparator(distance_bl(targets.at(MST_FRIEND),targets.at(target_type_id)),this->mDistance);
+ }
+};
+template <class TComparator>
+struct distance_from_master: public condition_predicate {
+ TComparator comparator;
+ int mDistance;
+ distance_from_master(e_mob_skill_target target_type_id,TComparator comparator,int mDistance): condition_predicate(target_type_id),comparator{comparator},mDistance{mDistance}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ return checkTarget(targets) && comparator(distance_bl(targets.at(MST_MASTER),targets.at(target_type_id)),this->mDistance);
+ }
+};
+template <class TComparator>
+struct physical_reflect: public condition_predicate {
+ TComparator comparator;
+ int threshold;
+ physical_reflect(e_mob_skill_target target_type_id,TComparator comparator,int threshold): condition_predicate(target_type_id),comparator{comparator},threshold{threshold}{}
+ inline bool operator()(const std::map<e_mob_skill_target,block_list*>& targets) {
+ if (!checkTarget(targets))
+ return false;
+ int totalReflect=0;
+ block_list* target = targets.at(target_type_id);
+ status_change *sc = status_get_sc(target);
+ if(sc->data[SC_REFLECTSHIELD])
+ totalReflect+=sc->data[SC_REFLECTSHIELD]->val2;
+ map_session_data* ssd = BL_CAST(BL_PC,target);
+ if(ssd && ssd->bonus.short_weapon_damage_return != 0)
+ totalReflect+=ssd->bonus.short_weapon_damage_return;
+ return comparator(totalReflect,this->threshold);
+ }
+};
+
+template <class TComparator>
+struct attack : public condition_predicate {
+ TComparator comparator;
+ int mAttack;
+ attack(e_mob_skill_target target_type_id, TComparator comparator, int mAttack) : condition_predicate(target_type_id), comparator{ comparator }, mAttack{ mAttack }{}
+ inline bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) {
+ return checkTarget(targets) && comparator(status_get_watk(targets.at(this->target_type_id)), this->mAttack);
+ }
+};
+
+struct mode : public condition_predicate {
+ e_mode mmode;
+ mode(e_mob_skill_target target_type_id, e_mode mmode) : condition_predicate(target_type_id), mmode{ mmode }{}
+ inline bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) {
+ return checkTarget(targets) && status_get_status_data(targets.at(this->target_type_id))->mode & mmode;
+ }
+};
+
+struct knockback : public condition_predicate {
+ knockback(e_mob_skill_target target_type_id) : condition_predicate(target_type_id){}
+ inline bool operator()(const std::map<e_mob_skill_target, block_list*>& targets) {
+ return checkTarget(targets) && unit_blown_immune(targets.at(this->target_type_id), 0x1);
+ }
+};
+
+
+} //namespace predicates
+
+namespace logic_gates {
+
+//XOR logic gate
+template <class _InIt, class _Pr>
+inline bool one_only(_InIt _First, _InIt _Last, _Pr _Pred) {
+ // test if all elements satisfy _Pred
+ std::_Adl_verify_range(_First, _Last);
+ auto _UFirst = std::_Get_unwrapped(_First);
+ const auto _ULast = std::_Get_unwrapped(_Last);
+ bool isOneTrue = (false);
+ for (; _UFirst != _ULast; ++_UFirst) {
+ if (_Pred(*_UFirst)) {
+ if (!isOneTrue)
+ isOneTrue = (true);
+ else
+ return (false);
+ }
+ }
+ return (isOneTrue);
+}
+
+//NAND logic gate
+template<class _InIt, class _Pr>
+_NODISCARD inline bool any_false_of(const _InIt _First, const _InIt _Last, _Pr _Pred)
+{ // test if any element satisfies _Pred
+ _Adl_verify_range(_First, _Last);
+ auto _UFirst = _Get_unwrapped(_First);
+ const auto _ULast = _Get_unwrapped(_Last);
+ for (; _UFirst != _ULast; ++_UFirst)
+ {
+ if (!_Pred(*_UFirst))
+ return (true);
+ }
+ return (false);
+}
+
+//NXOR logic gate
+template <class _InIt, class _Pr>
+inline bool all_or_none(_InIt _First, _InIt _Last, _Pr _Pred) {
+ // test if all elements satisfy _Pred
+ std::_Adl_verify_range(_First, _Last);
+ auto _UFirst = std::_Get_unwrapped(_First);
+ const auto _ULast = std::_Get_unwrapped(_Last);
+ bool first = _Pred(*_UFirst);
+ ++_UFirst;
+ for (; _UFirst != _ULast; ++_UFirst) {
+ if (_Pred(*_UFirst) != (first))
+ return (false);
+ }
+ return (true);
+}
+
+
+
+class LogicalAND : public LogicGate {
+public:
+ LogicalAND(){}
+ inline bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> nodes, const std::map<e_mob_skill_target, block_list*>& targets)const override {
+ return std::all_of(nodes.cbegin(), nodes.cend(), [&](const auto& it) {return (*it)(targets); });
+ }
+};
+
+class LogicalOR : public LogicGate {
+public:
+ LogicalOR(){}
+ inline bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> nodes, const std::map<e_mob_skill_target, block_list*>& targets)const override {
+ return std::any_of(nodes.cbegin(), nodes.cend(), [&](const auto& it) {return (*it)(targets); });
+ }
+};
+class LogicalNAND : public LogicGate {
+public:
+ LogicalNAND() {}
+ inline bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> nodes, const std::map<e_mob_skill_target, block_list*>& targets)const override {
+ return any_false_of(nodes.cbegin(), nodes.cend(), [&](const auto& it) {return (*it)(targets); });
+ }
+};
+class LogicalNOR : public LogicGate {
+public:
+ LogicalNOR() {}
+ inline bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> nodes, const std::map<e_mob_skill_target, block_list*>& targets)const override {
+ return std::none_of(nodes.cbegin(), nodes.cend(), [&](const auto& it) {return (*it)(targets); });
+ }
+};
+class LogicalXOR : public LogicGate {
+public:
+ LogicalXOR() {}
+ inline bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> nodes, const std::map<e_mob_skill_target, block_list*>& targets)const override {
+ return one_only(nodes.cbegin(), nodes.cend(), [&](const auto& it) {return (*it)(targets); });
+ }
+};
+class LogicalNXOR : public LogicGate {
+public:
+ LogicalNXOR() {}
+ inline bool operator()(std::vector<std::shared_ptr<ExpandedCondition>> nodes, const std::map<e_mob_skill_target, block_list*>& targets)const override {
+ return all_or_none(nodes.cbegin(), nodes.cend(), [&](const auto& it) {return (*it)(targets); });
+ }
+};
+}//logic_gates
+}//expanded_ai
+#endif MOB_INLINE_HPP
diff --git a/src/map/script_constants.hpp b/src/map/script_constants.hpp
index 63baa5d0e..34e763cc1 100644
--- a/src/map/script_constants.hpp
+++ b/src/map/script_constants.hpp
@@ -8090,6 +8090,7 @@
export_constant(MD_NORANDOMWALK);
export_constant(MD_NOCAST);
export_constant(MD_CANATTACK);
+ export_constant(MD_SKILLONLY);
export_constant(MD_CASTSENSORCHASE);
export_constant(MD_CHANGECHASE);
export_constant(MD_ANGRY);
@@ -8104,6 +8105,7 @@
export_constant(MD_IGNOREMISC);
export_constant(MD_KNOCKBACKIMMUNE);
export_constant(MD_TELEPORTBLOCK);
+ export_constant(MD_PCSKILLBEHAVIOR);
export_constant(MD_FIXEDITEMDROP);
export_constant(MD_DETECTOR);
export_constant(MD_STATUSIMMUNE);
diff --git a/src/map/status.cpp b/src/map/status.cpp
index 7309acaf9..7d839e385 100644
--- a/src/map/status.cpp
+++ b/src/map/status.cpp
@@ -10587,6 +10587,240 @@ t_tick status_get_sc_def(struct block_list *src, struct block_list *bl, enum sc_
return tick;
}
+
+int status_get_sc_total_resist(struct block_list* src, struct block_list* bl, enum sc_type type)
+{
+ int sc_def = 0;
+ int sc_def2 = 0;
+ int rate = 10000;
+ t_tick tick= 5000;
+ int flag = 0;
+ struct status_data* status, * status_src;
+ struct status_change* sc;
+ struct map_session_data* sd;
+ status = status_get_status_data(bl);
+ if (status_has_mode(status, MD_MVP) || status_has_mode(status, MD_STATUSIMMUNE))
+ return rate;
+ nullpo_ret(bl);
+ // Skills (magic type) that are blocked by Golden Thief Bug card or Wand of Hermod
+ if (status_isimmune(bl)) {
+ std::shared_ptr<s_skill_db> skill = skill_db.find(battle_getcurrentskill(src));
+
+ if (skill != nullptr && skill->nameid != AG_DEADLY_PROJECTION && skill->skill_type == BF_MAGIC)
+ return 0;
+ }
+
+
+ rate = cap_value(rate, 0, 10000);
+ sd = BL_CAST(BL_PC, bl);
+ status = status_get_status_data(bl);
+ status_src = status_get_status_data(src);
+ sc = status_get_sc(bl);
+ if (sc && !sc->count)
+ sc = NULL;
+
+ switch (type) {
+ case SC_POISON:
+ case SC_DPOISON:
+ sc_def = status->vit * 100;
+#ifndef RENEWAL
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#endif
+ break;
+ case SC_STUN:
+ sc_def = status->vit * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+
+ break;
+ case SC_SILENCE:
+#ifndef RENEWAL
+ sc_def = status->vit * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#else
+ sc_def = status->int_ * 100;
+ sc_def2 = (status->vit + status->luk) * 5 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#endif
+
+ break;
+ case SC_BLEEDING:
+#ifndef RENEWAL
+ sc_def = status->vit * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#else
+ sc_def = status->agi * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#endif
+
+ break;
+ case SC_SLEEP:
+#ifndef RENEWAL
+ sc_def = status->int_ * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#else
+ sc_def = status->agi * 100;
+ sc_def2 = (status->int_ + status->luk) * 5 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+#endif
+
+ break;
+ case SC_STONE:
+ if (battle_check_undead(status->race, status->def_ele))return 10000;
+ sc_def = status->mdef * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+
+ break;
+ case SC_FREEZE:
+ if (battle_check_undead(status->race, status->def_ele))return 10000;
+ sc_def = status->mdef * 100;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+
+ break;
+ case SC_CURSE:
+ // Special property: immunity when luk is zero
+ if (status->luk == 0)
+ return 0;
+ sc_def = status->luk * 100;
+ sc_def2 = status->luk * 10 - status_get_lv(src) * 10; // Curse only has a level penalty and no resistance
+
+ break;
+ case SC_BLIND:
+ sc_def = (status->vit + status->int_) * 50;
+ sc_def2 = status->luk * 10 + status_get_lv(bl) * 10 - status_get_lv(src) * 10;
+
+ break;
+ case SC_CONFUSION:
+ sc_def = (status->str + status->int_) * 50;
+ sc_def2 = status_get_lv(src) * 10 - status_get_lv(bl) * 10 - status->luk * 10; // Reversed sc_def2
+
+ break;
+ case SC_DECREASEAGI:
+
+ sc_def2 = status->mdef * 100;
+ break;
+ case SC_ANKLE:
+
+ sc_def = status->agi * 50;
+ break;
+ case SC_JOINTBEAT:
+ sc_def2 = 270 * status->str / 100; // 270 * STR / 100
+ break;
+ case SC_DEEPSLEEP:
+ case SC_NETHERWORLD:
+ case SC_MARSHOFABYSS:
+ case SC_STASIS:
+ case SC_WHITEIMPRISON:
+ case SC_BURNING:
+ case SC_FREEZING:
+ break;
+ case SC_OBLIVIONCURSE: // 100% - (100 - 0.8 x INT)
+ sc_def = status->int_ * 80;
+ sc_def = max(sc_def, 500); // minimum of 5% resist
+ break;
+ case SC_TOXIN:
+ case SC_PARALYSE:
+ case SC_VENOMBLEED:
+ case SC_MAGICMUSHROOM:
+ case SC_DEATHHURT:
+ case SC_PYREXIA:
+ case SC_LEECHESEND:
+ break;
+ case SC_BITE: // {(Base Success chance) - (Target's AGI / 4)}
+ sc_def2 = status->agi * 25;
+ break;
+ case SC_ELECTRICSHOCKER:
+ case SC_CRYSTALIZE:
+ case SC_VACUUM_EXTREME:
+ case SC_KYOUGAKU:
+ case SC_PARALYSIS:
+ case SC_VOICEOFSIREN:
+ case SC_B_TRAP:
+ case SC_NORECOVER_STATE:
+ break;
+ default:
+ return 0;
+ }
+
+ if (sd) {
+ if (battle_config.pc_sc_def_rate != 100) {
+ sc_def = sc_def * battle_config.pc_sc_def_rate / 100;
+ sc_def2 = sc_def2 * battle_config.pc_sc_def_rate / 100;
+ }
+
+ sc_def = min(sc_def, battle_config.pc_max_sc_def * 100);
+ sc_def2 = min(sc_def2, battle_config.pc_max_sc_def * 100);
+
+ if (battle_config.pc_sc_def_rate != 100) {
+ }
+ } else {
+ if (battle_config.mob_sc_def_rate != 100) {
+ sc_def = sc_def * battle_config.mob_sc_def_rate / 100;
+ sc_def2 = sc_def2 * battle_config.mob_sc_def_rate / 100;
+ }
+
+ sc_def = min(sc_def, battle_config.mob_max_sc_def * 100);
+ sc_def2 = min(sc_def2, battle_config.mob_max_sc_def * 100);
+
+ if (battle_config.mob_sc_def_rate != 100) {
+
+ }
+ }
+
+ if (sc) {
+ if (sc->data[SC_SCRESIST])
+ sc_def += sc->data[SC_SCRESIST]->val1 * 100; // Status resist
+#ifdef RENEWAL
+ else if (sc->data[SC_SIEGFRIED] && (type == SC_BLIND || type == SC_STONE || type == SC_FREEZE || type == SC_STUN || type == SC_CURSE || type == SC_SLEEP || type == SC_SILENCE))
+ sc_def += sc->data[SC_SIEGFRIED]->val3 * 100; // Status resistance.
+#else
+ else if (sc->data[SC_SIEGFRIED])
+ sc_def += sc->data[SC_SIEGFRIED]->val3 * 100; // Status resistance.
+#endif
+ else if (sc->data[SC_LEECHESEND] && sc->data[SC_LEECHESEND]->val3 == 0) {
+ switch (type) {
+ case SC_BLIND:
+ case SC_STUN:
+ return 0; // Immune
+ }
+ } else if (sc->data[SC_OBLIVIONCURSE] && sc->data[SC_OBLIVIONCURSE]->val3 == 0) {
+ switch (type) {
+ case SC_SILENCE:
+ case SC_CURSE:
+ return 0; // Immune
+ }
+ }
+ }
+
+ // Natural resistance
+ if (!(flag & SCSTART_NORATEDEF)) {
+ rate -= rate * sc_def / 10000;
+ rate -= sc_def2;
+
+ // Minimum chances
+ switch (type) {
+ case SC_BITE:
+ rate = max(rate, 5000); // Minimum of 50%
+ break;
+ }
+
+ // Item resistance (only applies to rate%)
+ if (sd) {
+ for (const auto& it : sd->reseff) {
+ if (it.id == type)
+ rate -= rate * it.val / 10000;
+ }
+ if (sd->sc.data[SC_COMMONSC_RESIST] && SC_COMMON_MIN <= type && type <= SC_COMMON_MAX)
+ rate -= rate * sd->sc.data[SC_COMMONSC_RESIST]->val1 / 100;
+ }
+
+ // Aegis accuracy
+ if (rate > 0 && rate % 10 != 0) rate += (10 - rate % 10);
+ }
+
+ rate = rate >= 0 ? rate : 0;
+ return 10000 - rate;
+
+}
+
+
/**
* Applies SC effect
* @param bl: Source to apply effect
diff --git a/src/map/status.hpp b/src/map/status.hpp
index 887b8bb4b..7b59876d4 100644
--- a/src/map/status.hpp
+++ b/src/map/status.hpp
@@ -3104,7 +3104,8 @@ struct view_data *status_get_viewdata(struct block_list *bl);
void status_set_viewdata(struct block_list *bl, int class_);
void status_change_init(struct block_list *bl);
struct status_change *status_get_sc(struct block_list *bl);
-
+t_tick status_get_sc_tick(block_list*, block_list*, enum sc_type type);
+int status_get_sc_total_resist(block_list*, block_list*, enum sc_type type);
int status_isdead(struct block_list *bl);
int status_isimmune(struct block_list *bl);
diff --git a/src/map/unit.cpp b/src/map/unit.cpp
index 74c46c602..9f0d7157e 100644
--- a/src/map/unit.cpp
+++ b/src/map/unit.cpp
@@ -2798,6 +2798,13 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, t_tick tick)
unit_stop_walking(src,1);
if(md) {
+
+ e_mode mode = static_cast<e_mode>(status_get_mode(&md->bl));
+ if (mode & MD_SKILLONLY) {
+ md->state.skillstate = md->state.aggressive ? MSS_ANGRY : MSS_BERSERK; // on met ça car sinon il reste iddle après le skill
+ mobskill_use(md, tick, -1); // si on enlève pas le if ca fait une auto au lieu de skill car pour si le retour vaut 0 c'est que le mob a échoué le skill et ca continue le code qui conduit à l'autoattaque
+ return 1;
+ }
//First attack is always a normal attack
if(md->state.skillstate == MSS_ANGRY || md->state.skillstate == MSS_BERSERK) {
if (mobskill_use(md,tick,-1))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment