Skip to content

Commit

Permalink
Official monster/MVP target selection (fixes #926)
Browse files Browse the repository at this point in the history
* Monsters with mode MD_CHANGETARGET_MELEE will now only change targets on "attack" state if they are attacked by a normal attack
* Monsters will change targets if the target is within "attack range+1" distance instead of a static distance of 3
* When a monster gets attacked, it will now switch to the attacker even if the attacker is farther away than its current target and the current target is auto-attacking it
* Angry mode monsters will now always switch target to the first person that attacked them
  • Loading branch information
Playtester committed Jan 23, 2016
1 parent 418dc47 commit 4fdcb2e
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 45 deletions.
7 changes: 5 additions & 2 deletions conf/battle/monster.conf
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ monster_max_aspd: 199
// are attacked and they can't attack back regardless of how they were
// attacked (eg: GrimTooth), otherwise, their rude attack" is only activated
// if they can't melee reach the target (eg: sniping)
// 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
// 0x004: If not set, mobs that can change target only do so when attacked within a
// distance of [attack range+1], otherwise mobs may change target and chase
// ranged attackers. This flag also overrides the 'provoke' target.
// 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
Expand All @@ -38,6 +38,9 @@ monster_max_aspd: 199
// of players.
// 0x040: When set, when the mob's target changes map, the mob will walk towards
// any npc-warps in it's sight of view (use with mob_warp below)
// 0x080: If not set, mobs on attack state will only change targets when attacked
// by normal attacks. Set this if you want mobs to also switch targets when
// hit by skills.
// 0x100: When set, a mob will pick a random skill from it's list and start from
// that instead of checking skills in orders (when unset, if a mob has too
// many skills, the ones near the end will rarely get selected)
Expand Down
50 changes: 35 additions & 15 deletions src/map/battle.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,36 @@ struct block_list* battle_getenemyarea(struct block_list *src, int x, int y, int
return bl_list[rnd()%c];
}

/*========================================== [Playtester]
* Deals damage without delay, applies additional effects and triggers monster events
* This function is called from battle_delay_damage or battle_delay_damage_sub
* @param src: Source of damage
* @param target: Target of damage
* @param damage: Damage to be dealt
* @param delay: Damage delay
* @param skill_lv: Level of skill used
* @param skill_id: ID o skill used
* @param dmg_lv: State of the attack (miss, etc.)
* @param attack_type: Damage delay
* @param additional_effects: Whether additional effect should be applied
* @param tick: Current tick
*------------------------------------------*/
void battle_damage(struct block_list *src, struct block_list *target, int64 damage, int delay, uint16 skill_lv, uint16 skill_id, enum damage_lv dmg_lv, unsigned short attack_type, bool additional_effects, unsigned int tick) {
map_freeblock_lock();
status_fix_damage(src, target, damage, delay); // We have to separate here between reflect damage and others [icescope]
if (attack_type && !status_isdead(target) && additional_effects)
skill_additional_effect(src, target, skill_id, skill_lv, attack_type, dmg_lv, tick);
if (dmg_lv > ATK_BLOCK && attack_type)
skill_counter_additional_effect(src, target, skill_id, skill_lv, attack_type, tick);
// This is the last place where we have access to the actual damage type, so any monster events depending on type must be placed here
if (target->type == BL_MOB && damage && (attack_type&BF_NORMAL)) {
// Monsters differentiate whether they have been attacked by a skill or a normal attack
struct mob_data* md = BL_CAST(BL_MOB, target);
md->norm_attacked_id = md->attacked_id;
}
map_freeblock_unlock();
}

/// Damage Delayed Structure
struct delay_damage {
int src_id;
Expand Down Expand Up @@ -281,13 +311,8 @@ int battle_delay_damage_sub(int tid, unsigned int tick, int id, intptr_t data)
(target->type != BL_PC || ((TBL_PC*)target)->invincible_timer == INVALID_TIMER) &&
check_distance_bl(src, target, dat->distance) ) //Check to see if you haven't teleported. [Skotlex]
{
map_freeblock_lock();
status_fix_damage(src, target, dat->damage, dat->delay);
if( dat->attack_type && !status_isdead(target) && dat->additional_effects )
skill_additional_effect(src,target,dat->skill_id,dat->skill_lv,dat->attack_type,dat->dmg_lv,tick);
if( dat->dmg_lv > ATK_BLOCK && dat->attack_type )
skill_counter_additional_effect(src,target,dat->skill_id,dat->skill_lv,dat->attack_type,tick);
map_freeblock_unlock();
//Deal damage
battle_damage(src, target, dat->damage, dat->delay, dat->skill_lv, dat->skill_id, dat->dmg_lv, dat->attack_type, dat->additional_effects, tick);
} else if( !src && dat->skill_id == CR_REFLECTSHIELD ) { // it was monster reflected damage, and the monster died, we pass the damage to the character as expected
map_freeblock_lock();
status_fix_damage(target, target, dat->damage, dat->delay);
Expand Down Expand Up @@ -327,13 +352,8 @@ int battle_delay_damage(unsigned int tick, int amotion, struct block_list *src,
damage = 0;

if ( !battle_config.delay_battle_damage || amotion <= 1 ) {
map_freeblock_lock();
status_fix_damage(src, target, damage, ddelay); // We have to separate here between reflect damage and others [icescope]
if( attack_type && !status_isdead(target) && additional_effects )
skill_additional_effect(src, target, skill_id, skill_lv, attack_type, dmg_lv, gettick());
if( dmg_lv > ATK_BLOCK && attack_type )
skill_counter_additional_effect(src, target, skill_id, skill_lv, attack_type, gettick());
map_freeblock_unlock();
//Deal damage
battle_damage(src, target, damage, ddelay, skill_lv, skill_id, dmg_lv, attack_type, additional_effects, gettick());
return 0;
}
dat = ers_alloc(delay_damage_ers, struct delay_damage);
Expand Down Expand Up @@ -8010,7 +8030,7 @@ static const struct _battle_data {
{ "ignore_items_gender", &battle_config.ignore_items_gender, 1, 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, },
{ "monster_ai", &battle_config.mob_ai, 0x000, 0x000, 0x77F, },
{ "monster_ai", &battle_config.mob_ai, 0x000, 0x000, 0x7FF, },
{ "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, },
Expand Down
1 change: 1 addition & 0 deletions src/map/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ int64 battle_calc_damage(struct block_list *src,struct block_list *bl,struct Dam
int64 battle_calc_gvg_damage(struct block_list *src,struct block_list *bl,int64 damage,uint16 skill_id,int flag);
int64 battle_calc_bg_damage(struct block_list *src,struct block_list *bl,int64 damage,uint16 skill_id,int flag);

void battle_damage(struct block_list *src, struct block_list *target, int64 damage, int delay, uint16 skill_lv, uint16 skill_id, enum damage_lv dmg_lv, unsigned short attack_type, bool additional_effects, unsigned int tick);
int battle_delay_damage (unsigned int tick, int amotion, struct block_list *src, struct block_list *target, int attack_type, uint16 skill_id, uint16 skill_lv, int64 damage, enum damage_lv dmg_lv, int ddelay, bool additional_effects);

// Summary normal attack treatment (basic attack)
Expand Down
42 changes: 18 additions & 24 deletions src/map/mob.c
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,7 @@ int mob_spawn (struct mob_data *md)
memset(&md->state, 0, sizeof(md->state));
status_calc_mob(md, SCO_FIRST);
md->attacked_id = 0;
md->norm_attacked_id = 0;
md->target_id = 0;
md->move_fail_count = 0;
md->ud.state.attack_continue = 0;
Expand Down Expand Up @@ -1047,7 +1048,9 @@ static int mob_can_changetarget(struct mob_data* md, struct block_list* target,
case MSS_BERSERK:
if (!(mode&MD_CHANGETARGET_MELEE))
return 0;
return (battle_config.mob_ai&0x4 || check_distance_bl(&md->bl, target, 3));
if (!(battle_config.mob_ai&0x80) && md->norm_attacked_id != target->id)
return 0;
return (battle_config.mob_ai&0x4 || check_distance_bl(&md->bl, target, md->status.rhw.range+1));
case MSS_RUSH:
return (mode&MD_CHANGETARGET_CHASE);
case MSS_FOLLOW:
Expand Down Expand Up @@ -1476,7 +1479,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
// 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_BLADESTOP] || md->sc.data[SC__MANHOLE] || md->sc.data[SC_CURSEDCIRCLE_TARGET]) {//Should reset targets.
md->target_id = md->attacked_id = 0;
md->target_id = md->attacked_id = md->norm_attacked_id = 0;
return false;
}

Expand Down Expand Up @@ -1527,7 +1530,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
&& !mobskill_use(md, tick, MSC_RUDEATTACKED) // If can't rude Attack
&& can_move && unit_escape(&md->bl, tbl, rnd()%10 +1)) // Attempt escape
{ //Escaped
md->attacked_id = 0;
md->attacked_id = md->norm_attacked_id = 0;
return true;
}
}
Expand Down Expand Up @@ -1555,7 +1558,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
&& !tbl && unit_escape(&md->bl, abl, rnd()%10 +1))
{ //Escaped.
//TODO: Maybe it shouldn't attempt to run if it has another, valid target?
md->attacked_id = 0;
md->attacked_id = md->norm_attacked_id = 0;
return true;
}
}
Expand All @@ -1566,23 +1569,19 @@ static bool mob_ai_sub_hard(struct mob_data *md, unsigned int tick)
}
else
{ //Attackable
if (!tbl || dist < md->status.rhw.range || !check_distance_bl(&md->bl, tbl, dist)
|| battle_gettarget(tbl) != md->bl.id)
{ //Change if the new target is closer than the actual one
//or if the previous target is not attacking the mob. [Skotlex]
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;
tbl = abl; //Set the new target
}
//If a monster can change the target to the attacker, it will change the target
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;
tbl = abl; //Set the new target
}
}

