diff --git a/conf/map/battle/skill.conf b/conf/map/battle/skill.conf index 0b4d3bae121..bb66dc8d095 100644 --- a/conf/map/battle/skill.conf +++ b/conf/map/battle/skill.conf @@ -119,6 +119,11 @@ skill_min_damage: 6 // The delay rate of monk's combo (Note 2) combo_delay_rate: 100 +// Store and postpone execution of combo skills during combo delay instead of rejecting usage? +// Makes executing combo skills with delays more intuitive and easy +// Official behavior is false +combo_cache_skill: false + // Use alternate auto Counter Attack Skill Type? (Note 3) // For those characters on which it is set, 100% Critical, // Otherwise it disregard DEF and HIT+20, CRIT*2 diff --git a/src/map/battle.c b/src/map/battle.c index 3c9816de94c..a4850d34362 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -7524,6 +7524,7 @@ static const struct config_data_old battle_data[] = { { "max_heal_lv", &battle_config.max_heal_lv, 11, 1, INT_MAX, }, { "max_heal", &battle_config.max_heal, 9999, 0, INT_MAX, }, { "combo_delay_rate", &battle_config.combo_delay_rate, 100, 0, INT_MAX, }, + { "combo_cache_skill", &battle_config.combo_cache_skill, 0, 0, 1, }, { "item_check", &battle_config.item_check, 0, 0, 0xF, }, { "item_use_interval", &battle_config.item_use_interval, 100, 0, INT_MAX, }, { "wedding_modifydisplay", &battle_config.wedding_modifydisplay, 0, 0, 1, }, @@ -7758,8 +7759,8 @@ static const struct config_data_old battle_data[] = { { "client_sort_storage", &battle_config.client_sort_storage, 0, 0, 1, }, { "features/buying_store", &battle_config.feature_buying_store, 1, 0, 1, }, { "features/search_stores", &battle_config.feature_search_stores, 1, 0, 1, }, - { "searchstore_querydelay", &battle_config.searchstore_querydelay, 10, 0, INT_MAX, }, - { "searchstore_maxresults", &battle_config.searchstore_maxresults, 30, 1, INT_MAX, }, + { "searchstore_querydelay", &battle_config.searchstore_querydelay, 10, 0, INT_MAX, }, + { "searchstore_maxresults", &battle_config.searchstore_maxresults, 30, 1, INT_MAX, }, { "display_party_name", &battle_config.display_party_name, 0, 0, 1, }, { "send_party_options", &battle_config.send_party_options, 0x31F9, 0, 0x1FFFF, }, { "cashshop_show_points", &battle_config.cashshop_show_points, 0, 0, 1, }, @@ -7799,10 +7800,10 @@ static const struct config_data_old battle_data[] = { { "guild_notice_changemap", &battle_config.guild_notice_changemap, 7, 0, 7, }, { "features/banking", &battle_config.feature_banking, 1, 0, 1, }, { "features/auction", &battle_config.feature_auction, 0, 0, 2, }, - { "idletime_criteria", &battle_config.idletime_criteria, 0x25, 1, INT_MAX, }, + { "idletime_criteria", &battle_config.idletime_criteria, 0x25, 1, INT_MAX, }, { "mon_trans_disable_in_gvg", &battle_config.mon_trans_disable_in_gvg, 0, 0, 1, }, { "case_sensitive_aegisnames", &battle_config.case_sensitive_aegisnames, 1, 0, 1, }, - { "search_freecell_map_margin", &battle_config.search_freecell_map_margin, 15, 0, INT_MAX, }, + { "search_freecell_map_margin", &battle_config.search_freecell_map_margin, 15, 0, INT_MAX, }, { "guild_castle_invite", &battle_config.guild_castle_invite, 0, 0, 1, }, { "guild_castle_expulsion", &battle_config.guild_castle_expulsion, 0, 0, 1, }, { "song_timer_reset", &battle_config.song_timer_reset, 0, 0, 1, }, @@ -7866,30 +7867,30 @@ static const struct config_data_old battle_data[] = { { "hit_min_limit", &battle_config.hit_min, 1, 1, INT_MAX, }, { "hit_max_limit", &battle_config.hit_max, SHRT_MAX, 1, INT_MAX, }, { "autoloot_adjust", &battle_config.autoloot_adjust, 0, 0, 1, }, - { "hom_bonus_exp_from_master", &battle_config.hom_bonus_exp_from_master, 10, 0, 100, }, + { "hom_bonus_exp_from_master", &battle_config.hom_bonus_exp_from_master, 10, 0, 100, }, { "allowed_actions_when_dead", &battle_config.allowed_actions_when_dead, 0, 0, 3, }, { "teleport_close_storage", &battle_config.teleport_close_storage, 1, 0, 1, }, { "features/show_attendance_window", &battle_config.show_attendance_window, 1, 0, 1, }, - { "elem_natural_heal_hp", &battle_config.elem_natural_heal_hp, 6000, NATURAL_HEAL_INTERVAL, INT_MAX,}, - { "elem_natural_heal_sp", &battle_config.elem_natural_heal_sp, 8000, NATURAL_HEAL_INTERVAL, INT_MAX,}, - { "elem_natural_heal_cap", &battle_config.elem_natural_heal_cap, 1000, 1, INT_MAX, }, - { "hom_natural_heal_hp", &battle_config.hom_natural_heal_hp, 2000, NATURAL_HEAL_INTERVAL, INT_MAX,}, - { "hom_natural_heal_sp", &battle_config.hom_natural_heal_sp, 4000, NATURAL_HEAL_INTERVAL, INT_MAX,}, - { "hom_natural_heal_cap", &battle_config.hom_natural_heal_cap, 1000, 1, INT_MAX, }, - { "merc_natural_heal_hp", &battle_config.merc_natural_heal_hp, 6000, NATURAL_HEAL_INTERVAL, INT_MAX,}, - { "merc_natural_heal_sp", &battle_config.merc_natural_heal_sp, 8000, NATURAL_HEAL_INTERVAL, INT_MAX,}, - { "merc_natural_heal_cap", &battle_config.merc_natural_heal_cap, 1000, 1, INT_MAX, }, - { "macro_detect_retry", &battle_config.macro_detect_retry, 1, 1, INT_MAX, }, - { "macro_detect_timeout", &battle_config.macro_detect_timeout, 0, 0, INT_MAX, }, - { "roulette_gold_step", &battle_config.roulette_gold_step, 10, 1, INT_MAX, }, - { "roulette_silver_step", &battle_config.roulette_silver_step, 10, 1, INT_MAX, }, - { "roulette_bronze_step", &battle_config.roulette_bronze_step, 1, 1, INT_MAX, }, - { "features/grader_max_used", &battle_config.grader_max_used, 0, 0, MAX_ITEM_GRADE, }, - { "dynamic_npc_timeout", &battle_config.dynamic_npc_timeout, 0, 0, INT_MAX, }, - { "dynamic_npc_range", &battle_config.dynamic_npc_range, 0, 0, INT_MAX, }, - { "features/goldpc/enable", &battle_config.feature_goldpc_enable, 0, 0, 1, }, - { "features/goldpc/default_mode", &battle_config.feature_goldpc_default_mode, 1, 0, INT_MAX, }, - { "venom_dust_exp", &battle_config.venom_dust_exp, 0, 0, 1, }, + { "elem_natural_heal_hp", &battle_config.elem_natural_heal_hp, 6000, NATURAL_HEAL_INTERVAL, INT_MAX,}, + { "elem_natural_heal_sp", &battle_config.elem_natural_heal_sp, 8000, NATURAL_HEAL_INTERVAL, INT_MAX,}, + { "elem_natural_heal_cap", &battle_config.elem_natural_heal_cap, 1000, 1, INT_MAX, }, + { "hom_natural_heal_hp", &battle_config.hom_natural_heal_hp, 2000, NATURAL_HEAL_INTERVAL, INT_MAX,}, + { "hom_natural_heal_sp", &battle_config.hom_natural_heal_sp, 4000, NATURAL_HEAL_INTERVAL, INT_MAX,}, + { "hom_natural_heal_cap", &battle_config.hom_natural_heal_cap, 1000, 1, INT_MAX, }, + { "merc_natural_heal_hp", &battle_config.merc_natural_heal_hp, 6000, NATURAL_HEAL_INTERVAL, INT_MAX,}, + { "merc_natural_heal_sp", &battle_config.merc_natural_heal_sp, 8000, NATURAL_HEAL_INTERVAL, INT_MAX,}, + { "merc_natural_heal_cap", &battle_config.merc_natural_heal_cap, 1000, 1, INT_MAX, }, + { "macro_detect_retry", &battle_config.macro_detect_retry, 1, 1, INT_MAX, }, + { "macro_detect_timeout", &battle_config.macro_detect_timeout, 0, 0, INT_MAX, }, + { "roulette_gold_step", &battle_config.roulette_gold_step, 10, 1, INT_MAX, }, + { "roulette_silver_step", &battle_config.roulette_silver_step, 10, 1, INT_MAX, }, + { "roulette_bronze_step", &battle_config.roulette_bronze_step, 1, 1, INT_MAX, }, + { "features/grader_max_used", &battle_config.grader_max_used, 0, 0, MAX_ITEM_GRADE, }, + { "dynamic_npc_timeout", &battle_config.dynamic_npc_timeout, 0, 0, INT_MAX, }, + { "dynamic_npc_range", &battle_config.dynamic_npc_range, 0, 0, INT_MAX, }, + { "features/goldpc/enable", &battle_config.feature_goldpc_enable, 0, 0, 1, }, + { "features/goldpc/default_mode", &battle_config.feature_goldpc_default_mode, 1, 0, INT_MAX, }, + { "venom_dust_exp", &battle_config.venom_dust_exp, 0, 0, 1, }, }; static bool battle_set_value_sub(int index, int value) diff --git a/src/map/battle.h b/src/map/battle.h index 6edce81f964..d8fdd044761 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -239,6 +239,7 @@ struct Battle_Config { int resurrection_exp; int shop_exp; int combo_delay_rate; + int combo_cache_skill; int item_check; int item_use_interval; //[Skotlex] int wedding_modifydisplay; diff --git a/src/map/clif.c b/src/map/clif.c index 3ff3ed96477..951a01d3269 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -13490,8 +13490,8 @@ static void clif_parse_UseSkillToPos_mercenary(struct mercenary_data *md, struct unit->skilluse_pos(&md->bl, x, y, skill_id, skill_lv); } -static void clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id) __attribute__((nonnull (2))); -static void clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id) +static void clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id, bool skip_combo_check) __attribute__((nonnull (2))); +static void clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id, bool skip_combo_check) { int64 tick = timer->gettick(); @@ -13562,6 +13562,18 @@ static void clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill if (skill_id != SA_CASTCANCEL && skill_id != SO_SPELLFIST && sd->auto_cast_current.type == AUTOCAST_NONE) return; } else if (DIFF_TICK(tick, sd->ud.canact_tick) < 0) { + // Cannot perform skill yet, delay execution until canact_tick allows it + // Only for combo skills with implicit delays, otherwise we create a speedhack that bypasses client's animation delay + if (battle_config.combo_cache_skill + && !skip_combo_check + && sd->sc.data[SC_COMBOATTACK] != NULL + && skill->is_combo(skill_id) + && skill->get_delaynodex(skill_id, skill_lv) != 0 + ) { + timer->add(sd->ud.canact_tick, clif->combo_delay_timer, sd->bl.id, (intptr_t)MakeDWord((uint16)skill_id, (uint16)skill_lv)); + return; + } + if (sd->auto_cast_current.type == AUTOCAST_NONE) { clif->skill_fail(sd, skill_id, USESKILL_FAIL_SKILLINTERVAL, 0, 0); return; @@ -13608,6 +13620,21 @@ static void clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill unit->skilluse_id(&sd->bl, target_id, skill_id, skill_lv); } +/// Trigger next combo skill as previously requested by client +static int clif_combo_delay_timer(int tid, int64 tick, int id, intptr_t data) { + struct map_session_data* sd = map->id2sd(id); + + if (sd == NULL) + return 0; + + int skill_id = (int)GetWord((uint32)data, 0); + int skill_lv = (int)GetWord((uint32)data, 1); + + clif->useSkillToIdReal(0, sd, skill_id, skill_lv, battle->get_target(&sd->bl), true); + + return 0; +} + static void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); /// Request to use a targeted skill. /// 0113 .W .W .L (CZ_USE_SKILL) @@ -13619,7 +13646,8 @@ static void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) sd, RFIFOW(fd, packet_db[RFIFOW(fd, 0)].pos[1]), RFIFOW(fd, packet_db[RFIFOW(fd, 0)].pos[0]), - RFIFOL(fd, packet_db[RFIFOW(fd, 0)].pos[2])); + RFIFOL(fd, packet_db[RFIFOW(fd, 0)].pos[2]), + false); } static void clif_parse_startUseSkillToId(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); @@ -13627,7 +13655,7 @@ static void clif_parse_startUseSkillToId(int fd, struct map_session_data *sd) { #if PACKETVER_MAIN_NUM >= 20181002 || PACKETVER_RE_NUM >= 20181002 || PACKETVER_ZERO_NUM >= 20181010 const struct PACKET_CZ_USE_SKILL_START *p = RFIFOP(fd, 0); - clif->useSkillToIdReal(fd, sd, p->skillId, p->skillLv, p->targetId); + clif->useSkillToIdReal(fd, sd, p->skillId, p->skillLv, p->targetId, false); #endif } @@ -27288,6 +27316,7 @@ void clif_defaults(void) clif->clan_leave = clif_clan_leave; clif->clan_message = clif_clan_message; clif->pClanMessage = clif_parse_ClanMessage; + clif->combo_delay_timer = clif_combo_delay_timer; // -- Hat Effect clif->hat_effect = clif_hat_effect; clif->hat_effect_single = clif_hat_effect_single; diff --git a/src/map/clif.h b/src/map/clif.h index 01213081c44..9475f2a0e68 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -1582,7 +1582,8 @@ struct clif_interface { void (*pChangeCart) (int fd,struct map_session_data *sd); void (*pStatusUp) (int fd,struct map_session_data *sd); void (*pSkillUp) (int fd,struct map_session_data *sd); - void (*useSkillToIdReal) (int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id); + void (*useSkillToIdReal) (int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id, bool skip_combo_check); + int (*combo_delay_timer)(int tid, int64 tick, int id, intptr_t data); void (*pUseSkillToId) (int fd, struct map_session_data *sd); void (*pStartUseSkillToId) (int fd, struct map_session_data *sd); void (*pStopUseSkillToId) (int fd, struct map_session_data *sd); diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc index ffcd18b114a..480ca7d4587 100644 --- a/src/plugins/HPMHooking/HPMHooking.Defs.inc +++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc @@ -2458,8 +2458,10 @@ typedef void (*HPMHOOK_pre_clif_pStatusUp) (int *fd, struct map_session_data **s typedef void (*HPMHOOK_post_clif_pStatusUp) (int fd, struct map_session_data *sd); typedef void (*HPMHOOK_pre_clif_pSkillUp) (int *fd, struct map_session_data **sd); typedef void (*HPMHOOK_post_clif_pSkillUp) (int fd, struct map_session_data *sd); -typedef void (*HPMHOOK_pre_clif_useSkillToIdReal) (int *fd, struct map_session_data **sd, int *skill_id, int *skill_lv, int *target_id); -typedef void (*HPMHOOK_post_clif_useSkillToIdReal) (int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id); +typedef void (*HPMHOOK_pre_clif_useSkillToIdReal) (int *fd, struct map_session_data **sd, int *skill_id, int *skill_lv, int *target_id, bool *skip_combo_check); +typedef void (*HPMHOOK_post_clif_useSkillToIdReal) (int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id, bool skip_combo_check); +typedef int (*HPMHOOK_pre_clif_combo_delay_timer) (int *tid, int64 *tick, int *id, intptr_t *data); +typedef int (*HPMHOOK_post_clif_combo_delay_timer) (int retVal___, int tid, int64 tick, int id, intptr_t data); typedef void (*HPMHOOK_pre_clif_pUseSkillToId) (int *fd, struct map_session_data **sd); typedef void (*HPMHOOK_post_clif_pUseSkillToId) (int fd, struct map_session_data *sd); typedef void (*HPMHOOK_pre_clif_pStartUseSkillToId) (int *fd, struct map_session_data **sd); diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc index e82ebbe6ce7..43b90b00356 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc @@ -1818,6 +1818,8 @@ struct { struct HPMHookPoint *HP_clif_pSkillUp_post; struct HPMHookPoint *HP_clif_useSkillToIdReal_pre; struct HPMHookPoint *HP_clif_useSkillToIdReal_post; + struct HPMHookPoint *HP_clif_combo_delay_timer_pre; + struct HPMHookPoint *HP_clif_combo_delay_timer_post; struct HPMHookPoint *HP_clif_pUseSkillToId_pre; struct HPMHookPoint *HP_clif_pUseSkillToId_post; struct HPMHookPoint *HP_clif_pStartUseSkillToId_pre; @@ -9373,6 +9375,8 @@ struct { int HP_clif_pSkillUp_post; int HP_clif_useSkillToIdReal_pre; int HP_clif_useSkillToIdReal_post; + int HP_clif_combo_delay_timer_pre; + int HP_clif_combo_delay_timer_post; int HP_clif_pUseSkillToId_pre; int HP_clif_pUseSkillToId_post; int HP_clif_pStartUseSkillToId_pre; diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc index 386ec7093ed..0e7ea52e124 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc @@ -934,6 +934,7 @@ struct HookingPointData HookingPoints[] = { { HP_POP(clif->pStatusUp, HP_clif_pStatusUp) }, { HP_POP(clif->pSkillUp, HP_clif_pSkillUp) }, { HP_POP(clif->useSkillToIdReal, HP_clif_useSkillToIdReal) }, + { HP_POP(clif->combo_delay_timer, HP_clif_combo_delay_timer) }, { HP_POP(clif->pUseSkillToId, HP_clif_pUseSkillToId) }, { HP_POP(clif->pStartUseSkillToId, HP_clif_pStartUseSkillToId) }, { HP_POP(clif->pStopUseSkillToId, HP_clif_pStopUseSkillToId) }, diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc index 431d9dc28c8..09ef646e18c 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc @@ -23680,14 +23680,14 @@ void HP_clif_pSkillUp(int fd, struct map_session_data *sd) { } return; } -void HP_clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id) { +void HP_clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id, bool skip_combo_check) { int hIndex = 0; if (HPMHooks.count.HP_clif_useSkillToIdReal_pre > 0) { - void (*preHookFunc) (int *fd, struct map_session_data **sd, int *skill_id, int *skill_lv, int *target_id); + void (*preHookFunc) (int *fd, struct map_session_data **sd, int *skill_id, int *skill_lv, int *target_id, bool *skip_combo_check); *HPMforce_return = false; for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_useSkillToIdReal_pre; hIndex++) { preHookFunc = HPMHooks.list.HP_clif_useSkillToIdReal_pre[hIndex].func; - preHookFunc(&fd, &sd, &skill_id, &skill_lv, &target_id); + preHookFunc(&fd, &sd, &skill_id, &skill_lv, &target_id, &skip_combo_check); } if (*HPMforce_return) { *HPMforce_return = false; @@ -23695,17 +23695,44 @@ void HP_clif_useSkillToIdReal(int fd, struct map_session_data *sd, int skill_id, } } { - HPMHooks.source.clif.useSkillToIdReal(fd, sd, skill_id, skill_lv, target_id); + HPMHooks.source.clif.useSkillToIdReal(fd, sd, skill_id, skill_lv, target_id, skip_combo_check); } if (HPMHooks.count.HP_clif_useSkillToIdReal_post > 0) { - void (*postHookFunc) (int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id); + void (*postHookFunc) (int fd, struct map_session_data *sd, int skill_id, int skill_lv, int target_id, bool skip_combo_check); for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_useSkillToIdReal_post; hIndex++) { postHookFunc = HPMHooks.list.HP_clif_useSkillToIdReal_post[hIndex].func; - postHookFunc(fd, sd, skill_id, skill_lv, target_id); + postHookFunc(fd, sd, skill_id, skill_lv, target_id, skip_combo_check); } } return; } +int HP_clif_combo_delay_timer(int tid, int64 tick, int id, intptr_t data) { + int hIndex = 0; + int retVal___ = 0; + if (HPMHooks.count.HP_clif_combo_delay_timer_pre > 0) { + int (*preHookFunc) (int *tid, int64 *tick, int *id, intptr_t *data); + *HPMforce_return = false; + for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_combo_delay_timer_pre; hIndex++) { + preHookFunc = HPMHooks.list.HP_clif_combo_delay_timer_pre[hIndex].func; + retVal___ = preHookFunc(&tid, &tick, &id, &data); + } + if (*HPMforce_return) { + *HPMforce_return = false; + return retVal___; + } + } + { + retVal___ = HPMHooks.source.clif.combo_delay_timer(tid, tick, id, data); + } + if (HPMHooks.count.HP_clif_combo_delay_timer_post > 0) { + int (*postHookFunc) (int retVal___, int tid, int64 tick, int id, intptr_t data); + for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_combo_delay_timer_post; hIndex++) { + postHookFunc = HPMHooks.list.HP_clif_combo_delay_timer_post[hIndex].func; + retVal___ = postHookFunc(retVal___, tid, tick, id, data); + } + } + return retVal___; +} void HP_clif_pUseSkillToId(int fd, struct map_session_data *sd) { int hIndex = 0; if (HPMHooks.count.HP_clif_pUseSkillToId_pre > 0) {