Improved chasing behavior (#6903)

- Fixes #6899
- Monsters will now always walk to the end of their chase path before checking for their target again (you can still change this by changing monster_chase_refresh in monster.conf)
- A monster's chase range is now exactly as defined in mob_db.yml except when it was just attacked
- A just-attacked monster's chase range is now only enhanced until it reaches its original target cell
- Ranged monsters will now always stop when their target is in attack range, even if they still have attack delay
- Fixed a small math error when calculating chase path
- When a monster loses its target, it will now always spread to an empty cell when it was chasing something and reaches the end of its chase path, but will no longer spread when it was already attacking the target that disappeared (it will still spread if you set the custom mob_ai setting for this)
- Fixed monsters not starting to chase a target while they are randomly walking

Thanks to @aleos89 for support.
This commit is contained in:
Playtester 2022-04-29 21:34:39 +02:00 committed by GitHub
parent e56977b5f2
commit 5181c70626
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 28 deletions

View File

@ -58,13 +58,13 @@ monster_ai: 0
// How often should a monster rethink its chase?
// 0: Every 100ms (MIN_MOBTHINKTIME)
// 1: Every cell moved
// 2: Every 2 cells moved
// 3: Every 3 cells moved (official)
// x: Every x cells moved
// Regardless of this setting, a monster will always rethink its chase if it has
// reached its target. Increase this value if you want to make monsters continue
// moving after they lost their target (hide, no line of sight, etc.).
monster_chase_refresh: 3
// x: Every x cells moved or at end of the chase path
// 30 (max): Only at end of the chase path (official)
// Regardless of this setting, a monster will always check for targets in attack
// range. Decrease this value if you want to make monsters to be more reactive while
// chasing. This also defines the maximum amount of cells monsters will move after
// they lost their target (hide, no line of sight, etc.).
monster_chase_refresh: 30
// Should mobs be able to be warped (add as needed)?
// 0: Disable.

View File

@ -10017,7 +10017,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, 3, 0, 30, },
{ "monster_chase_refresh", &battle_config.mob_chase_refresh, 30, 0, MAX_MINCHASE, },
{ "mob_icewall_walk_block", &battle_config.mob_icewall_walk_block, 75, 0, 255, },
{ "boss_icewall_walk_block", &battle_config.boss_icewall_walk_block, 0, 0, 255, },
{ "snap_dodge", &battle_config.snap_dodge, 0, 0, 1, },

View File

