From c813ffe0ba08d3330397da4116b25bfe374a6116 Mon Sep 17 00:00:00 2001 From: Michieru Date: Thu, 2 Oct 2014 12:07:50 +0200 Subject: * Rewrote the hard monster AI. Monsters will now behave a lot closer to official servers: * Monsters will now attack immediately when they are chasing a target and it comes into attack range (bugreport:7370) * Monsters will now chase their target during their aDelay, but they still have to wait for aMotion to be able to move again (bugreport:9269) * Monsters will now rethink their chase in a configurable interval (see monster_chase_refresh in monster.conf), official value is once per cell, previously it was once per 3 cells * Monsters will now stop when they rethink their chase and their target is gone (player hides or target loot was picked), regardless of the monster_ai setting (note: if you want the old, stupid behavior, just increase monster_chase_refresh instead) Thanks to Playtester (rathena: cfef8a0088c3) --- conf/battle/monster.conf | 15 ++++++++--- src/map/battle.c | 1 + src/map/battle.h | 1 + src/map/mob.c | 66 ++++++++++++++++++++++++++---------------------- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/conf/battle/monster.conf b/conf/battle/monster.conf index 50d661087..9ca8df825 100644 --- a/conf/battle/monster.conf +++ b/conf/battle/monster.conf @@ -29,9 +29,7 @@ monster_max_aspd: 199 // 0x004: If not set, mobs that can change target only do so when melee attacked // (distance player/mob < 3), otherwise mobs may change target and chase // ranged attackers. This flag also overrides the 'provoke' target. -// 0x008: If set, when a mob loses track of their target, they stop walking -// immediately. Otherwise, they continue to their last target tile. When -// set mobs also scatter as soon as they lose their target. Use this mode +// 0x008: When set, mobs scatter as soon as they lose their target. Use this mode // to make it much harder to mob-train by hiding and collecting them on a // single spot (ie: GrimTooth training) // 0x010: If set, mob skills defined for friends will also trigger on themselves. @@ -52,6 +50,17 @@ monster_max_aspd: 199 // Example: 0x140 -> Chase players through warps + use skills in random order. monster_ai: 0 +// How often should a monster rethink its chase? +// 0: Every 100ms (MIN_MOBTHINKTIME) +// 1: Every cell moved (official) +// 2: Every 2 cells moved +// 3: Every 3 cells moved (previous setting) +// 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, loot picked, etc.). +monster_chase_refresh: 1 + // Should mobs be able to be warped (add as needed)? // 0: Disable. // 1: Enable mob-warping when standing on NPC-warps diff --git a/src/map/battle.c b/src/map/battle.c index bdd1b9bc9..5eabc719d 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -6715,6 +6715,7 @@ 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, }, diff --git a/src/map/battle.h b/src/map/battle.h index 734a6187d..eeb9897fa 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -353,6 +353,7 @@ 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] diff --git a/src/map/mob.c b/src/map/mob.c index ffab804a6..cdd842075 100644 --- a/src/map/mob.c +++ b/src/map/mob.c @@ -1298,8 +1298,7 @@ int mob_unlocktarget(struct mob_data *md, int64 tick) { break; default: mob_stop_attack(md); - if (battle_config.mob_ai&0x8) - mob_stop_walking(md,1); //Immediately stop chasing. + mob_stop_walking(md,1); //Stop chasing. md->state.skillstate = MSS_IDLE; md->next_walktime=tick+rnd()%3000+3000; break; @@ -1404,9 +1403,6 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 tick) { if (md->ud.skilltimer != INVALID_TIMER) return false; - if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= 3) - return false; - // Abnormalities if(( md->sc.opt1 > 0 && md->sc.opt1 != OPT1_STONEWAIT && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE ) || md->sc.data[SC_DEEP_SLEEP] || md->sc.data[SC_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) { @@ -1434,10 +1430,12 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 tick) { || ((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER) ) ) { - //Unlock current target. + //No valid target if (mob->warpchase(md, tbl)) return true; //Chasing this target. - mob->unlocktarget(md, tick-(battle_config.mob_ai&0x8?3000:0)); //Immediately do random walk. + 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. tbl = NULL; } } @@ -1604,27 +1602,25 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 tick) { mob->unlocktarget (md,tick); return true; } + //Attempt to attack. //At this point we know the target is attackable, we just gotta check if the range matches. - if (md->ud.target == tbl->id && md->ud.attacktimer != INVALID_TIMER) //Already locked. - return true; - if (battle->check_range (&md->bl, tbl, md->status.rhw.range)) { //Target within range, engage - - if(tbl->type == BL_PC) - mob->log_damage(md, tbl, 0); //Log interaction (counts as 'attacker' for the exp bonus) - - if(!(mode&MD_RANDOMTARGET)) - unit->attack(&md->bl,tbl->id,1); - else { // Attack once and find new random target - int search_size = (view_range < md->status.rhw.range) ? view_range : md->status.rhw.range; - unit->attack(&md->bl,tbl->id,0); - tbl = battle->get_enemy(&md->bl, DEFAULT_ENEMY_TYPE(md), search_size); - // If no target was found, keep atacking the old one - if( tbl ) { - md->target_id = tbl->id; - md->min_chase = md->db->range3; + if (md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER) + { //Only attack if no more attack delay left + if(tbl->type == BL_PC) + mob->log_damage(md, tbl, 0); //Log interaction (counts as 'attacker' for the exp bonus) + + if( !(mode&MD_RANDOMTARGET) ) + unit->attack(&md->bl,tbl->id,1); + else { // Attack once and find a new random target + int search_size = (view_range < md->status.rhw.range) ? view_range : md->status.rhw.range; + unit->attack(&md->bl,tbl->id, 0); + if ((tbl = battle->get_enemy(&md->bl, DEFAULT_ENEMY_TYPE(md), search_size))) { + md->target_id = tbl->id; + md->min_chase = md->db->range3; + } } } return true; @@ -1633,17 +1629,23 @@ 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. - md->state.skillstate = MSS_IDLE; - if (!mob->skill_use(md, tick, -1)) - mob->unlocktarget(md,tick); + 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); + } return true; } if (!can_move) { //Stuck. Attempt an idle skill - md->state.skillstate = MSS_IDLE; - if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL)) - mob->skill_use(md, tick, -1); + 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)) + mob->skill_use(md, tick, -1); + } return true; } @@ -1654,6 +1656,10 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 tick) { )) //Current target tile is still within attack range. return true; + //Only update target cell after having moved at least "mob_chase_refresh" cells + if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh) + return true; + //Follow up if possible. if(!mob->can_reach(md, tbl, md->min_chase, MSS_RUSH) || !unit->walktobl(&md->bl, tbl, md->status.rhw.range, 2)) -- cgit v1.2.3-60-g2f50