diff options
author | Michieru <Michieru@users.noreply.github.com> | 2014-10-13 17:52:07 +0200 |
---|---|---|
committer | Michieru <Michieru@users.noreply.github.com> | 2014-10-13 17:52:07 +0200 |
commit | b5a92f944f126e920442307bebd3c2b53c9b0d54 (patch) | |
tree | 9d4f345bfd275eeea26b0f13c5b3ab6d2589c993 /src/map/unit.c | |
parent | 1b86f4b8f1605eb58fa9a68329d1fd5d0da8e776 (diff) | |
download | hercules-b5a92f944f126e920442307bebd3c2b53c9b0d54.tar.gz hercules-b5a92f944f126e920442307bebd3c2b53c9b0d54.tar.bz2 hercules-b5a92f944f126e920442307bebd3c2b53c9b0d54.tar.xz hercules-b5a92f944f126e920442307bebd3c2b53c9b0d54.zip |
* Monster skill use behavior, ranges, position lag fixes
- Updated monster skill use behavior so monsters use skills the same way and at the same rhythm as official servers (bugreport:009326), the changes include:
* Unified the "monster can't move by default" and the "monster trapped" code as it really should behave exactly the same
* Fixed a bug that caused the "monster skill use" routine to be called 20 times more often than it should in some situations
* When a monster attacks you and you run away from it, the monster will now check if it has a ranged skill on "attack" state before switching to chase state
* Monsters will now always do a normal attack before using "attack" state skills
* Fixed a bug that caused monsters to switch to idle mode and never use their chase skills when they get hit continuously
* Changed default for skillrange_from_weapon from 30 (all but player) to 0 (none); monsters will now use all skills at the skill range and not at their own attack range, if they get tanked from farther away than skill range, they won't use these skills
- Updated ranges to work as on official servers (bugreport:009326), the changes include:
* Implemented new functions "check_distance_client" and "distance_client", that instead of the server-sided square range system use the circular one that the client uses; these functions should be used for units that send their commands via the client (players mostly)
* Applied the new distance algorithm to some player-specific range checks, players will now have a circular attack range that reflects their attack range on the client; this makes it impossible to hack the client for more diagonal range
* Removed the arbitrary +1 range bonus at range checks; as monsters now react fluently, they won't need it anymore, that means a monster with for example 4 attack range will now only attack targets within a 9x9 area around it, the moment you step out of this area, the attacks will stop (if the monster can move it will follow you); as for players, the extra cell attack range when on a linear line to the target now is integrated into the distance algorithm, that means a player with attack range 4 can attack from 5 cells away when directly on line with a monster, but no longer diagonally as range is circular for players now
* Implemented a new "step action" feature to reflect official chase range behavior; when an attack or skill is used inside the attack range, it will act exactly as now, the skill is used once the signal from the client was received; however, if an attack or skill is used outside the attack range, the player should move into the chase area which is 1 cell inside the attack range border; the client actually sends us where to use the attack / skill from, but previously we just dropped that information the moment an attack request came; now instead of stopping the player instantly on an attack request, the player will continue moving to the target cell and then automatically use the command received earlier (it will be remembered); this change was absolutely necessary as the client sends the attack request slightly before attack range is reached, execution on official servers only takes place on every full cell moved; the new system copies this behavior
- Implemented an improved hit-lock system (bugreport:007460), the changes include:
* MVPs are no longer immune to being stopped by a hit unless they used Endure
* When hitting any unit, it will stop for its "dMotion" interval, exactly at the end of "dMotion" it will continue walking (official behavior); this helps getting more "move" packets to be displayed
* The unit that was hit will be immune to being stopped for another "dMotion" interval, this allows to slow down any units by hitting them frequently, but makes it almost impossible to completely stop them forever (depends a lot of ASPD and dMotion value); this does not affect special hit-lock properties (some skills and events should set delay anyway)
* The unit that was hit will no longer be "pulled" to the next cell; this caused a lot of position lag, it should be much better now
* Random walking, monster target dropping
- Implemented official behavior for random walking of monsters (bugreport:9340)
* Updated the interval between walks from 3-6 seconds to 4-5 seconds
* Added a define MIN_RANDOMWALKTIME that is now used anywhere the interval is applied so it's easier to change
* Monsters will no longer attempt to walk to the cell they are currently standing on (note: still requires a proper "no cell stacking" implementation)
* Monsters will only target a cell within a 15x15 area around them
- Moved the "mob_ai" 0x008 configuration setting to mob_unlocktarget, so it applies to all cases of monsters unlocking targets
- Cleaned up the unit_stop_attack and unit_stop_stepaction functions
* unit_stop_attack now makes sure that the target is always set to 0, even if no attack timer currently exists
* replaced several calls of unit_set_target that set target to 0 with unit_stop_attack, this is important because the attack timer should always be deleted at the same time the target is set to 0
* this also fixes the problem that caused monsters to sometimes drop their target after an attack
* Minor monster walk and skill use fixes
- Fixed a bug that caused monsters to never use their idle skills even if the define MOB_LAZYSKILLPERC was set
* The default value of the define is now 100% for spotted mobs and 0% for non-spotted mobs as on official servers
- Monsters now use complex path searching for their random walk, so they can also walk around corners now (OFFICIAL_WALKPATH still applies!)
- Fixed spotted monsters using their "walk" skills even if nobody is on the map
- Monsters will no longer stop when using "walk" skills (they are supposed to be used while walking)
* Monster idle skills, chase, final cleanups
- Spotted monsters will now use their idle skills even if no players are on the map
* This is official behavior and allows them to metamorph and heal up even if nobody is on the map
* I originally had concerns about the performance, but as the dynamic mobs option is enabled by default and set to 5 minutes, monsters will only stay "spotted" for 5 minutes when nobody is on the map anyway, this doesn't cost much extra performance
- Increased chase range of monsters by 2
* Tested this thoroughly and the effective chase range is actually 2 cells larger than listed in the "Range3" column
* Special thanks to ultramage and Michieru for helping me testing this
- Some final cleanups in the unit_stop_attack and unit_stop_stepaction functions
* Special thanks to icxbb-xx for pointing these out
Super ultra mega thanks to Playtester (rAthena 2c86ee435670168,ebd3dc97bc7e,f3916c1baf1,b1330a4c6,c2377c8f54,464dd4586380)
Diffstat (limited to 'src/map/unit.c')
-rw-r--r-- | src/map/unit.c | 252 |
1 files changed, 206 insertions, 46 deletions
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; |