From f8cd4aa8c754fe375d85131cf1a5b49f48d73142 Mon Sep 17 00:00:00 2001 From: Aleos Date: Fri, 16 Sep 2022 12:46:53 -0400 Subject: [PATCH] Implements petautobonus script commands (#7053) * Fixes #7038. * Implements script commands: petautobonus, petautobonus2, and petautobonus3. * This allows pets to utilize the same autobonus features that players can use without having to check for the equipped item position. Thanks to @EditorFc's suggestion and @Lemongrass3110! --- db/re/pet_db.yml | 18 +++--- doc/script_commands.txt | 13 ++++- src/map/pet.cpp | 111 +++++++++++++++++++++++++++++++++++ src/map/pet.hpp | 41 +++++++++++++ src/map/script.cpp | 114 ++++++++++++++++++++++++++++++++++++ src/map/script.hpp | 1 + src/map/skill.cpp | 126 ++++++++++++++++++++++++++++------------ src/map/status.cpp | 6 ++ src/map/unit.cpp | 4 ++ 9 files changed, 386 insertions(+), 48 deletions(-) diff --git a/db/re/pet_db.yml b/db/re/pet_db.yml index 64e9cacfdb..5013ef7c19 100644 --- a/db/re/pet_db.yml +++ b/db/re/pet_db.yml @@ -877,9 +877,9 @@ Body: .@i = getpetinfo(PETINFO_INTIMATE); if (.@i >= PET_INTIMATE_LOYAL) { - autobonus "{ bonus2 bSubEle,Ele_Neutral,2; heal 100,0; }",100,5000,BF_SHORT|BF_NORMAL; + petautobonus "{ bonus2 bSubEle,Ele_Neutral,2; heal 100,0; }",100,5000,BF_SHORT|BF_NORMAL; } else if (.@i >= PET_INTIMATE_CORDIAL) { - autobonus "{ bonus2 bSubEle,Ele_Neutral,2; heal 100,0; }",100,3000,BF_SHORT|BF_NORMAL; + petautobonus "{ bonus2 bSubEle,Ele_Neutral,2; heal 100,0; }",100,3000,BF_SHORT|BF_NORMAL; } - Mob: MEDUSA TameItem: Splendid_Mirror @@ -1511,15 +1511,15 @@ Body: if (.@i >= PET_INTIMATE_LOYAL) { bonus bCritical,6; bonus bHit,6; - autobonus "{ bonus2 bHPRegenRate,500,1000; bonus2 bSPRegenRate,20,1000; }",30,5000,BF_WEAPON|BF_SHORT; + petautobonus "{ bonus2 bHPRegenRate,500,1000; bonus2 bSPRegenRate,20,1000; }",30,5000,BF_WEAPON|BF_SHORT; } else if (.@i >= PET_INTIMATE_CORDIAL) { bonus bCritical,5; bonus bHit,5; - autobonus "{ bonus2 bHPRegenRate,400,1000; bonus2 bSPRegenRate,10,1000; }",30,5000,BF_WEAPON|BF_SHORT; + petautobonus "{ bonus2 bHPRegenRate,400,1000; bonus2 bSPRegenRate,10,1000; }",30,5000,BF_WEAPON|BF_SHORT; } else if (.@i >= PET_INTIMATE_NEUTRAL) { bonus bCritical,4; bonus bHit,4; - autobonus "{ bonus2 bHPRegenRate,300,1000; }",20,5000,BF_WEAPON|BF_SHORT; + petautobonus "{ bonus2 bHPRegenRate,300,1000; }",20,5000,BF_WEAPON|BF_SHORT; } else { bonus bCritical,3; bonus bHit,3; @@ -1667,11 +1667,11 @@ Body: if (.@i >= PET_INTIMATE_LOYAL) { bonus bCritical,5; bonus bHit,5; - autobonus "{ bonus2 bHPRegenRate,400,1000; }",20,5000,BF_WEAPON|BF_SHORT; + petautobonus "{ bonus2 bHPRegenRate,400,1000; }",20,5000,BF_WEAPON|BF_SHORT; } else if (.@i >= PET_INTIMATE_CORDIAL) { bonus bCritical,4; bonus bHit,4; - autobonus "{ bonus2 bHPRegenRate,300,1000; }",20,5000,BF_WEAPON|BF_SHORT; + petautobonus "{ bonus2 bHPRegenRate,300,1000; }",20,5000,BF_WEAPON|BF_SHORT; } else if (.@i >= PET_INTIMATE_NEUTRAL) { bonus bCritical,3; bonus bHit,3; @@ -1780,10 +1780,10 @@ Body: if (.@i >= PET_INTIMATE_LOYAL) { bonus bMaxSP,150; - autobonus "{ bonus2 bSPRegenRate,40,1000; }",30,5000,BF_MAGIC; + petautobonus "{ bonus2 bSPRegenRate,40,1000; }",30,5000,BF_MAGIC; } else if (.@i >= PET_INTIMATE_CORDIAL) { bonus bMaxSP,150; - autobonus "{ bonus2 bSPRegenRate,30,1000; }",30,5000,BF_MAGIC; + petautobonus "{ bonus2 bSPRegenRate,30,1000; }",30,5000,BF_MAGIC; } else if (.@i >= PET_INTIMATE_NEUTRAL) { bonus bMaxSP,100; } else { diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 2d0d39efe5..fa090c58ba 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -6176,9 +6176,7 @@ kind in 'doc/item_bonus.txt'. *autobonus3 ,,,,{}; *autobonus3 ,,,"",{}; -These commands are meant to be used in item scripts. They will probably work -outside item scripts, but the bonus will not persist for long. They, as -expected, refer only to an invoking character. +These commands are meant to be used in item scripts only! See 'petautobonus' for pet usage. What these commands do is 'attach' a script to the player which will get executed on attack (or when attacked in the case of autobonus2). @@ -10313,6 +10311,15 @@ Value of 'rate' is between 1 and 100. 100 = 100% --------------------------------------- +*petautobonus ,,{,,{}}; +*petautobonus2 ,,{,,{}}; +*petautobonus3 ,,,,{}; +*petautobonus3 ,,,"",{}; + +See 'autobonus' for more details. + +--------------------------------------- + =========================== |11.- Homunculus commands.| =========================== diff --git a/src/map/pet.cpp b/src/map/pet.cpp index 457e6a09b7..765a945926 100644 --- a/src/map/pet.cpp +++ b/src/map/pet.cpp @@ -31,6 +31,7 @@ using namespace rathena; +std::unordered_map> pet_autobonuses; const t_tick MIN_PETTHINKTIME = 100; const std::string PetDatabase::getDefaultLocation(){ @@ -2332,6 +2333,113 @@ void pet_evolution(struct map_session_data *sd, int16 pet_id) { clif_inventorylist(sd); } +/** + * Add petautobonus to player when attacking/attacked. + * @param bonus: Bonus + * @param script: Script to execute + * @param rate: Success chance + * @param dur: Duration + * @param flag: Battle flag/skill + * @param other_script: Secondary script to execute + * @param onskill: Skill used to trigger autobonus + * @return True on success or false otherwise + */ +bool pet_addautobonus(std::vector> &bonus, const std::string &script, int16 rate, uint32 dur, uint16 flag, const std::string &other_script, bool onskill) { + if (bonus.size() == MAX_PC_BONUS) { + ShowWarning("pet_addautobonus: Reached max (%d) number of petautobonus per pet!\n", MAX_PC_BONUS); + return false; + } + + if (!onskill) { + if (!(flag & BF_RANGEMASK)) + flag |= BF_SHORT | BF_LONG; //No range defined? Use both. + if (!(flag & BF_WEAPONMASK)) + flag |= BF_WEAPON; //No attack type defined? Use weapon. + if (!(flag & BF_SKILLMASK)) { + if (flag & (BF_MAGIC | BF_MISC)) + flag |= BF_SKILL; //These two would never trigger without BF_SKILL + if (flag & BF_WEAPON) + flag |= BF_NORMAL | BF_SKILL; + } + } + + + if (rate < -10000 || rate > 10000) + ShowWarning("pet_addautobonus: Bonus rate %d exceeds -10000~10000 range, capping.\n", rate); + + std::shared_ptr entry = std::make_shared(); + + entry->rate = cap_value(rate, -10000, 10000); + entry->duration = dur; + entry->timer = INVALID_TIMER; + entry->atk_type = flag; + entry->bonus_script = script; + entry->other_script = other_script; + + bonus.push_back(entry); + + return true; +} + +/** + * Remove a petautobonus from player. + * @param sd: Player data + * @param bonus: Autobonus + * @param restore: Run script on clearing or not + */ +void pet_delautobonus(map_session_data &sd, std::vector> &bonus, bool restore) { + std::vector>::iterator it = bonus.begin(); + + while (it != bonus.end()) { + std::shared_ptr b = *it; + + if (b->timer != INVALID_TIMER && !b->bonus_script.empty() && restore) { + script_run_petautobonus(b->bonus_script, sd); + + it = bonus.erase(it); + } + } +} + +/** + * Execute petautobonus on player. + * @param sd: Player data + * @param bonus: Bonus vector + * @param autobonus: Autobonus to run + */ +void pet_exeautobonus(map_session_data &sd, std::vector> *bonus, std::shared_ptr &autobonus) { + if (autobonus->timer != INVALID_TIMER) + delete_timer(autobonus->timer, pet_endautobonus); + + if (!autobonus->other_script.empty()) { + script_run_petautobonus(autobonus->other_script, sd); + } + + autobonus->timer = add_timer(gettick() + autobonus->duration, pet_endautobonus, sd.bl.id, (intptr_t)&bonus); + status_calc_pc(&sd, SCO_FORCE); +} + +/** + * Remove petautobonus timer from player. + */ +TIMER_FUNC(pet_endautobonus) { + map_session_data *sd = map_id2sd(id); + std::vector> *bonus = (std::vector> *)data; + + nullpo_ret(sd); + nullpo_ret(bonus); + + for (std::shared_ptr autobonus : *bonus) { + if (autobonus->timer == tid) { + autobonus->timer = INVALID_TIMER; + break; + } + } + + status_calc_pc(sd, SCO_FORCE); + return 0; +} + /** * Initialize pet data. */ @@ -2349,6 +2457,7 @@ void do_init_pet(void) add_timer_func_list(pet_skill_support_timer, "pet_skill_support_timer"); // [Skotlex] add_timer_func_list(pet_recovery_timer,"pet_recovery_timer"); // [Valaris] add_timer_func_list(pet_heal_timer,"pet_heal_timer"); // [Valaris] + add_timer_func_list(pet_endautobonus, "pet_endautobonus"); add_timer_interval(gettick()+MIN_PETTHINKTIME,pet_ai_hard,0,0,MIN_PETTHINKTIME); } @@ -2360,5 +2469,7 @@ void do_final_pet(void) ers_destroy(item_drop_ers); ers_destroy(item_drop_list_ers); + pet_autobonuses.clear(); + pet_db.clear(); } diff --git a/src/map/pet.hpp b/src/map/pet.hpp index 84c5e6dc9d..aaf9fdc82f 100644 --- a/src/map/pet.hpp +++ b/src/map/pet.hpp @@ -144,6 +144,42 @@ public: extern PetDatabase pet_db; +TIMER_FUNC(pet_endautobonus); + +/// Pet AutoBonus bonus struct +struct s_petautobonus { + int16 rate; + uint16 atk_type; + std::string bonus_script, other_script; + t_tick duration; + int32 timer; + + ~s_petautobonus() { + if (this->timer != INVALID_TIMER) { + delete_timer(this->timer, pet_endautobonus); + this->timer = INVALID_TIMER; + } + + this->bonus_script.clear(); + this->other_script.clear(); + } + +}; + +/// Pet Autobonus database wrapper +struct s_pet_autobonus_wrapper { + script_code *script; + + ~s_pet_autobonus_wrapper() { + if (this->script != nullptr) { + script_free_code(this->script); + this->script = nullptr; + } + } +}; + +extern std::unordered_map> pet_autobonuses; + struct pet_data { struct block_list bl; struct unit_data ud; @@ -165,6 +201,7 @@ struct pet_data { struct pet_skill_attack* a_skill; struct pet_skill_support* s_skill; struct pet_loot* loot; + std::vector> autobonus, autobonus2, autobonus3; int masterteleport_timer; struct map_session_data *master; @@ -220,6 +257,10 @@ void pet_clear_support_bonuses(struct map_session_data *sd); #define pet_stop_walking(pd, type) unit_stop_walking(&(pd)->bl, type) #define pet_stop_attack(pd) unit_stop_attack(&(pd)->bl) +bool pet_addautobonus(std::vector> &bonus, const std::string &script, int16 rate, uint32 dur, uint16 atk_type, const std::string &other_script, bool onskill); +void pet_exeautobonus(map_session_data &sd, std::vector> *bonus, std::shared_ptr &autobonus); +void pet_delautobonus(map_session_data &sd, std::vector> &bonus, bool restore); + void do_init_pet(void); void do_final_pet(void); diff --git a/src/map/script.cpp b/src/map/script.cpp index c8d4f0493b..bcbb263f0c 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -4613,6 +4613,27 @@ void script_add_autobonus(const char *autobonus) } } +void script_run_petautobonus(const std::string &autobonus, map_session_data &sd) { + std::shared_ptr script = util::umap_find(pet_autobonuses, autobonus); + + if (script != nullptr) { + run_script(script->script, 0, sd.bl.id, 0); + } +} + +void script_add_petautobonus(const std::string &autobonus) { + if (util::umap_find(pet_autobonuses, autobonus) == nullptr) { + script_code *script = parse_script(autobonus.c_str(), "petautobonus", 0, 0); + + if (script != nullptr) { + std::shared_ptr bonus = std::make_shared(); + + bonus->script = script; + + pet_autobonuses.emplace(autobonus, bonus); + } + } +} /// resets a temporary character array variable to given value void script_cleararray_pc( struct map_session_data* sd, const char* varname ){ @@ -10065,6 +10086,96 @@ BUILDIN_FUNC(autobonus3) return SCRIPT_CMD_SUCCESS; } +BUILDIN_FUNC(petautobonus) { + map_session_data *sd; + + if (!script_rid2sd(sd)) + return SCRIPT_CMD_FAILURE; // No player attached + + const char *command = script_getfuncname(st); + + if (sd->pd == nullptr) { + ShowError("buildin_%s: Requires an active pet.\n", command); + return SCRIPT_CMD_FAILURE; // No pet attached to player + } + + std::string bonus_script = script_getstr(st, 2); + int16 rate = script_getnum(st, 3); + uint32 dur = script_getnum(st, 4); + + if (!rate || !dur || bonus_script.empty()) + return SCRIPT_CMD_FAILURE; + + uint16 atk_type = 0; + std::string other_script = {}; + bool bonus2 = false; + + if (strcmpi(command, "petautobonus2") == 0) + bonus2 = true; + + if (script_hasdata(st, 5)) + atk_type = script_getnum(st, 5); + if (script_hasdata(st, 6)) + other_script = script_getstr(st, 6); + + if (pet_addautobonus(bonus2 ? sd->pd->autobonus2 : sd->pd->autobonus, bonus_script, rate, dur, atk_type, other_script, false)) { + script_add_petautobonus(bonus_script); + if (!other_script.empty()) + script_add_petautobonus(other_script); + } + + return SCRIPT_CMD_SUCCESS; +} + +BUILDIN_FUNC(petautobonus3) { + map_session_data *sd; + + if (!script_rid2sd(sd)) + return SCRIPT_CMD_SUCCESS; // No player attached + + if (sd->pd == nullptr) { + ShowError("buildin_petautobonus3: Requires an active pet.\n"); + return SCRIPT_CMD_FAILURE; // No pet attached to player + } + + std::string bonus_script = script_getstr(st, 2); + int16 rate = script_getnum(st, 3); + uint32 dur = script_getnum(st, 4); + + if (!rate || !dur || bonus_script.empty()) + return SCRIPT_CMD_SUCCESS; + + uint16 skill_id = 0; + std::string other_script = {}; + + if (script_isstring(st, 5)) { + const char *name = script_getstr(st, 5); + + if (!(skill_id = skill_name2id(name))) { + ShowError("buildin_petautobonus3: Invalid skill name %s passed to item bonus. Skipping.\n", name); + return SCRIPT_CMD_FAILURE; + } + } else { + skill_id = script_getnum(st, 5); + + if (!skill_get_index(skill_id)) { + ShowError("buildin_petautobonus3: Invalid skill ID %d passed to item bonus. Skipping.\n", skill_id); + return SCRIPT_CMD_FAILURE; + } + } + + if (script_hasdata(st, 6)) + other_script = script_getstr(st, 6); + + if (pet_addautobonus(sd->pd->autobonus3, bonus_script, rate, dur, skill_id, other_script, true)) { + script_add_petautobonus(bonus_script); + if (!other_script.empty()) + script_add_petautobonus(other_script); + } + + return SCRIPT_CMD_SUCCESS; +} + /// Changes the level of a player skill. /// defaults to 1 /// =0 : set the level of the skill @@ -26578,6 +26689,9 @@ struct script_function buildin_func[] = { BUILDIN_DEF(autobonus,"sii??"), BUILDIN_DEF(autobonus2,"sii??"), BUILDIN_DEF(autobonus3,"siiv?"), + BUILDIN_DEF(petautobonus,"sii??"), + BUILDIN_DEF2(petautobonus,"petautobonus2","sii??"), + BUILDIN_DEF(petautobonus3,"siiv?"), BUILDIN_DEF(skill,"vi?"), BUILDIN_DEF2(skill,"addtoskill","vi?"), // [Valaris] BUILDIN_DEF(guildskill,"vi"), diff --git a/src/map/script.hpp b/src/map/script.hpp index 0a0c759771..17eedd4621 100644 --- a/src/map/script.hpp +++ b/src/map/script.hpp @@ -2169,6 +2169,7 @@ void script_free_state(struct script_state* st); struct DBMap* script_get_label_db(void); struct DBMap* script_get_userfunc_db(void); void script_run_autobonus(const char *autobonus, struct map_session_data *sd, unsigned int pos); +void script_run_petautobonus(const std::string &autobonus, map_session_data &sd); const char* script_get_constant_str(const char* prefix, int64 value); bool script_get_parameter(const char* name, int64* value); diff --git a/src/map/skill.cpp b/src/map/skill.cpp index 37cd6ab939..42a75ac6e5 100755 --- a/src/map/skill.cpp +++ b/src/map/skill.cpp @@ -2360,21 +2360,38 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1 } } - //Autobonus when attacking - if( sd && !sd->autobonus.empty() ) - { - for(auto &it : sd->autobonus) { - if( it == nullptr ){ - continue; - } + // Check for player and pet autobonuses when attacking + if (sd != nullptr) { + // Player + if (!sd->autobonus.empty()) { + for (auto &it : sd->autobonus) { + if (it == nullptr) + continue; + if (rnd_value(0, 1000) >= it->rate) + continue; + if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && + ((it->atk_type) & attack_type) & BF_RANGEMASK && + ((it->atk_type) & attack_type) & BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled - if (rnd()%1000 >= it->rate) - continue; - if (!(((it->atk_type)&attack_type)&BF_WEAPONMASK && - ((it->atk_type)&attack_type)&BF_RANGEMASK && - ((it->atk_type)&attack_type)&BF_SKILLMASK)) - continue; // one or more trigger conditions were not fulfilled - pc_exeautobonus(*sd, &sd->autobonus, it); + pc_exeautobonus(*sd, &sd->autobonus, it); + } + } + + // Pet + if (sd->pd != nullptr && !sd->pd->autobonus.empty()) { + for (auto &it : sd->pd->autobonus) { + if (it == nullptr) + continue; + if (rnd_value(0, 1000) >= it->rate) + continue; + if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && + ((it->atk_type) & attack_type) & BF_RANGEMASK && + ((it->atk_type) & attack_type) & BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + + pet_exeautobonus(*sd, &sd->pd->autobonus, it); + } } } @@ -2461,15 +2478,34 @@ int skill_onskillusage(struct map_session_data *sd, struct block_list *bl, uint1 sd->state.autocast = 0; } - if( sd && !sd->autobonus3.empty() ) { - for (auto &it : sd->autobonus3) { - if (it == nullptr) - continue; - if (rnd()%1000 >= it->rate) - continue; - if (it->atk_type != skill_id) - continue; - pc_exeautobonus(*sd, &sd->autobonus3, it); + // Check for player and pet autobonuses when being attacked by skill_id + if (sd != nullptr) { + // Player + if (!sd->autobonus3.empty()) { + for (auto &it : sd->autobonus3) { + if (it == nullptr) + continue; + if (rnd_value(0, 1000) >= it->rate) + continue; + if (it->atk_type != skill_id) + continue; + + pc_exeautobonus(*sd, &sd->autobonus3, it); + } + } + + // Pet + if (sd->pd != nullptr && !sd->pd->autobonus3.empty()) { + for (auto &it : sd->pd->autobonus3) { + if (it == nullptr) + continue; + if (rnd_value(0, 1000) >= it->rate) + continue; + if (it->atk_type != skill_id) + continue; + + pet_exeautobonus(*sd, &sd->pd->autobonus3, it); + } } } @@ -2690,20 +2726,38 @@ int skill_counter_additional_effect (struct block_list* src, struct block_list * } } - //Autobonus when attacked - if( dstsd && !status_isdead(bl) && !dstsd->autobonus2.empty() && !(skill_id && skill_get_nk(skill_id, NK_NODAMAGE)) ) { - for (auto &it : dstsd->autobonus2) { - if( it == nullptr ){ - continue; - } + // Check for player and pet autobonuses when attacked + if (dstsd != nullptr && !status_isdead(bl) && !(skill_id && skill_get_nk(skill_id, NK_NODAMAGE))) { + // Player + if (!dstsd->autobonus2.empty()) { + for (auto &it : dstsd->autobonus2) { + if (it == nullptr) + continue; + if (rnd_value(0, 1000) >= it->rate) + continue; + if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && + ((it->atk_type) & attack_type) & BF_RANGEMASK && + ((it->atk_type) & attack_type) & BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled - if (rnd()%1000 >= it->rate) - continue; - if (!(((it->atk_type)&attack_type)&BF_WEAPONMASK && - ((it->atk_type)&attack_type)&BF_RANGEMASK && - ((it->atk_type)&attack_type)&BF_SKILLMASK)) - continue; // one or more trigger conditions were not fulfilled - pc_exeautobonus(*dstsd, &dstsd->autobonus2, it); + pc_exeautobonus(*dstsd, &dstsd->autobonus2, it); + } + } + + // Pet + if (dstsd->pd != nullptr && !dstsd->pd->autobonus2.empty()) { + for (auto &it : dstsd->pd->autobonus2) { + if (it == nullptr) + continue; + if (rnd_value(0, 1000) >= it->rate) + continue; + if (!(((it->atk_type) & attack_type) & BF_WEAPONMASK && + ((it->atk_type) & attack_type) & BF_RANGEMASK && + ((it->atk_type) & attack_type) & BF_SKILLMASK)) + continue; // one or more trigger conditions were not fulfilled + + pet_exeautobonus(*dstsd, &dstsd->pd->autobonus2, it); + } } } diff --git a/src/map/status.cpp b/src/map/status.cpp index 06558e9635..31b8515858 100644 --- a/src/map/status.cpp +++ b/src/map/status.cpp @@ -3641,6 +3641,12 @@ int status_calc_pc_sub(struct map_session_data* sd, uint8 opt) pc_delautobonus(*sd, sd->autobonus2, true); pc_delautobonus(*sd, sd->autobonus3, true); + if (sd->pd != nullptr) { + pet_delautobonus(*sd, sd->pd->autobonus, true); + pet_delautobonus(*sd, sd->pd->autobonus2, true); + pet_delautobonus(*sd, sd->pd->autobonus3, true); + } + // Parse equipment for (i = 0; i < EQI_MAX; i++) { current_equip_item_index = index = sd->equip_index[i]; // We pass INDEX to current_equip_item_index - for EQUIP_SCRIPT (new cards solution) [Lupus] diff --git a/src/map/unit.cpp b/src/map/unit.cpp index 854c29cb89..435ff0b18a 100644 --- a/src/map/unit.cpp +++ b/src/map/unit.cpp @@ -3464,6 +3464,10 @@ int unit_free(struct block_list *bl, clr_type clrtype) struct pet_data *pd = (struct pet_data*)bl; struct map_session_data *sd = pd->master; + pet_delautobonus(*sd, pd->autobonus, false); + pet_delautobonus(*sd, pd->autobonus2, false); + pet_delautobonus(*sd, pd->autobonus3, false); + pet_hungry_timer_delete(pd); pet_clear_support_bonuses(sd);