diff --git a/conf/battle/skill.conf b/conf/battle/skill.conf index 827305cd6c..735b3d6243 100644 --- a/conf/battle/skill.conf +++ b/conf/battle/skill.conf @@ -69,9 +69,9 @@ skill_out_range_consume: no skillrange_by_distance: 14 // Should the equipped weapon's range override the skill's range defined in the skill_db for most weapon-based skills? (Note 3) -// NOTE: Skills affected by this option are those whose range in the skill_db are negative. Note that unless monster_ai&0x400 is -// set, the range of all skills is 9 for monsters. -skillrange_from_weapon: 30 +// NOTE: Skills affected by this option are those whose range in the skill_db are negative. By default always the skill range is used. +// Note that if you want all monster skills to have a range of 9 you need to set monster_ai&0x400. +skillrange_from_weapon: 0 // Should a check on the caster's status be performed in all skill attacks? // When set to yes, meteors, storm gust and any other ground skills will have diff --git a/db/re/skill_cast_db.txt b/db/re/skill_cast_db.txt index 7162b4056f..ee6e4ea402 100644 --- a/db/re/skill_cast_db.txt +++ b/db/re/skill_cast_db.txt @@ -158,7 +158,7 @@ //-- WZ_FIREPILLAR 80,1920:1728:1536:1344:1152:960:768:576:384:192,1000,0,30000,600:800:1000:1200:1400:1600:1800:2000:2200:2400,0,480:432:384:336:288:240:192:144:96:48 //-- WZ_SIGHTRASHER -81,0,2000,0,500,0,0,80 +81,320,2000,0,500,0,0,80 //-- WZ_METEOR 83,9600,2000:3000:3000:4000:4000:5000:5000:6000:6000:7000,0,500,5000,0,2400 //-- WZ_JUPITEL diff --git a/src/config/core.h b/src/config/core.h index e8c0fa0dfd..505d71e1fa 100644 --- a/src/config/core.h +++ b/src/config/core.h @@ -42,9 +42,10 @@ //#define CELL_NOSTACK /// Uncomment to enable circular area checks. -/// By default, all range checks in Aegis are of Square shapes, so a weapon range -/// - of 10 allows you to attack from anywhere within a 21x21 area. -/// Enabling this changes such checks to circular checks, which is more realistic, +/// By default, most server-sided range checks in Aegis are of square shapes, so a monster +/// with a range of 4 can attack anything within a 9x9 area. +/// Client-sided range checks are, however, are always circular. +/// Enabling this changes all checks to circular checks, which is more realistic, /// - but is not the official behaviour. //#define CIRCULAR_AREA diff --git a/src/map/battle.c b/src/map/battle.c index d1b1c48353..50501165d2 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -7425,7 +7425,7 @@ bool battle_check_range(struct block_list *src, struct block_list *bl, int range #ifndef CIRCULAR_AREA if( src->type == BL_PC ) { // Range for players' attacks and skills should always have a circular check. [Angezerus] int dx = src->x - bl->x, dy = src->y - bl->y; - if( !check_distance(dx, dy, range) ) + if( !check_distance_client(dx, dy, range) ) return false; } else #endif @@ -7467,7 +7467,7 @@ static const struct _battle_data { { "skill_add_range", &battle_config.skill_add_range, 0, 0, INT_MAX, }, { "skill_out_range_consume", &battle_config.skill_out_range_consume, 1, 0, 1, }, { "skillrange_by_distance", &battle_config.skillrange_by_distance, ~BL_PC, BL_NUL, BL_ALL, }, - { "skillrange_from_weapon", &battle_config.use_weapon_skill_range, ~BL_PC, BL_NUL, BL_ALL, }, + { "skillrange_from_weapon", &battle_config.use_weapon_skill_range, BL_NUL, BL_NUL, BL_ALL, }, { "player_damage_delay_rate", &battle_config.pc_damage_delay_rate, 100, 0, INT_MAX, }, { "defunit_not_enemy", &battle_config.defnotenemy, 0, 0, 1, }, { "gvg_traps_target_all", &battle_config.vs_traps_bctall, BL_PC, BL_NUL, BL_ALL, }, @@ -7744,7 +7744,6 @@ static const struct _battle_data { { "berserk_cancels_buffs", &battle_config.berserk_cancels_buffs, 0, 0, 1, }, { "debuff_on_logout", &battle_config.debuff_on_logout, 1|2, 0, 1|2, }, { "monster_ai", &battle_config.mob_ai, 0x000, 0x000, 0x77F, }, - { "monster_chase_refresh", &battle_config.mob_chase_refresh, 1, 0, 30, }, { "hom_setting", &battle_config.hom_setting, 0xFFFF, 0x0000, 0xFFFF, }, { "dynamic_mobs", &battle_config.dynamic_mobs, 1, 0, 1, }, { "mob_remove_damaged", &battle_config.mob_remove_damaged, 1, 0, 1, }, @@ -7901,6 +7900,7 @@ static const struct _battle_data { { "arrow_shower_knockback", &battle_config.arrow_shower_knockback, 1, 0, 1, }, { "devotion_rdamage_skill_only", &battle_config.devotion_rdamage_skill_only, 1, 0, 1, }, { "max_extended_aspd", &battle_config.max_extended_aspd, 193, 100, 199, }, + { "monster_chase_refresh", &battle_config.mob_chase_refresh, 1, 0, 30, } }; #ifndef STATS_OPT_OUT diff --git a/src/map/battle.h b/src/map/battle.h index 2329d40510..f1f357cf64 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -402,7 +402,6 @@ extern struct Battle_Config int berserk_cancels_buffs; // [Aru] int debuff_on_logout; // Removes a few "official" negative Scs on logout. [Skotlex] int mob_ai; //Configures various mob_ai settings to make them smarter or dumber(official). [Skotlex] - int mob_chase_refresh; //How often a monster should refresh its chase [Playtester] int hom_setting; //Configures various homunc settings which make them behave unlike normal characters.. [Skotlex] int dynamic_mobs; // Dynamic Mobs [Wizputer] - battle_athena flag implemented by [random] int mob_remove_damaged; // Dynamic Mobs - Remove mobs even if damaged [Wizputer] @@ -571,6 +570,7 @@ extern struct Battle_Config int arrow_shower_knockback; int devotion_rdamage_skill_only; int max_extended_aspd; + int mob_chase_refresh; //How often a monster should refresh its chase [Playtester] } battle_config; void do_init_battle(void); diff --git a/src/map/clif.c b/src/map/clif.c index ab9ccd7006..b8af507781 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -10306,7 +10306,8 @@ void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, sd->sc.data[SC__MANHOLE] )) return; - pc_stop_walking(sd, 1); + if(action_type != 0x00 && action_type != 0x07) + pc_stop_walking(sd, 1); pc_stop_attack(sd); if(target_id<0 && -target_id == sd->bl.id) // for disguises [Valaris] diff --git a/src/map/mob.c b/src/map/mob.c index 4e08a8d81c..0e2b177b5e 100644 --- a/src/map/mob.c +++ b/src/map/mob.c @@ -1649,26 +1649,24 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick) } //Out of range... - if (!(mode&MD_CANMOVE)) - { //Can't chase. Attempt an idle skill before unlocking. - if (md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER) - { //Only use skill if no more attack delay left - md->state.skillstate = MSS_IDLE; - if (!mobskill_use(md, tick, -1)) + if (!(mode&MD_CANMOVE) || (!can_move && DIFF_TICK(tick, md->ud.canmove_tick) > 0)) + { //Can't chase. Immobile and trapped mobs should unlock target and use an idle skill on next interval. + if ((md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER)) + { //Only unlock target to use idle skill if no more attack left + md->ud.walk_count = (md->ud.walk_count+1)%250; + if (!(md->ud.walk_count%IDLE_SKILL_INTERVAL)) mob_unlocktarget(md,tick); } return true; } - if (!can_move) - { //Stuck. Attempt an idle skill - if (md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER) - { //Only use skill if no more attack delay left - md->state.skillstate = MSS_IDLE; - if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL)) - mobskill_use(md, tick, -1); + //Before a monster starts to chase a target, it will check if it has a ranged "attack" skill to use on it. + if(md->ud.walktimer == INVALID_TIMER && (md->state.skillstate == MSS_BERSERK || md->state.skillstate == MSS_ANGRY)) + { + if (DIFF_TICK(md->ud.canmove_tick, tick) <= MIN_MOBTHINKTIME && DIFF_TICK(md->ud.canact_tick, tick) < -MIN_MOBTHINKTIME*IDLE_SKILL_INTERVAL) + { //Only use skill if able to walk on next tick and not used a skill the last second + mobskill_use(md, tick, -1); } - return true; } if (md->ud.walktimer != INVALID_TIMER && md->ud.target == tbl->id && @@ -1683,6 +1681,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick) return true; //Follow up if possible. + //Hint: Chase skills are handled in the walktobl routine if(!mob_can_reach(md, tbl, md->min_chase, MSS_RUSH) || !unit_walktobl(&md->bl, tbl, md->status.rhw.range, 2)) mob_unlocktarget(md,tick); diff --git a/src/map/path.c b/src/map/path.c index 805d29c329..7c9c89aef2 100644 --- a/src/map/path.c +++ b/src/map/path.c @@ -14,6 +14,7 @@ #include #include #include +#include #define SET_OPEN 0 #define SET_CLOSED 1 @@ -465,3 +466,34 @@ unsigned int distance(int dx, int dy) return (dxx-(x1), (bl)->y-(y1)) #define distance_xy(x0, y0, x1, y1) distance((x0)-(x1), (y0)-(y1)) +#define check_distance_client_bl(bl1, bl2, distance) check_distance_client((bl1)->x - (bl2)->x, (bl1)->y - (bl2)->y, distance) +#define check_distance_client_blxy(bl, x1, y1, distance) check_distance_client((bl)->x-(x1), (bl)->y-(y1), distance) +#define check_distance_client_xy(x0, y0, x1, y1, distance) check_distance_client((x0)-(x1), (y0)-(y1), distance) + +#define distance_client_bl(bl1, bl2) distance_client((bl1)->x - (bl2)->x, (bl1)->y - (bl2)->y) +#define distance_client_blxy(bl, x1, y1) distance_client((bl)->x-(x1), (bl)->y-(y1)) +#define distance_client_xy(x0, y0, x1, y1) distance_client((x0)-(x1), (y0)-(y1)) + // calculates destination cell for knockback int path_blownpos(int16 m,int16 x0,int16 y0,int16 dx,int16 dy,int count); @@ -44,5 +52,7 @@ bool path_search_long(struct shootpath_data *spd,int16 m,int16 x0,int16 y0,int16 // distance related functions int check_distance(int dx, int dy, int distance); unsigned int distance(int dx, int dy); +int check_distance_client(int dx, int dy, int distance); +unsigned int distance_client(int dx, int dy); #endif /* _PATH_H_ */ diff --git a/src/map/unit.c b/src/map/unit.c index 0c6d0cfb67..a3dd4fe1be 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -355,6 +355,61 @@ static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data if(tid == INVALID_TIMER) // A directly invoked timer is from battle_stop_walking, therefore the rest is irrelevant. return 0; + //If stepaction is set then we remembered a client request that should be executed on the next step + //Execute request now if target is in attack range + if (ud->stepaction && ud->target_to) { + //Delay stepactions by half a step (so they are executed at full step) + if(ud->walkpath.path[ud->walkpath.path_pos]&1) + i = status_get_speed(bl)*14/20; + else + i = status_get_speed(bl)/2; + if(ud->stepskill_id && skill_get_inf(ud->stepskill_id) & INF_GROUND_SKILL) { + //Ground skill, create imaginary target + struct block_list tbl; + struct map_data *md = &map[bl->m]; + tbl.type = BL_NUL; + tbl.m = bl->m; + //Convert target_to back to map coordinates + tbl.x = ud->target_to%md->xs; + tbl.y = ud->target_to/md->xs; + if (battle_check_range(bl, &tbl, ud->chaserange)) { + //Execute ground skill + ud->stepaction = false; + ud->target_to = 0; + unit_stop_walking(bl, 1); + //TODO: Delay skill use + unit_skilluse_pos(bl, tbl.x, tbl.y, ud->stepskill_id, ud->stepskill_lv); + return 0; + } + } else { + //If a player has target_to set and target is in range, attempt attack + struct block_list *tbl = map_id2bl(ud->target_to); + if (!tbl || !status_check_visibility(bl, tbl)) { + ud->target_to = 0; + } + if (battle_check_range(bl, tbl, ud->chaserange)) { + // Close enough to attempt an attack + if(ud->stepskill_id == 0) { + //Execute normal attack + ud->stepaction = false; + ud->target = ud->target_to; + ud->target_to = 0; + unit_stop_walking(bl, 1); + ud->attacktimer=add_timer(tick+i,unit_attack_timer,bl->id,0); + return 0; + } else { + //Execute non-ground skill + ud->stepaction = false; + ud->target_to = 0; + unit_stop_walking(bl, 1); + //TODO: Delay skill use + unit_skilluse_id(bl, tbl->id, ud->stepskill_id, ud->stepskill_lv); + return 0; + } + } + } + } + if(ud->state.change_walk_target) return unit_walktoxy_sub(bl); @@ -375,7 +430,7 @@ static int unit_walktoxy_timer(int tid, unsigned int tick, int id, intptr_t data // Keep trying to run. if ( !(unit_run(bl) || unit_wugdash(bl,sd)) ) ud->state.running = 0; - } else if (ud->target_to) { + } else if (!ud->stepaction && ud->target_to) { // Update target trajectory. struct block_list *tbl = map_id2bl(ud->target_to); if (!tbl || !status_check_visibility(bl, tbl)) { // Cancel chase. @@ -1300,10 +1355,6 @@ int unit_set_walkdelay(struct block_list *bl, unsigned int tick, int delay, int if (delay <= 0 || !ud) return 0; - // /MvP mobs have no walk delay - if( bl->type == BL_MOB && (((TBL_MOB*)bl)->status.mode&MD_BOSS) ) - return 0; - if (type) { if (DIFF_TICK(ud->canmove_tick, tick+delay) > 0) return 0; @@ -1311,6 +1362,9 @@ int unit_set_walkdelay(struct block_list *bl, unsigned int tick, int delay, int // Don't set walk delays when already trapped. if (!unit_can_move(bl)) return 0; + //Immune to being stopped for double the flinch time + if (DIFF_TICK(ud->canmove_tick, tick-delay) > 0) + return 0; } ud->canmove_tick = tick + delay; @@ -1323,7 +1377,7 @@ int unit_set_walkdelay(struct block_list *bl, unsigned int tick, int delay, int if(ud->state.running) add_timer(ud->canmove_tick, unit_resume_running, bl->id, (intptr_t)ud); else { - unit_stop_walking(bl,2|4); + unit_stop_walking(bl,4); if(ud->target) add_timer(ud->canmove_tick+1, unit_walktobl_sub, bl->id, ud->target); @@ -1554,6 +1608,19 @@ int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill_id, ui else range = skill_get_range2(src, skill_id, skill_lv); // Skill cast distance from database + // Remember the skill request from the client while walking to the next cell + if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle_check_range(src, target, range-1)) { + ud->stepaction = true; + ud->target_to = target_id; + ud->chaserange = range; + ud->stepskill_id = skill_id; + ud->stepskill_lv = skill_lv; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } else { + // To make sure a failed stepaction is not remembered any longer + ud->stepaction = false; + } + // Check range when not using skill on yourself or is a combo-skill during attack // (these are supposed to always have the same range as your attack) if( src->id != target_id && (!combo || ud->attacktimer == INVALID_TIMER) ) { @@ -1858,10 +1925,25 @@ int unit_skilluse_pos2( struct block_list *src, short skill_x, short skill_y, ui else range = skill_get_range2(src, skill_id, skill_lv); // Skill cast distance from database + // Remember the skill request from the client while walking to the next cell + if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle_check_range(src, &bl, range-1)) { + struct map_data *md = &map[src->m]; + // Convert coordinates to target_to so we can use it as target later + ud->stepaction = true; + ud->target_to = (skill_x + skill_y*md->xs); + ud->chaserange = range; + ud->stepskill_id = skill_id; + ud->stepskill_lv = skill_lv; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } else { + // To make sure a failed stepaction is not remembered any longer + ud->stepaction = false; + } + if( skill_get_state(ud->skill_id) == ST_MOVE_ENABLE ) { if( !unit_can_reach_bl(src, &bl, range + 1, 1, NULL, NULL) ) return 0; // Walk-path check failed. - }else if( !battle_check_range(src, &bl, range + 1) ) + }else if( !battle_check_range(src, &bl, range) ) return 0; // Arrow-path check failed. unit_stop_attack(src); @@ -2013,6 +2095,7 @@ int unit_attack(struct block_list *src,int target_id,int continuous) { struct block_list *target; struct unit_data *ud; + int range; nullpo_ret(ud = unit_bl2ud(src)); @@ -2049,17 +2132,28 @@ int unit_attack(struct block_list *src,int target_id,int continuous) ud->state.attack_continue = continuous; unit_set_target(ud, target_id); + range = status_get_range(src); + if (continuous) // If you're to attack continously, set to auto-chase character - ud->chaserange = status_get_range(src); + ud->chaserange = range; // Just change target/type. [Skotlex] if(ud->attacktimer != INVALID_TIMER) return 0; - // Set Mob's ANGRY/BERSERK states. - if(src->type == BL_MOB) - ((TBL_MOB*)src)->state.skillstate = ((TBL_MOB*)src)->state.aggressive?MSS_ANGRY:MSS_BERSERK; - + // Remember the attack request from the client while walking to the next cell + if(src->type == BL_PC && ud->walktimer != INVALID_TIMER && !battle_check_range(src, target, range-1)) { + ud->stepaction = true; + ud->target_to = ud->target; + ud->chaserange = range; + ud->stepskill_id = 0; + ud->stepskill_lv = 0; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } else { + // To make sure a failed stepaction is not remembered any longer + ud->stepaction = false; + } + if(DIFF_TICK(ud->attackabletime, gettick()) > 0) // Do attack next time it is possible. [Skotlex] ud->attacktimer=add_timer(ud->attackabletime,unit_attack_timer,src->id,0); else // Attack NOW. @@ -2323,17 +2417,18 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, unsigned int t } sstatus = status_get_status_data(src); - range = sstatus->rhw.range + 1; + range = sstatus->rhw.range; if( unit_is_walking(target) ) range++; // Extra range when chasing - if( !check_distance_bl(src,target,range) ) { // Chase if required. - if(sd) - clif_movetoattack(sd,target); - else if(ud->state.attack_continue) - unit_walktobl(src,target,ud->chaserange,ud->state.walk_easy|2); - + if(sd && !check_distance_client_bl(src,target,range)) { + // Player tries to attack but target is too far, notify client + clif_movetoattack(sd,target); + return 1; + } else if(md && !check_distance_bl(src,target,range)) { + // Monster: Chase if required + unit_walktobl(src,target,ud->chaserange,ud->state.walk_easy|2); return 1; } @@ -2362,8 +2457,14 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, unsigned int t unit_stop_walking(src,1); if(md) { - if (mobskill_use(md,tick,-1)) - 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)) + return 1; + } else { + // Set mob's ANGRY/BERSERK states. + md->state.skillstate = md->state.aggressive?MSS_ANGRY:MSS_BERSERK; + } if (sstatus->mode&MD_ASSIST && DIFF_TICK(md->last_linktime, tick) < MIN_MOBLINKTIME) { // Link monsters nearby [Skotlex] diff --git a/src/map/unit.h b/src/map/unit.h index 1655ab00b8..01b30a138b 100644 --- a/src/map/unit.h +++ b/src/map/unit.h @@ -33,7 +33,9 @@ struct unit_data { int target_to; int attacktimer; int walktimer; - int chaserange; + int chaserange; + bool stepaction; //Action should be executed on step [Playtester] + uint16 stepskill_id,stepskill_lv; //Remembers skill that should be casted on step [Playtester] unsigned int attackabletime; unsigned int canact_tick; unsigned int canmove_tick;