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)
This commit is contained in:
parent
9a4f574d12
commit
cfef8a0088
@ -29,9 +29,7 @@ monster_max_aspd: 199
|
|||||||
// 0x004: If not set, mobs that can change target only do so when melee attacked
|
// 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
|
// (distance player/mob < 3), otherwise mobs may change target and chase
|
||||||
// ranged attackers. This flag also overrides the 'provoke' target.
|
// ranged attackers. This flag also overrides the 'provoke' target.
|
||||||
// 0x008: If set, when a mob loses track of their target, they stop walking
|
// 0x008: When set, mobs scatter as soon as they lose their target. Use this mode
|
||||||
// immediately. Otherwise, they continue to their last target tile. When
|
|
||||||
// set mobs also 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
|
// to make it much harder to mob-train by hiding and collecting them on a
|
||||||
// single spot (ie: GrimTooth training)
|
// single spot (ie: GrimTooth training)
|
||||||
// 0x010: If set, mob skills defined for friends will also trigger on themselves.
|
// 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.
|
// Example: 0x140 -> Chase players through warps + use skills in random order.
|
||||||
monster_ai: 0
|
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)?
|
// Should mobs be able to be warped (add as needed)?
|
||||||
// 0: Disable.
|
// 0: Disable.
|
||||||
// 1: Enable mob-warping when standing on NPC-warps
|
// 1: Enable mob-warping when standing on NPC-warps
|
||||||
|
@ -7666,6 +7666,7 @@ static const struct _battle_data {
|
|||||||
{ "berserk_cancels_buffs", &battle_config.berserk_cancels_buffs, 0, 0, 1, },
|
{ "berserk_cancels_buffs", &battle_config.berserk_cancels_buffs, 0, 0, 1, },
|
||||||
{ "debuff_on_logout", &battle_config.debuff_on_logout, 1|2, 0, 1|2, },
|
{ "debuff_on_logout", &battle_config.debuff_on_logout, 1|2, 0, 1|2, },
|
||||||
{ "monster_ai", &battle_config.mob_ai, 0x000, 0x000, 0x77F, },
|
{ "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, },
|
{ "hom_setting", &battle_config.hom_setting, 0xFFFF, 0x0000, 0xFFFF, },
|
||||||
{ "dynamic_mobs", &battle_config.dynamic_mobs, 1, 0, 1, },
|
{ "dynamic_mobs", &battle_config.dynamic_mobs, 1, 0, 1, },
|
||||||
{ "mob_remove_damaged", &battle_config.mob_remove_damaged, 1, 0, 1, },
|
{ "mob_remove_damaged", &battle_config.mob_remove_damaged, 1, 0, 1, },
|
||||||
|
@ -402,6 +402,7 @@ extern struct Battle_Config
|
|||||||
int berserk_cancels_buffs; // [Aru]
|
int berserk_cancels_buffs; // [Aru]
|
||||||
int debuff_on_logout; // Removes a few "official" negative Scs on logout. [Skotlex]
|
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_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 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 dynamic_mobs; // Dynamic Mobs [Wizputer] - battle_athena flag implemented by [random]
|
||||||
int mob_remove_damaged; // Dynamic Mobs - Remove mobs even if damaged [Wizputer]
|
int mob_remove_damaged; // Dynamic Mobs - Remove mobs even if damaged [Wizputer]
|
||||||
|
@ -1316,8 +1316,7 @@ int mob_unlocktarget(struct mob_data *md, unsigned int tick)
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mob_stop_attack(md);
|
mob_stop_attack(md);
|
||||||
if (battle_config.mob_ai&0x8)
|
mob_stop_walking(md,1); //Stop chasing.
|
||||||
mob_stop_walking(md,1); //Immediately stop chasing.
|
|
||||||
md->state.skillstate = MSS_IDLE;
|
md->state.skillstate = MSS_IDLE;
|
||||||
md->next_walktime=tick+rnd()%3000+3000;
|
md->next_walktime=tick+rnd()%3000+3000;
|
||||||
break;
|
break;
|
||||||
@ -1423,9 +1422,6 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
|||||||
if (md->ud.skilltimer != INVALID_TIMER)
|
if (md->ud.skilltimer != INVALID_TIMER)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(md->ud.walktimer != INVALID_TIMER && md->ud.walkpath.path_pos <= 3)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Abnormalities
|
// Abnormalities
|
||||||
if(( md->sc.opt1 > 0 && md->sc.opt1 != OPT1_STONEWAIT && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE )
|
if(( md->sc.opt1 > 0 && md->sc.opt1 != OPT1_STONEWAIT && md->sc.opt1 != OPT1_BURNING && md->sc.opt1 != OPT1_CRYSTALIZE )
|
||||||
|| md->sc.data[SC_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) {//Should reset targets.
|
|| md->sc.data[SC_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) {//Should reset targets.
|
||||||
@ -1451,10 +1447,12 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
|||||||
tbl->type == BL_PC &&
|
tbl->type == BL_PC &&
|
||||||
((((TBL_PC*)tbl)->state.gangsterparadise && !(mode&MD_BOSS)) ||
|
((((TBL_PC*)tbl)->state.gangsterparadise && !(mode&MD_BOSS)) ||
|
||||||
((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER)
|
((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER)
|
||||||
)) { //Unlock current target.
|
)) { //No valid target
|
||||||
if (mob_warpchase(md, tbl))
|
if (mob_warpchase(md, tbl))
|
||||||
return true; //Chasing this target.
|
return true; //Chasing this target.
|
||||||
mob_unlocktarget(md, tick-(battle_config.mob_ai&0x8?3000:0)); //Imediately 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;
|
tbl = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1626,25 +1624,25 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
|||||||
mob_unlocktarget (md,tick);
|
mob_unlocktarget (md,tick);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Attempt to attack.
|
//Attempt to attack.
|
||||||
//At this point we know the target is attackable, we just gotta check if the range matches.
|
//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))
|
if (battle_check_range (&md->bl, tbl, md->status.rhw.range))
|
||||||
{ //Target within range, engage
|
{ //Target within range, engage
|
||||||
|
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(tbl->type == BL_PC)
|
if( !(mode&MD_RANDOMTARGET) )
|
||||||
mob_log_damage(md, tbl, 0); //Log interaction (counts as 'attacker' for the exp bonus)
|
unit_attack(&md->bl,tbl->id,1);
|
||||||
|
else { // Attack once and find a new random target
|
||||||
if( !(mode&MD_RANDOMTARGET) )
|
int search_size = (view_range < md->status.rhw.range) ? view_range : md->status.rhw.range;
|
||||||
unit_attack(&md->bl,tbl->id,1);
|
unit_attack(&md->bl, tbl->id, 0);
|
||||||
else { // Attack once and find a new random target
|
if ((tbl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md), search_size))) {
|
||||||
int search_size = (view_range < md->status.rhw.range) ? view_range : md->status.rhw.range;
|
md->target_id = tbl->id;
|
||||||
unit_attack(&md->bl, tbl->id, 0);
|
md->min_chase = md->db->range3;
|
||||||
if ((tbl = battle_getenemy(&md->bl, DEFAULT_ENEMY_TYPE(md), search_size))) {
|
}
|
||||||
md->target_id = tbl->id;
|
|
||||||
md->min_chase = md->db->range3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -1653,17 +1651,23 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
|||||||
//Out of range...
|
//Out of range...
|
||||||
if (!(mode&MD_CANMOVE))
|
if (!(mode&MD_CANMOVE))
|
||||||
{ //Can't chase. Attempt an idle skill before unlocking.
|
{ //Can't chase. Attempt an idle skill before unlocking.
|
||||||
md->state.skillstate = MSS_IDLE;
|
if (md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER)
|
||||||
if (!mobskill_use(md, tick, -1))
|
{ //Only use skill if no more attack delay left
|
||||||
mob_unlocktarget(md,tick);
|
md->state.skillstate = MSS_IDLE;
|
||||||
|
if (!mobskill_use(md, tick, -1))
|
||||||
|
mob_unlocktarget(md,tick);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!can_move)
|
if (!can_move)
|
||||||
{ //Stuck. Attempt an idle skill
|
{ //Stuck. Attempt an idle skill
|
||||||
md->state.skillstate = MSS_IDLE;
|
if (md->ud.target != tbl->id || md->ud.attacktimer == INVALID_TIMER)
|
||||||
if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL))
|
{ //Only use skill if no more attack delay left
|
||||||
mobskill_use(md, tick, -1);
|
md->state.skillstate = MSS_IDLE;
|
||||||
|
if (!(++md->ud.walk_count%IDLE_SKILL_INTERVAL))
|
||||||
|
mobskill_use(md, tick, -1);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1674,6 +1678,10 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
|
|||||||
)) //Current target tile is still within attack range.
|
)) //Current target tile is still within attack range.
|
||||||
return true;
|
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.
|
//Follow up if possible.
|
||||||
if(!mob_can_reach(md, tbl, md->min_chase, MSS_RUSH) ||
|
if(!mob_can_reach(md, tbl, md->min_chase, MSS_RUSH) ||
|
||||||
!unit_walktobl(&md->bl, tbl, md->status.rhw.range, 2))
|
!unit_walktobl(&md->bl, tbl, md->status.rhw.range, 2))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user