//Clear it since it's been checked for already.
md->attacked_id = 0;
md->attacked_id = md->norm_attacked_id = 0;
}

// Processing of slave monster
Expand Down Expand Up @@ -2155,13 +2154,8 @@ void mob_damage(struct mob_data *md, struct block_list *src, int damage)
damage = (int)(UINT_MAX - md->tdmg);
md->tdmg = UINT_MAX;
}
if (md->state.aggressive) { //No longer aggressive, change to retaliate AI.
if (md->state.aggressive) //No longer aggressive, change to retaliate AI.
md->state.aggressive = 0;
if(md->state.skillstate== MSS_ANGRY)
md->state.skillstate = MSS_BERSERK;
if(md->state.skillstate== MSS_FOLLOW)
md->state.skillstate = MSS_RUSH;
}
//Log damage
if (src)
mob_log_damage(md, src, damage);
Expand Down Expand Up @@ -2941,7 +2935,7 @@ int mob_class_change (struct mob_data *md, int mob_id)
md->lootitems = (struct s_mob_lootitem *)aCalloc(LOOTITEM_SIZE,sizeof(struct s_mob_lootitem));

//Targets should be cleared no morph
md->target_id = md->attacked_id = 0;
md->target_id = md->attacked_id = md->norm_attacked_id = 0;

//Need to update name display.
clif_charnameack(0, &md->bl);
Expand Down
2 changes: 1 addition & 1 deletion src/map/mob.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ struct mob_data {
short mob_id;
unsigned int tdmg; //Stores total damage given to the mob, for exp calculations. [Skotlex]
int level;
int target_id,attacked_id;
int target_id,attacked_id,norm_attacked_id;
int areanpc_id; //Required in OnTouchNPC (to avoid multiple area touchs)
unsigned int bg_id; // BattleGround System

Expand Down
5 changes: 2 additions & 3 deletions src/map/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -2576,10 +2576,9 @@ static int unit_attack_timer_sub(struct block_list* src, int tid, unsigned int t
if(md->state.skillstate == MSS_ANGRY || md->state.skillstate == MSS_BERSERK) {
if (mobskill_use(md,tick,-1))
return 1;
} else {
// Set mob's ANGRY/BERSERK states.
md->state.skillstate = md->state.aggressive?MSS_ANGRY:MSS_BERSERK;
}
// 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]
Expand Down

1 comment on commit 4fdcb2e

@Playtester
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to mention: I added a config that allows you to make monsters switch targets on skills again (previous behavior).

Please sign in to comment.