diff options
-rw-r--r-- | src/config/core.h | 9 | ||||
-rw-r--r-- | src/map/battle.c | 10 | ||||
-rw-r--r-- | src/map/battle.h | 2 | ||||
-rw-r--r-- | src/map/clif.c | 3 | ||||
-rw-r--r-- | src/map/mob.c | 78 | ||||
-rw-r--r-- | src/map/mob.h | 2 | ||||
-rw-r--r-- | src/map/path.c | 41 | ||||
-rw-r--r-- | src/map/path.h | 12 | ||||
-rw-r--r-- | src/map/pet.c | 2 | ||||
-rw-r--r-- | src/map/unit.c | 252 | ||||
-rw-r--r-- | src/map/unit.h | 7 |
11 files changed, 324 insertions, 94 deletions
diff --git a/src/config/core.h b/src/config/core.h index ac59563b5..f80cdd3f7 100644 --- a/src/config/core.h +++ b/src/config/core.h @@ -40,10 +40,11 @@ //#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, -/// - but is not the official behavior. +/// 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 //This is the distance at which @autoloot works, diff --git a/src/map/battle.c b/src/map/battle.c index f95a4e568..3b50d3507 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -6415,6 +6415,12 @@ bool battle_check_range(struct block_list *src, struct block_list *bl, int range if( src->m != bl->m ) return false; +#ifndef CIRCULAR_AREA + if( src->type == BL_PC ) { // Range for players' attacks and skills should always have a circular check. [Angezerus] + if ( !check_distance_client_bl(src, bl, range) ) + return false; + } else +#endif if( !check_distance_bl(src, bl, range) ) return false; @@ -6449,7 +6455,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, }, @@ -6711,7 +6717,6 @@ static const struct battle_data { { "copyskill_restrict", &battle_config.copyskill_restrict, 2, 0, 2, }, { "berserk_cancels_buffs", &battle_config.berserk_cancels_buffs, 0, 0, 1, }, { "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, }, @@ -6825,6 +6830,7 @@ static const struct battle_data { { "guild_castle_expulsion", &battle_config.guild_castle_expulsion, 0, 0, 1, }, { "song_timer_reset", &battle_config.song_timer_reset, 0, 0, 1, }, { "snap_dodge", &battle_config.snap_dodge, 0, 0, 1, }, + { "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 eeb9897fa..966fa073e 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -353,7 +353,6 @@ struct Battle_Config { int copyskill_restrict; // [Aru] int berserk_cancels_buffs; // [Aru] 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.conf flag implemented by [random] int mob_remove_damaged; // Dynamic Mobs - Remove mobs even if damaged [Wizputer] @@ -452,6 +451,7 @@ struct Battle_Config { int mob_size_influence; // Enable modifications on earned experience, drop rates and monster status depending on monster size. [mkbu95] int bowling_bash_area; + int mob_chase_refresh; //How often a monster should refresh its chase [Playtester] /** Hercules **/ int skill_trap_type; diff --git a/src/map/clif.c b/src/map/clif.c index b9b5a8419..a5928bc89 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -10121,7 +10121,8 @@ void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, ) 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 cdd842075..122cc91aa 100644 --- a/src/map/mob.c +++ b/src/map/mob.c @@ -52,7 +52,9 @@ struct mob_interface mob_s; #define IDLE_SKILL_INTERVAL 10 //Active idle skills should be triggered every 1 second (1000/MIN_MOBTHINKTIME) -#define MOB_LAZYSKILLPERC 0 // Probability for mobs far from players from doing their IDLE skill. (rate of 1000 minute) +// Probability for mobs far from players from doing their IDLE skill. (rate of 1000 minute) +// in Aegis, this is 100% for mobs that have been activated by players and none otherwise. +#define MOB_LAZYSKILLPERC(md) (md->state.spotted?1000:0) // Move probability for mobs away from players (rate of 1000 minute) // in Aegis, this is 100% for mobs that have been activated by players and none otherwise. #define MOB_LAZYMOVEPERC(md) ((md)->state.spotted?1000:0) @@ -946,7 +948,7 @@ int mob_spawn (struct mob_data *md) md->state.aggressive = md->status.mode&MD_ANGRY?1:0; md->state.skillstate = MSS_IDLE; - md->next_walktime = tick+rnd()%5000+1000; + md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME; md->last_linktime = tick; md->dmgtick = tick - 5000; md->last_pcneartime = 0; @@ -1294,13 +1296,16 @@ int mob_unlocktarget(struct mob_data *md, int64 tick) { DIFF_TICK(md->next_walktime, tick) <= 0 && !mob->randomwalk(md,tick)) //Delay next random walk when this one failed. - md->next_walktime=tick+rnd()%3000; + md->next_walktime = tick+rnd()%1000; break; default: mob_stop_attack(md); mob_stop_walking(md,1); //Stop chasing. md->state.skillstate = MSS_IDLE; - md->next_walktime=tick+rnd()%3000+3000; + if(battle_config.mob_ai&0x8) //Walk instantly after dropping target + md->next_walktime = tick+rnd()%1000; + else + md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME; break; } if (md->target_id) { @@ -1327,6 +1332,7 @@ int mob_randomwalk(struct mob_data *md, int64 tick) { d =12-md->move_fail_count; if(d<5) d=5; + if(d>7) d=7; for(i=0;i<retrycount;i++){ // Search of a movable place int r=rnd(); x=r%(d*2+1)-d; @@ -1334,7 +1340,7 @@ int mob_randomwalk(struct mob_data *md, int64 tick) { x+=md->bl.x; y+=md->bl.y; - if((map->getcell(md->bl.m,x,y,CELL_CHKPASS)) && unit->walktoxy(&md->bl,x,y,1)){ + if(((x != md->bl.x) || (y != md->bl.y)) && map->getcell(md->bl.m,x,y,CELL_CHKPASS) && unit->walktoxy(&md->bl,x,y,0)){ break; } } @@ -1357,7 +1363,7 @@ int mob_randomwalk(struct mob_data *md, int64 tick) { } md->state.skillstate=MSS_WALK; md->move_fail_count=0; - md->next_walktime = tick+rnd()%3000+3000+c; + md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME+c; return 1; } @@ -1435,7 +1441,7 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 tick) { return true; //Chasing this target. if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh) return true; //Walk at least "mob_chase_refresh" cells before dropping the target - mob_unlocktarget(md, tick-(battle_config.mob_ai&0x8?3000:0)); //Immediately do random walk. + mob_unlocktarget(md, tick); //Unlock target tbl = NULL; } } @@ -1627,26 +1633,24 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 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 (!mob->skill_use(md, tick, -1)) - mob->unlocktarget(md,tick); + 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)) + //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 mob->skill_use(md, tick, -1); } - return true; } if (md->ud.walktimer != INVALID_TIMER && md->ud.target == tbl->id && @@ -1661,6 +1665,7 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 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); @@ -1741,20 +1746,17 @@ int mob_ai_sub_lazy(struct mob_data *md, va_list args) { } if( DIFF_TICK(md->next_walktime,tick) < 0 && (status_get_mode(&md->bl)&MD_CANMOVE) && unit->can_move(&md->bl) ) { - if( map->list[md->bl.m].users > 0 ) - { - if( rnd()%1000 < MOB_LAZYMOVEPERC(md) ) - mob->randomwalk(md, tick); - else - if( rnd()%1000 < MOB_LAZYSKILLPERC ) //Chance to do a mob's idle skill. - mob->skill_use(md, tick, -1); - } - else - { - if( rnd()%1000 < MOB_LAZYMOVEPERC(md) ) - mob->randomwalk(md, tick); - } + if( rnd()%1000 < MOB_LAZYMOVEPERC(md) ) + mob_randomwalk(md, tick); + } + else if( md->ud.walktimer == INVALID_TIMER ) + { + //Because it is not unset when the mob finishes walking. + md->state.skillstate = MSS_IDLE; + if( rnd()%1000 < MOB_LAZYSKILLPERC(md) ) //Chance to do a mob's idle skill. + mob->skill_use(md, tick, -1); } + return 0; } @@ -2647,7 +2649,7 @@ void mob_revive(struct mob_data *md, unsigned int hp) int64 tick = timer->gettick(); md->state.skillstate = MSS_IDLE; md->last_thinktime = tick; - md->next_walktime = tick+rnd()%50+5000; + md->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME; md->last_linktime = tick; md->last_pcneartime = 0; memset(md->dmglog, 0, sizeof(md->dmglog)); // Reset the damage done on the rebirthed monster, otherwise will grant full exp + damage done. [Valaris] @@ -3717,6 +3719,10 @@ bool mob_parse_dbrow(char** str) { if (mstatus->dex < 1) mstatus->dex = 1; if (mstatus->luk < 1) mstatus->luk = 1; + //Tests showed that chase range is effectively 2 cells larger than expected [Playtester] + if (db->range3 > 0) + db->range3 += 2; + db->range2 = atoi(str[20]); db->range3 = atoi(str[21]); if (battle_config.view_range_rate != 100) { diff --git a/src/map/mob.h b/src/map/mob.h index 728f3d81c..4df96d7f8 100644 --- a/src/map/mob.h +++ b/src/map/mob.h @@ -27,6 +27,8 @@ #define MIN_MOBTHINKTIME 100 //Min time before mobs do a check to call nearby friends for help (or for slaves to support their master) #define MIN_MOBLINKTIME 1000 +//Min time between random walks +#define MIN_RANDOMWALKTIME 4000 //Distance that slaves should keep from their master. #define MOB_SLAVEDISTANCE 2 diff --git a/src/map/path.c b/src/map/path.c index 086b0af9a..681dfcb06 100644 --- a/src/map/path.c +++ b/src/map/path.c @@ -10,6 +10,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <math.h> #include "map.h" #include "../common/cbasetypes.h" @@ -410,7 +411,7 @@ bool path_search(struct walkpath_data *wpd, int16 m, int16 x0, int16 y0, int16 x //Distance functions, taken from http://www.flipcode.com/articles/article_fastdistance.shtml -int check_distance(int dx, int dy, int distance) +bool check_distance(int dx, int dy, int distance) { #ifdef CIRCULAR_AREA //In this case, we just do a square comparison. Add 1 tile grace for diagonal range checks. @@ -450,6 +451,42 @@ unsigned int distance(int dx, int dy) return (dx<dy?dy:dx); #endif } + +/** + * The client uses a circular distance instead of the square one. The circular distance + * is only used by units sending their attack commands via the client (not monsters). + * @param dx: Horizontal distance + * @param dy: Vertical distance + * @param distance: Distance to check against + * @return Within distance(1); Not within distance(0); + */ +bool check_distance_client(int dx, int dy, int distance) +{ + if(distance < 0) distance = 0; + + return (path->distance_client(dx,dy) <= distance); +} + +/** + * The client uses a circular distance instead of the square one. The circular distance + * is only used by units sending their attack commands via the client (not monsters). + * @param dx: Horizontal distance + * @param dy: Vertical distance + * @return Circular distance + */ +int distance_client(int dx, int dy) +{ + double temp_dist = sqrt((double)(dx*dx + dy*dy)); + + //Bonus factor used by client + //This affects even horizontal/vertical lines so they are one cell longer than expected + temp_dist -= 0.0625; + + if(temp_dist < 0) temp_dist = 0; + + return ((int)temp_dist); +} + void path_defaults(void) { path = &path_s; @@ -458,4 +495,6 @@ void path_defaults(void) { path->search = path_search; path->check_distance = check_distance; path->distance = distance; + path->check_distance_client = check_distance_client; + path->distance_client = distance_client; } diff --git a/src/map/path.h b/src/map/path.h index 8d02e6558..4b71f2189 100644 --- a/src/map/path.h +++ b/src/map/path.h @@ -32,6 +32,14 @@ struct shootpath_data { #define distance_blxy(bl, x1, y1) (path->distance((bl)->x - (x1), (bl)->y - (y1))) #define distance_xy(x0, y0, x1, y1) (path->distance((x0) - (x1), (y0) - (y1))) +#define check_distance_client_bl(bl1, bl2, distance) (path->check_distance_client((bl1)->x - (bl2)->x, (bl1)->y - (bl2)->y, distance)) +#define check_distance_client_blxy(bl, x1, y1, distance) (path->check_distance_client((bl)->x-(x1), (bl)->y-(y1), distance)) +#define check_distance_client_xy(x0, y0, x1, y1, distance) (path->check_distance_client((x0)-(x1), (y0)-(y1), distance)) + +#define distance_client_bl(bl1, bl2) (path->distance_client((bl1)->x - (bl2)->x, (bl1)->y - (bl2)->y)) +#define distance_client_blxy(bl, x1, y1) (path->distance_client((bl)->x-(x1), (bl)->y-(y1))) +#define distance_client_xy(x0, y0, x1, y1) (path->distance_client((x0)-(x1), (y0)-(y1))) + struct path_interface { // calculates destination cell for knockback int (*blownpos) (int16 m, int16 x0, int16 y0, int16 dx, int16 dy, int count); @@ -39,8 +47,10 @@ struct path_interface { bool (*search) (struct walkpath_data *wpd, int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int flag, cell_chk cell); // tries to find a shootable path bool (*search_long) (struct shootpath_data *spd, int16 m, int16 x0, int16 y0, int16 x1, int16 y1, cell_chk cell); - int (*check_distance) (int dx, int dy, int distance); + bool (*check_distance) (int dx, int dy, int distance); unsigned int (*distance) (int dx, int dy); + bool (*check_distance_client) (int dx, int dy, int distance); + int (*distance_client) (int dx, int dy); }; struct path_interface *path; diff --git a/src/map/pet.c b/src/map/pet.c index 9275a6de5..d90727b97 100644 --- a/src/map/pet.c +++ b/src/map/pet.c @@ -821,7 +821,7 @@ int pet_randomwalk(struct pet_data *pd, int64 tick) { else c+=pd->status.speed; } - pd->next_walktime = tick+rnd()%3000+3000+c; + pd->next_walktime = tick+rnd()%1000+MIN_RANDOMWALKTIME+c; return 1; } diff --git a/src/map/unit.c b/src/map/unit.c index 12d125c49..f823a3fed 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -137,6 +137,75 @@ int unit_walktoxy_sub(struct block_list *bl) return 1; } +/** + * Triggered on full step if stepaction is true and executes remembered action. + * @param tid: Timer ID + * @param tick: Unused + * @param id: ID of bl to do the action + * @param data: Not used + * @return 1: Success 0: Fail (No valid bl) + */ +int unit_step_timer(int tid, int64 tick, int id, intptr_t data) +{ + struct block_list *bl; + struct unit_data *ud; + int target_id; + + bl = map->id2bl(id); + + if (!bl || bl->prev == NULL) + return 0; + + ud = unit_bl2ud(bl); + + if(!ud) + return 0; + + if(ud->steptimer != tid) { + ShowError("unit_step_timer mismatch %d != %d\n",ud->steptimer,tid); + return 0; + } + + ud->steptimer = INVALID_TIMER; + + if(!ud->stepaction) + return 0; + + //Set to false here because if an error occurs, it should not be executed again + ud->stepaction = false; + + if(!ud->target_to) + return 0; + + //Flush target_to as it might contain map coordinates which should not be used by other functions + target_id = ud->target_to; + ud->target_to = 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->stepskill_id && skill->get_inf(ud->stepskill_id) & INF_GROUND_SKILL) { + //Execute ground skill + struct map_data *md = &map->list[bl->m]; + unit->skilluse_pos(bl, target_id%md->xs, target_id/md->xs, ud->stepskill_id, ud->stepskill_lv); + } else { + //If a player has target_id set and target is in range, attempt attack + struct block_list *tbl = map->id2bl(target_id); + if (!tbl || !status->check_visibility(bl, tbl)) { + return 0; + } + if(ud->stepskill_id == 0) { + //Execute normal attack + unit->attack(bl, tbl->id, ud->state.attack_continue); + } else { + //Execute non-ground skill + unit->skilluse_id(bl, tbl->id, ud->stepskill_id, ud->stepskill_lv); + } + } + + return 1; +} + + int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { int i; int x,y,dx,dy; @@ -240,9 +309,11 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { //But avoid triggering on stop-walk calls. if(tid != INVALID_TIMER && !(ud->walk_count%WALK_SKILL_INTERVAL) && + map->list[bl->m].users > 0 && mob->skill_use(md, tick, -1)) { - if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER)) + if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER) + && md->state.skillstate != MSS_WALK) //Walk skills are supposed to be used while walking { //Skill used, abort walking clif->fixpos(bl); //Fix position as walk has been canceled. return 0; @@ -275,6 +346,21 @@ int unit_walktoxy_timer(int tid, int64 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 + if (ud->stepaction && ud->target_to) { + //Delete old stepaction even if not executed yet, the latest command is what counts + if(ud->steptimer != INVALID_TIMER) { + timer->delete(ud->steptimer, unit->step_timer); + ud->steptimer = INVALID_TIMER; + } + //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; + ud->steptimer = timer->add(tick+i, unit->step_timer, bl->id, 0); + } + if(ud->state.change_walk_target) return unit->walktoxy_sub(bl); @@ -294,7 +380,7 @@ int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data) { //Keep trying to run. if ( !(unit->run(bl, NULL, SC_RUN) || unit->run(bl, sd, SC_WUGDASH)) ) 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)) { @@ -377,7 +463,7 @@ int unit_walktoxy( struct block_list *bl, short x, short y, int flag) ud->state.walk_easy = flag&1; ud->to_x = x; ud->to_y = y; - unit->set_target(ud, 0); + unit->stop_attack(bl); //Sets target to 0 sc = status->get_sc(bl); if( sc ) { @@ -394,11 +480,6 @@ int unit_walktoxy( struct block_list *bl, short x, short y, int flag) return 1; } - if(ud->attacktimer != INVALID_TIMER) { - timer->delete( ud->attacktimer, unit->attack_timer ); - ud->attacktimer = INVALID_TIMER; - } - return unit->walktoxy_sub(bl); } @@ -453,7 +534,7 @@ int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int ud->target_to = tbl->id; ud->chaserange = range; //Note that if flag&2, this SHOULD be attack-range ud->state.attack_continue = flag&2?1:0; //Chase to attack. - unit->set_target(ud, 0); + unit->stop_attack(bl); //Sets target to 0 sc = status->get_sc(bl); if (sc && (sc->data[SC_CONFUSION] || sc->data[SC__CHAOS])) //Randomize the target position @@ -474,11 +555,6 @@ int unit_walktobl(struct block_list *bl, struct block_list *tbl, int range, int if(!unit->can_move(bl)) return 0; - if(ud->attacktimer != INVALID_TIMER) { - timer->delete( ud->attacktimer, unit->attack_timer ); - ud->attacktimer = INVALID_TIMER; - } - if (unit->walktoxy_sub(bl)) { set_mobstate(bl, flag&2); return 1; @@ -999,12 +1075,6 @@ int unit_set_walkdelay(struct block_list *bl, int64 tick, int delay, int type) { struct unit_data *ud = unit->bl2ud(bl); 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; @@ -1012,6 +1082,9 @@ int unit_set_walkdelay(struct block_list *bl, int64 tick, int delay, int type) { //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; if (ud->walktimer != INVALID_TIMER) @@ -1027,7 +1100,7 @@ int unit_set_walkdelay(struct block_list *bl, int64 tick, int delay, int type) { } else { - unit->stop_walking(bl,2|4); + unit->stop_walking(bl,4); if(ud->target) timer->add(ud->canmove_tick+1, unit->walktobl_sub, bl->id, ud->target); } @@ -1223,6 +1296,18 @@ 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 + // New action request received, delete previous action request if not executed yet + if(ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(src); + // 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->stepskill_id = skill_id; + ud->stepskill_lv = skill_lv; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } + //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 && (!temp || ud->attacktimer == INVALID_TIMER) ) { @@ -1501,10 +1586,24 @@ 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 + // New action request received, delete previous action request if not executed yet + if(ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(src); + // 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->list[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->stepskill_id = skill_id; + ud->stepskill_lv = skill_lv; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } + 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); @@ -1586,18 +1685,51 @@ int unit_set_target(struct unit_data* ud, int target_id) return 0; } -int unit_stop_attack(struct block_list *bl) +/** + * Stop a unit's attacks + * @param bl: Object to stop + */ +void unit_stop_attack(struct block_list *bl) { - struct unit_data *ud = unit->bl2ud(bl); - nullpo_ret(bl); + struct unit_data *ud; + nullpo_retv(bl); + ud = unit_bl2ud(bl); + nullpo_retv(ud); - if(!ud || ud->attacktimer == INVALID_TIMER) - return 0; + //Clear target + unit_set_target(ud, 0); + + if(ud->attacktimer == INVALID_TIMER) + return; - timer->delete( ud->attacktimer, unit->attack_timer ); + //Clear timer + timer->delete(ud->attacktimer, unit->attack_timer); ud->attacktimer = INVALID_TIMER; - unit->set_target(ud, 0); - return 0; +} + +/** + * Stop a unit's step action + * @param bl: Object to stop + */ +void unit_stop_stepaction(struct block_list *bl) +{ + struct unit_data *ud; + nullpo_retv(bl); + ud = unit_bl2ud(bl); + nullpo_retv(ud); + + //Clear remembered step action + ud->stepaction = false; + ud->target_to = 0; + ud->stepskill_id = 0; + ud->stepskill_lv = 0; + + if(ud->steptimer == INVALID_TIMER) + return; + + //Clear timer + timer->delete(ud->steptimer, unit->step_timer); + ud->steptimer = INVALID_TIMER; } //Means current target is unattackable. For now only unlocks mobs. @@ -1623,6 +1755,7 @@ int unit_unattackable(struct block_list *bl) 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)); @@ -1654,16 +1787,26 @@ 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 continuously, set to auto-case 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; + // New action request received, delete previous action request if not executed yet + if(ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(src); + // 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->stepskill_id = 0; + ud->stepskill_lv = 0; + return 0; // Attacking will be handled by unit_walktoxy_timer in this case + } if(DIFF_TICK(ud->attackabletime, timer->gettick()) > 0) //Do attack next time it is possible. [Skotlex] @@ -1865,15 +2008,17 @@ int unit_attack_timer_sub(struct block_list* src, int tid, int64 tick) { } 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; } if( !battle->check_range(src,target,range) ) { @@ -1895,8 +2040,15 @@ int unit_attack_timer_sub(struct block_list* src, int tid, int64 tick) { if(ud->walktimer != INVALID_TIMER) unit->stop_walking(src,1); if(md) { - if (mob->skill_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 (mob->skill_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] md->last_linktime = tick; @@ -2013,6 +2165,7 @@ void unit_dataset(struct block_list *bl) { ud->walktimer = INVALID_TIMER; ud->skilltimer = INVALID_TIMER; ud->attacktimer = INVALID_TIMER; + ud->steptimer = INVALID_TIMER; ud->attackabletime = ud->canact_tick = ud->canmove_tick = timer->gettick(); @@ -2078,15 +2231,19 @@ int unit_remove_map(struct block_list *bl, clr_type clrtype, const char* file, i map->freeblock_lock(); - unit->set_target(ud, 0); - if (ud->walktimer != INVALID_TIMER) unit->stop_walking(bl,0); - if (ud->attacktimer != INVALID_TIMER) - unit->stop_attack(bl); if (ud->skilltimer != INVALID_TIMER) unit->skillcastcancel(bl,0); + //Clear target even if there is no timer + if (ud->target || ud->attacktimer != INVALID_TIMER) + unit_stop_attack(bl); + + //Clear stepaction even if there is no timer + if (ud->stepaction || ud->steptimer != INVALID_TIMER) + unit->stop_stepaction(bl); + // Do not reset can-act delay. [Skotlex] ud->attackabletime = ud->canmove_tick /*= ud->canact_tick*/ = timer->gettick(); if(sc && sc->count ) { //map-change/warp dispells. @@ -2599,6 +2756,7 @@ int do_init_unit(bool minimal) { timer->add_func_list(unit->walktoxy_timer,"unit_walktoxy_timer"); timer->add_func_list(unit->walktobl_sub, "unit_walktobl_sub"); timer->add_func_list(unit->delay_walktoxy_timer,"unit_delay_walktoxy_timer"); + timer->add_func_list(unit->step_timer,"unit_step_timer"); return 0; } @@ -2632,6 +2790,8 @@ void unit_defaults(void) { unit->warp = unit_warp; unit->stop_walking = unit_stop_walking; unit->skilluse_id = unit_skilluse_id; + unit->step_timer = unit_step_timer; + unit->stop_stepaction = unit_stop_stepaction; unit->is_walking = unit_is_walking; unit->can_move = unit_can_move; unit->resume_running = unit_resume_running; diff --git a/src/map/unit.h b/src/map/unit.h index 9b95bae41..d107992dd 100644 --- a/src/map/unit.h +++ b/src/map/unit.h @@ -30,6 +30,9 @@ struct unit_data { int attacktimer; int walktimer; int chaserange; + bool stepaction; //Action should be executed on step [Playtester] + int steptimer; //Timer that triggers the action [Playtester] + uint16 stepskill_id,stepskill_lv; //Remembers skill that should be casted on step [Playtester] int64 attackabletime; int64 canact_tick; int64 canmove_tick; @@ -93,6 +96,8 @@ struct unit_interface { int (*warp) (struct block_list *bl, short m, short x, short y, clr_type type); int (*stop_walking) (struct block_list *bl, int type); int (*skilluse_id) (struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv); + int (*step_timer) (int tid, int64 tick, int id, intptr_t data); + void (*stop_stepaction) (struct block_list *bl); int (*is_walking) (struct block_list *bl); int (*can_move) (struct block_list *bl); int (*resume_running) (int tid, int64 tick, int id, intptr_t data); @@ -101,7 +106,7 @@ struct unit_interface { int (*skilluse_pos) (struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv); int (*skilluse_pos2) (struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv, int casttime, int castcancel); int (*set_target) (struct unit_data *ud, int target_id); - int (*stop_attack) (struct block_list *bl); + void (*stop_attack) (struct block_list *bl); int (*unattackable) (struct block_list *bl); int (*attack) (struct block_list *src, int target_id, int continuous); int (*cancel_combo) (struct block_list *bl); |