aMotion delay is now set at cast begin (fixes #944)

* When casting a spell with cast time shorter than aMotion, the delay will now be set to aMotion or min_skill_delay_limit
* aMotion and min_skill_delay_limit are no longer applied at cast end nor by auto spells
* Aftercast delay can only increase the delay and never decrease it
* When there is no aftercast delay, the client will no longer show a non-existent 10 second delay
* Removed "double cast" code of Jupitel and Waterball, it's no longer needed as it fits into the official delay system
* Added a constant SECURITY_CASTTIME, which is added at cast begin and subtracted at cast end to prevent hackers to send in skill packets before the timer triggers cast end
* Set skill_amotion_leniency to 0 by default as it applies delay at cast end and would consequently block the official system from working; you can set it back to 90 or even 140 for increased security
This commit is contained in:
Playtester 2016-02-21 17:06:39 +01:00
parent f264885411
commit a63b031958
6 changed files with 34 additions and 46 deletions

View File

@ -20,7 +20,7 @@ delay_rate: 100
delay_dependon_dex: no
delay_dependon_agi: no
// Minimum allowed delay for ANY skills after casting (in milliseconds) (Note 1)
// Minimum allowed delay for ANY skills after castbegin (in milliseconds) (Note 1)
// Note: Setting this to anything above 0 can stop speedhacks.
min_skill_delay_limit: 100
@ -30,8 +30,8 @@ min_skill_delay_limit: 100
// appear to "teleport" afterwards.
default_walk_delay: 300
//Completely disable skill delay of the following types (Note 3)
//NOTE: By default mobs don't have the skill delay as specified in the skill
// Completely disable skill delay of the following types (Note 3)
// NOTE: By default mobs don't have the skill delay as specified in the skill
// database, but follow their own 'reuse' skill delay which is specified on
// the mob skill db. When set, the delay for all skills become
// min_skill_delay_limit.
@ -43,13 +43,12 @@ castrate_dex_scale: 150
// How much (dex*2+int) does variable cast turns zero?
vcast_stat_scale: 530
// What level of leniency should the skill system give for skills when
// accounting attack motion (ASPD) for casting skills (Note 2, between 0 and 300)
//
// NOTE: Setting this to 100% may cause some issues with valid skills not being cast.
// The time difference between client and server varies so allowing 90% leniency
// should be enough to forgive very small margins of error.
skill_amotion_leniency: 90
// On official servers, amotion delay is applied at castbegin. There is no amotion delay applied
// at castend. Set this to anything above 0 to also apply amotion delay at castend. (Note 2)
// NOTE: Setting this will break chaining of skills with cast time but no aftercast delay.
// The client-sided delays are different from skill to skill and usually range from 140 to 180.
// If you want to be secure, a value between 90 and 140 is recommended.
skill_amotion_leniency: 0
// Will normal attacks be able to ignore the delay after skills? (Note 1)
skill_delay_attack_enable: yes

View File

@ -6991,7 +6991,7 @@ enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* t
}
if (rnd()%100 < triple_rate) {
//Need to apply canact_tick here because it doesn't go through skill_castend_id
sd->ud.canact_tick = tick + skill_delayfix(src, MO_TRIPLEATTACK, skillv);
sd->ud.canact_tick = max(tick + skill_delayfix(src, MO_TRIPLEATTACK, skillv), sd->ud.canact_tick);
if( skill_attack(BF_WEAPON,src,src,target,MO_TRIPLEATTACK,skillv,tick,0) )
return ATK_DEF;
return ATK_MISS;
@ -7189,7 +7189,7 @@ enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* t
int autospell_tick = skill_delayfix(src, skill_id, skill_lv);
if (DIFF_TICK(ud->canact_tick, tick + autospell_tick) < 0) {
ud->canact_tick = tick + autospell_tick;
ud->canact_tick = max(tick + autospell_tick, ud->canact_tick);
if (battle_config.display_status_timers && sd)
clif_status_change(src, SI_ACTIONDELAY, 1, autospell_tick, 0, 0, 0);
}
@ -7253,7 +7253,7 @@ enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* t
}
sd->state.autocast = 0;
sd->ud.canact_tick = tick + skill_delayfix(src, r_skill, r_lv);
sd->ud.canact_tick = max(tick + skill_delayfix(src, r_skill, r_lv), sd->ud.canact_tick);
clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, r_skill, r_lv), 0, 0, 1);
}
}
@ -8098,7 +8098,7 @@ static const struct _battle_data {
{ "max_trans_parameter", &battle_config.max_trans_parameter, 99, 10, SHRT_MAX, },
{ "max_third_trans_parameter", &battle_config.max_third_trans_parameter, 135, 10, SHRT_MAX, },
{ "max_extended_parameter", &battle_config.max_extended_parameter, 125, 10, SHRT_MAX, },
{ "skill_amotion_leniency", &battle_config.skill_amotion_leniency, 90, 0, 300 },
{ "skill_amotion_leniency", &battle_config.skill_amotion_leniency, 0, 0, 300 },
{ "mvp_tomb_enabled", &battle_config.mvp_tomb_enabled, 1, 0, 1 },
{ "feature.atcommand_suggestions", &battle_config.atcommand_suggestions_enabled, 0, 0, 1 },
{ "min_npc_vendchat_distance", &battle_config.min_npc_vendchat_distance, 3, 0, 100 },

View File

@ -5868,6 +5868,9 @@ void clif_status_change(struct block_list *bl,int type,int flag,int tick,int val
if (type == SI_BLANK) //It shows nothing on the client...
return;
if (type == SI_ACTIONDELAY && tick == 0)
return;
nullpo_retv(bl);
sd = BL_CAST(BL_PC, bl);

View File

@ -1920,7 +1920,7 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1
if (ud) {
rate = skill_delayfix(src, skill, skill_lv);
if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){
ud->canact_tick = tick+rate;
ud->canact_tick = max(tick + rate, ud->canact_tick);
if ( battle_config.display_status_timers )
clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0);
}
@ -2014,7 +2014,7 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1
if (ud) {
rate = skill_delayfix(src, skill, autospl_skill_lv);
if (DIFF_TICK(ud->canact_tick, tick + rate) < 0){
ud->canact_tick = tick+rate;
ud->canact_tick = max(tick + rate, ud->canact_tick);
if ( battle_config.display_status_timers && sd )
clif_status_change(src, SI_ACTIONDELAY, 1, rate, 0, 0, 0);
}
@ -2348,7 +2348,7 @@ int skill_counter_additional_effect (struct block_list* src, struct block_list *
if (ud) {
autospl_rate = skill_delayfix(bl, autospl_skill_id, autospl_skill_lv);
if (DIFF_TICK(ud->canact_tick, tick + autospl_rate) < 0){
ud->canact_tick = tick+autospl_rate;
ud->canact_tick = max(tick + autospl_rate, ud->canact_tick);
if ( battle_config.display_status_timers && dstsd )
clif_status_change(bl, SI_ACTIONDELAY, 1, autospl_rate, 0, 0, 0);
}
@ -3930,7 +3930,7 @@ static int skill_timerskill(int tid, unsigned int tick, int id, intptr_t data)
// Official behaviour is to hit as long as there is a line of sight, regardless of distance
if (skl->type > 0 && !status_isdead(target) && path_search_long(NULL,src->m,src->x,src->y,target->x,target->y,CELL_CHKWALL)) {
// Apply canact delay here to prevent hacks (unlimited casting)
ud->canact_tick = tick + skill_delayfix(src, skl->skill_id, skl->skill_lv);
ud->canact_tick = max(tick + status_get_amotion(src), ud->canact_tick);
skill_attack(BF_MAGIC, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag);
}
if (unit && !status_isdead(target) && !status_isdead(src)) {
@ -5231,7 +5231,7 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint
break;
}
sd->ud.canact_tick = tick + skill_delayfix(src, pres_skill_id, pres_skill_lv);
sd->ud.canact_tick = max(tick + skill_delayfix(src, pres_skill_id, pres_skill_lv), sd->ud.canact_tick);
clif_status_change(src, SI_ACTIONDELAY, 1, skill_delayfix(src, pres_skill_id, pres_skill_lv), 0, 0, 0);
cooldown = pc_get_skillcooldown(sd,pres_skill_id, pres_skill_lv);
@ -5721,22 +5721,7 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint
if( sd && !(flag&1) )
{// ensure that the skill last-cast tick is recorded
tick = gettick();
switch (skill_id) {
//These skill don't call skill_attack right away and allow to cast a second spell before the first skill deals damage
case WZ_JUPITEL:
case WZ_WATERBALL:
//Only allow the double-cast trick every 2000ms to prevent hacks
if (DIFF_TICK(tick, sd->canskill_tick) > 2000) {
sd->ud.canact_tick = tick;
sd->canskill_tick = tick-2000+TIMERSKILL_INTERVAL;
break;
}
//Fall through
default:
sd->canskill_tick = tick;
break;
}
sd->canskill_tick = gettick();
if( sd->state.arrow_atk )
{// consume arrow on last invocation to this skill.
@ -10999,8 +10984,8 @@ int skill_castend_id(int tid, unsigned int tick, int id, intptr_t data)
if (ud->walktimer != INVALID_TIMER && ud->skill_id != TK_RUN && ud->skill_id != RA_WUGDASH)
unit_stop_walking(src,1);
if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) )
ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv); //Tests show wings don't overwrite the delay but skill scrolls do. [Inkfish]
if (!sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id, ud->skill_lv))
ud->canact_tick = max(tick + skill_delayfix(src, ud->skill_id, ud->skill_lv), ud->canact_tick - SECURITY_CASTTIME);
if (sd) { //Cooldown application
int cooldown = pc_get_skillcooldown(sd,ud->skill_id, ud->skill_lv); // Increases/Decreases cooldown of a skill by item/card bonuses.
if(cooldown) skill_blockpc_start(sd, ud->skill_id, cooldown);
@ -11228,8 +11213,8 @@ int skill_castend_pos(int tid, unsigned int tick, int id, intptr_t data)
if (ud->walktimer != INVALID_TIMER)
unit_stop_walking(src,1);
if( !sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id,ud->skill_lv) )
ud->canact_tick = tick + skill_delayfix(src, ud->skill_id, ud->skill_lv);
if (!sd || sd->skillitem != ud->skill_id || skill_get_delay(ud->skill_id, ud->skill_lv))
ud->canact_tick = max(tick + skill_delayfix(src, ud->skill_id, ud->skill_lv), ud->canact_tick - SECURITY_CASTTIME);
if (sd) { //Cooldown application
int cooldown = pc_get_skillcooldown(sd,ud->skill_id, ud->skill_lv);
if(cooldown) skill_blockpc_start(sd, ud->skill_id, cooldown);
@ -16233,9 +16218,6 @@ int skill_delayfix(struct block_list *bl, uint16 skill_id, uint16 skill_lv)
if (battle_config.delay_rate != 100)
time = time * battle_config.delay_rate / 100;
//min delay
time = max(time, status_get_amotion(bl)); // Delay can never be below amotion [Playtester]
time = max(time, battle_config.min_skill_delay_limit);
//ShowInfo("Delay delayfix = %d\n",time);
return time;

View File

@ -104,6 +104,10 @@ enum e_skill_inf3 {
/// If you change this, make sure it's an odd value (for icewall block behavior).
#define WALK_SKILL_INTERVAL 5
/// Time that's added to canact delay on castbegin and substracted on castend
/// This is to prevent hackers from sending a skill packet after cast but before a timer triggers castend
#define SECURITY_CASTTIME 100
/// Flags passed to skill_attack/skill_area_sub
enum e_skill_display {
SD_LEVEL = 0x1000, // skill_attack will send -1 instead of skill level (affects display of some skills)

View File

@ -1858,8 +1858,8 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui
if( casttime <= 0 )
ud->state.skillcastcancel = 0;
if( !sd || sd->skillitem != skill_id || skill_get_cast(skill_id,skill_lv) )
ud->canact_tick = tick + casttime + 100;
if (!sd || sd->skillitem != skill_id || skill_get_cast(skill_id, skill_lv))
ud->canact_tick = tick + max(casttime, max(status_get_amotion(src), battle_config.min_skill_delay_limit)) + SECURITY_CASTTIME;
if( sd ) {
switch( skill_id ) {
@ -2037,8 +2037,8 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui
casttime = 0;
ud->state.skillcastcancel = castcancel&&casttime>0?1:0;
if( !sd || sd->skillitem != skill_id || skill_get_cast(skill_id,skill_lv) )
ud->canact_tick = tick + casttime + 100;
if (!sd || sd->skillitem != skill_id || skill_get_cast(skill_id, skill_lv))
ud->canact_tick = tick + max(casttime, max(status_get_amotion(src), battle_config.min_skill_delay_limit)) + SECURITY_CASTTIME;
// if( sd )
// {