Browse Source

Improved Monster Warpchase Feature (#9127)

- Monsters will now reliably chase you through warp portals
  * This includes Priest portals if the corresponding setting is enabled in monster.conf
  * It will work regardless of other settings
- This also fixes a small issue with monsters not unlocking their target when nobody is on the map
Playtester 2 tháng trước cách đây
mục cha
commit
b9021da653
3 tập tin đã thay đổi với 120 bổ sung43 xóa
  1. 4 2
      conf/battle/monster.conf
  2. 116 34
      src/map/mob.cpp
  3. 0 7
      src/map/unit.cpp

+ 4 - 2
conf/battle/monster.conf

@@ -33,8 +33,10 @@ monster_hp_rate: 100
 // 0x0020: When set, the monster ai is executed for all monsters in maps that 
 //         have players on them, instead of only for mobs who are in the vicinity
 //         of players.
-// 0x0040: 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)
+// 0x0040: When set, when the mob's target changes map or is out of sight, the mob
+//         will walk towards npc-warps and/or priest warps in its sight of view.
+//         It will only walk to warps it can use and only to warps that bring it back
+//         in sight of the target (use with mob_warp below).
 // 0x0080: 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.

+ 116 - 34
src/map/mob.cpp

@@ -1413,31 +1413,91 @@ static int32 mob_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap)
 }
 
 static int32 mob_warpchase_sub(struct block_list *bl,va_list ap) {
-	struct block_list *target;
-	struct npc_data **target_nd;
-	struct npc_data *nd;
-	int32 *min_distance;
 	int32 cur_distance;
 
-	target= va_arg(ap, struct block_list*);
-	target_nd= va_arg(ap, struct npc_data**);
-	min_distance= va_arg(ap, int32*);
+	block_list* target= va_arg(ap, struct block_list*);
+	block_list** target_warp= va_arg(ap, struct block_list**);
+	int32* min_distance= va_arg(ap, int32*);
 
-	nd = (TBL_NPC*) bl;
+	switch (bl->type) {
+		case BL_NPC:
+		{
+			// NPC Warp
+			npc_data* nd = reinterpret_cast<npc_data*>(bl);
 
-	if(nd->subtype != NPCTYPE_WARP)
-		return 0; //Not a warp
+			// Not a warp
+			if (nd->subtype != NPCTYPE_WARP)
+				return 0;
 
-	if(nd->u.warp.mapindex != map_getmapdata(target->m)->index)
-		return 0; //Does not lead to the same map.
+			// Does not lead to the same map as target
+			if (nd->u.warp.mapindex != map_getmapdata(target->m)->index)
+				return 0;
 
-	cur_distance = distance_blxy(target, nd->u.warp.x, nd->u.warp.y);
-	if (cur_distance < *min_distance)
-	{	//Pick warp that leads closest to target.
-		*target_nd = nd;
-		*min_distance = cur_distance;
-		return 1;
+			// Leads to a different map that is set to not be accessible through warp chase
+			if (battle_config.mob_warp&4) {
+				int16 warp_m = map_mapindex2mapid(nd->u.warp.mapindex);
+				if (warp_m != bl->m && map_getmapflag(warp_m, MF_NOBRANCH))
+					return 0;
+			}
+
+			// Get distance from warp exit to target
+			cur_distance = distance_blxy(target, nd->u.warp.x, nd->u.warp.y);
+
+			//Pick warp that leads closest to target
+			if (cur_distance < *min_distance) {
+				*target_warp = &nd->bl;
+				*min_distance = cur_distance;
+				return 1;
+			}
+		}
+		break;
+
+		case BL_SKILL:
+		{
+			// Skill Warp
+			skill_unit* su = reinterpret_cast<skill_unit*>(bl);
+
+			if (su->group == nullptr)
+				return 0;
+
+			switch (su->group->unit_id) {
+				case UNT_WARP_WAITING:
+					// Monsters cannot use priest warps
+					if (!(battle_config.mob_warp&2))
+						return 0;
+
+					// Does not lead to the same map as target
+					if (su->group->val3 != map_getmapdata(target->m)->index)
+						return 0;
+
+					// Leads to a different map that is set to not be accessible through warp chase
+					if (battle_config.mob_warp&4) {
+						int16 warp_m = map_mapindex2mapid(su->group->val3);
+						if (warp_m != bl->m && map_getmapflag(warp_m, MF_NOBRANCH))
+							return 0;
+					}
+
+					// Get distance from warp exit to target
+					cur_distance = distance_blxy(target, su->group->val2 >> 16, su->group->val2 & 0xffff);
+					break;
+				case UNT_DIMENSIONDOOR:
+					// Not used for warp chase as the target coordinates are random
+					return 0;
+				default:
+					// Skill cannot warp
+					return 0;
+			}
+
+			//Pick warp that leads closest to target.
+			if (cur_distance < *min_distance) {
+				*target_warp = &su->bl;
+				*min_distance = cur_distance;
+				return 1;
+			}
+		}
+		break;
 	}
+
 	return 0;
 }
 /*==========================================
@@ -1695,10 +1755,23 @@ int32 mob_randomwalk(struct mob_data *md,t_tick tick)
  */
 int32 mob_warpchase(struct mob_data *md, struct block_list *target)
 {
-	struct npc_data *warp = nullptr;
-	int32 distance = AREA_SIZE;
-	if (!(target && battle_config.mob_ai&0x40 && battle_config.mob_warp&1))
-		return 0; //Can't warp chase.
+	if ((battle_config.mob_ai&0x40) == 0)
+		return 0; // Warp chase disabled
+
+	if (target == nullptr)
+		return 0; // No target
+
+	if (target->m < 0)
+		return 0; // Target isn't on any map
+
+	int32 type = BL_NUL;
+	if (battle_config.mob_warp&1)
+		type |= BL_NPC;
+	if (battle_config.mob_warp&2)
+		type |= BL_SKILL;
+
+	if (type == BL_NUL)
+		return 0; // No warp types to search for
 
 	if (target->m == md->bl.m && check_distance_bl(&md->bl, target, AREA_SIZE))
 		return 0; //No need to do a warp chase.
@@ -1707,11 +1780,14 @@ int32 mob_warpchase(struct mob_data *md, struct block_list *target)
 		map_getcell(md->bl.m,md->ud.to_x,md->ud.to_y,CELL_CHKNPC))
 		return 2; //Already walking to a warp.
 
+	block_list* warp = nullptr;
+	int32 distance = AREA_SIZE;
+
 	//Search for warps within mob's viewing range.
 	map_foreachinallrange(mob_warpchase_sub, &md->bl,
-		md->db->range2, BL_NPC, target, &warp, &distance);
+		md->db->range2, type, target, &warp, &distance);
 