@ -48,7 +48,6 @@ using namespace rathena;
#define IDLE_SKILL_INTERVAL 10 //Active idle skills should be triggered every 1 second (1000/MIN_MOBTHINKTIME)
const t_tick MOB_MAX_DELAY = 24 * 3600 * 1000;
#define MAX_MINCHASE 30 //Max minimum chase value to use for mobs.
#define RUDE_ATTACKED_COUNT 1 //After how many rude-attacks should the skill be used?
// On official servers, monsters will only seek targets that are closer to walk to than their
@ -962,6 +961,22 @@ int mob_spawn_bg(const char* mapname, int16 x, int16 y, const char* mobname, int
return md->bl.id;
}
/*==========================================
* Returns true if a mob is currently chasing something
* based on its skillstate.
* Chase states: MSS_LOOT, MSS_RUSH, MSS_FOLLOW
*------------------------------------------*/
bool mob_is_chasing(int state)
{
switch (state) {
case MSS_LOOT:
case MSS_RUSH:
case MSS_FOLLOW:
return true;
}
return false;
}
/*==========================================
* Reachability to a Specification ID existence place
* state indicates type of 'seek' mob should do:
@ -1246,9 +1261,7 @@ int mob_target(struct mob_data *md,struct block_list *bl,int dist)
// When an angry monster is provoked, it will switch to retaliate AI
if (md->state.provoke_flag && md->state.aggressive)
md->state.aggressive = 0;
md->min_chase=dist+md->db->range3;
if(md->min_chase>MAX_MINCHASE)
md->min_chase=MAX_MINCHASE;
md->min_chase = cap_value(dist + md->db->range3 - md->status.rhw.range, md->db->range3, MAX_MINCHASE);
return 0;
}
@ -1304,9 +1317,7 @@ static int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap)
#endif
(*target) = bl;
md->target_id=bl->id;
md->min_chase= dist + md->db->range3;
if(md->min_chase>MAX_MINCHASE)
md->min_chase=MAX_MINCHASE;
md->min_chase = cap_value(dist + md->db->range3 - md->status.rhw.range, md->db->range3, MAX_MINCHASE);
return 1;
}
break;
@ -1489,9 +1500,7 @@ static int mob_ai_sub_hard_slavemob(struct mob_data *md,t_tick tick)
}
if (tbl && status_check_skilluse(&md->bl, tbl, 0, 0)) {
md->target_id=tbl->id;
md->min_chase=md->db->range3+distance_bl(&md->bl, tbl);
if(md->min_chase>MAX_MINCHASE)
md->min_chase=MAX_MINCHASE;
md->min_chase = cap_value(distance_bl(&md->bl, tbl) + md->db->range3 - md->status.rhw.range, md->db->range3, MAX_MINCHASE);
return 1;
}
}
@ -1509,6 +1518,9 @@ int mob_unlocktarget(struct mob_data *md, t_tick tick)
{
nullpo_ret(md);
// Remember if the monster was in a "chasing" state
bool chasestate = mob_is_chasing(md->state.skillstate);
switch (md->state.skillstate) {
case MSS_WALK:
if (md->ud.walktimer != INVALID_TIMER)
@ -1549,7 +1561,7 @@ int mob_unlocktarget(struct mob_data *md, t_tick tick)
}
if (!md->ud.state.ignore_cell_stack_limit && battle_config.official_cell_stack_limit > 0
&& (md->min_chase == md->db->range3 || battle_config.mob_ai & 0x8)
&& (chasestate || battle_config.mob_ai & 0x8)
&& map_count_oncell(md->bl.m, md->bl.x, md->bl.y, BL_CHAR | BL_NPC, 1) > battle_config.official_cell_stack_limit) {
unit_walktoxy(&md->bl, md->bl.x, md->bl.y, 8);
}
@ -1793,9 +1805,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
md->target_id = md->attacked_id; // set target
if (md->state.attacked_count)
md->state.attacked_count--; //Should we reset rude attack count?
md->min_chase = dist+md->db->range3;
if(md->min_chase>MAX_MINCHASE)
md->min_chase=MAX_MINCHASE;
md->min_chase = cap_value(dist + md->db->range3 - md->status.rhw.range, md->db->range3, MAX_MINCHASE);
tbl = abl; //Set the new target
}
}
@ -1944,7 +1954,8 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
return true;
//Only update target cell / drop target after having moved at least "mob_chase_refresh" cells
if(md->ud.walktimer != INVALID_TIMER && (!can_move || md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh))
if(md->ud.walktimer != INVALID_TIMER && (!can_move || md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh)
&& mob_is_chasing(md->state.skillstate))
return true;
//Out of range...
@ -4917,9 +4928,6 @@ void MobDatabase::loadingFinished() {
if (battle_config.chase_range_rate != 100)
mob->range3 = max(mob->range2, mob->range3 * battle_config.chase_range_rate / 100);
// Tests showed that chase range is effectively 2 cells larger than expected [Playtester]
mob->range3 += 2;
// If the attack animation is longer than the delay, the client crops the attack animation!
// On aegis there is no real visible effect of having a recharge-time less than amotion anyway.
mob->status.adelay = max(mob->status.adelay, mob->status.amotion);

View File

@ -29,6 +29,8 @@ struct guardian_data;
#define MAX_MOB_DROP_TOTAL (MAX_MOB_DROP+MAX_MOB_DROP_ADD)
#define MAX_MVP_DROP_TOTAL (MAX_MVP_DROP+MAX_MVP_DROP_ADD)
#define MAX_MINCHASE 30 //Max minimum chase value to use for mobs.
//Min time between AI executions
const t_tick MIN_MOBTHINKTIME = 100;
//Min time before mobs do a check to call nearby friends for help (or for slaves to support their master)

View File

@ -109,8 +109,8 @@ int unit_walktoxy_sub(struct block_list *bl)
for (i = (ud->chaserange*10)-10; i > 0 && ud->walkpath.path_len>1;) {
ud->walkpath.path_len--;
enum directions dir = ud->walkpath.path[ud->walkpath.path_len];
if( direction_diagonal( dir ) )
i -= MOVE_COST*20; //When chasing, units will target a diamond-shaped area in range [Playtester]
if (direction_diagonal(dir) && bl->type != BL_MOB)
i -= MOVE_COST * 2; //When chasing, units will target a diamond-shaped area in range [Playtester]
else
i -= MOVE_COST;
ud->to_x -= dirx[dir];