-	if (warp && unit_walktobl(&md->bl, &warp->bl, 1, 1))
+	if (warp != nullptr && unit_walktobl(&md->bl, warp, 0, 0))
 		return 1;
 	return 0;
 }
@@ -1815,7 +1891,7 @@ static bool mob_ai_sub_hard(struct mob_data *md, t_tick tick)
 				((((TBL_PC*)tbl)->state.gangsterparadise && !(mode&MD_STATUSIMMUNE)) ||
 				((TBL_PC*)tbl)->invincible_timer != INVALID_TIMER)
 		)) {	//No valid target
-			if (mob_warpchase(md, tbl))
+			if (mob_warpchase(md, tbl) > 0)
 				return true; //Chasing this target.
 			if (tbl && md->ud.walktimer != INVALID_TIMER && (!can_move || md->ud.walkpath.path_pos <= battle_config.mob_chase_refresh))
 				return true; //Walk at least "mob_chase_refresh" cells before dropping the target unless target is non-existent
@@ -2136,9 +2212,13 @@ bool mob_ai_sub_hard_attacktimer(mob_data &md, t_tick tick)
 	if (target == nullptr)
 		return false;
 
+	// Check for warp chase
+	if (mob_warpchase(&md, target) > 0)
+		return true;
+
 	// Monsters have a special visibility check at the end of their attack delay
 	// If they still have a target, but it is not visible, they drop the target
-	if (target != nullptr && !status_check_visibility(&md.bl, target, true))
+	if (!status_check_visibility(&md.bl, target, true))
 		return false;
 
 	// Go through the whole monster AI
@@ -2224,6 +2304,13 @@ static int32 mob_ai_sub_lazy(struct mob_data *md, va_list args)
 	if (DIFF_TICK(tick, md->last_skillcheck) < MOB_SKILL_INTERVAL)
 		return 0;
 
+	// Check for warp chase if there's still a target
+	if (md->target_id > 0) {
+		block_list* tbl = map_id2bl(md->target_id);
+		if (tbl != nullptr && mob_warpchase(md, tbl) > 0)
+			return 0;
+	}
+
 	if (md->master_id) {
 		if (!mob_is_spotted(md)) {
 			if (battle_config.slave_active_with_master == 0)
@@ -2238,14 +2325,9 @@ static int32 mob_ai_sub_lazy(struct mob_data *md, va_list args)
 		return 0;
 	}
 
-	if (md->ud.walktimer == INVALID_TIMER) {
-		// Because it is not unset when the mob finishes walking.
-		mob_setstate(*md, MSS_IDLE);
-		if (md->idle_event[0] && npc_event_do_id( md->idle_event, md->bl.id ) > 0) {
-			md->idle_event[0] = 0;
-			return 0;
-		}
-	}
+	// Remove target and set to idle state when movement was finished
+	// This also triggers the idle NPC event
+	mob_unlocktarget(md, tick);
 
 	if( DIFF_TICK(md->next_walktime,tick) < 0 && status_has_mode(&md->status,MD_CANMOVE) && unit_can_move(&md->bl) )
 	{

+ 0 - 7
src/map/unit.cpp

@@ -3043,13 +3043,6 @@ static int32 unit_attack_timer_sub(struct block_list* src, int32 tid, t_tick tic
 	   || !unit_can_attack(src, target->id) )
 		return 0; // Can't attack under these conditions
 
-	if( src->m != target->m ) {
-		if( src->type == BL_MOB && mob_warpchase((TBL_MOB*)src, target) )
-			return 1; // Follow up.
-
-		return 0;
-	}
-
 	if( ud->skilltimer != INVALID_TIMER && !(sd && pc_checkskill(sd,SA_FREECAST) > 0) )
 		return 0; // Can't attack while casting