Browse Source

Finding Nearby Free Cell Algorithm (#9107)

- Added the official algorithm to find a nearby free cell
  * It is now used instead of the copy of the client-sided algorithm
  * The client-sided algorithm is still available, but not used (the client uses it automatically)
- Monsters will no longer spread out when dropping their target unless the corresponding monster AI setting is set
- Monsters will now always stop when their target is in attack range, even if the target is not visible
  * Does not apply to looters targeting items
- The unit_stop_walking_soon algorithm now also works when called from within the walk timer event
  * The issue was that during the walk timer event walktimer is INVALID_TIMER even if the walkpath is not finished
  * The function will now only be used when going from loot to idle state (dropping chase target is unaffected)
- Fixed an issue that unit_stop_walking_soon sometimes didn't set the target coordinates correctly
- Fixed various issues that caused position lag when looters lost their item target
- Fixes #9106
Playtester 2 months ago
parent
commit
0ff59d88d6
4 changed files with 91 additions and 35 deletions
  1. 41 0
      src/map/map.cpp
  2. 1 0
      src/map/map.hpp
  3. 12 6
      src/map/mob.cpp
  4. 37 29
      src/map/unit.cpp

+ 41 - 0
src/map/map.cpp

@@ -1877,6 +1877,47 @@ bool map_closest_freecell(int16 m, int16 *x, int16 *y, int32 type, int32 flag)
 	return false;
 }
 
+/*==========================================
+ * Locates a nearby, walkable cell with no blocks of a certain type on it
+ * This one uses the official algorithm the find a free cell
+ * Returns true on success and sets x and y to cell found.
+ * Otherwise returns false and x and y are not changed.
+ * type: Types of block to count
+ * flag: 
+ *		0x1 - only count standing units
+ *------------------------------------------*/
+bool map_nearby_freecell(int16 m, int16 &x, int16 &y, int32 type, int32 flag)
+{
+	int16 tx = x;
+	int16 ty = y;
+
+	if(!map_count_oncell(m, tx, ty, type, flag))
+		return true; //Current cell is free
+
+	// One of two possible orders of direction processing is used at random
+	directions dir[2][DIR_MAX] = {
+		{DIR_NORTHEAST, DIR_EAST, DIR_SOUTHEAST, DIR_SOUTH, DIR_NORTH, DIR_SOUTHWEST, DIR_WEST, DIR_NORTHWEST},
+		{DIR_SOUTHWEST, DIR_WEST, DIR_NORTHWEST, DIR_NORTH, DIR_SOUTH, DIR_NORTHEAST, DIR_EAST, DIR_SOUTHEAST}
+	};
+	uint16 array_idx = rnd_value<decltype(array_idx)>(0, ARRAYLENGTH(dir) - 1);
+
+	// Try each direction in the selected array in order
+	for(uint8 dir_idx = 0; dir_idx < DIR_MAX; dir_idx++) {
+		int16 dx = dirx[dir[array_idx][dir_idx]];
+		int16 dy = diry[dir[array_idx][dir_idx]];
+
+		tx = x + dx;
+		ty = y + dy;
+		if (!map_count_oncell(m, tx, ty, type, flag) && map_getcell(m, tx, ty, CELL_CHKPASS)) {
+			x = tx;
+			y = ty;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 /*==========================================
  * Add an item in floor to location (m,x,y) and add restriction for those who could pickup later
  * NB : If charids are null their no restriction for pickup

+ 1 - 0
src/map/map.hpp

@@ -1137,6 +1137,7 @@ struct skill_unit *map_find_skill_unit_oncell(struct block_list *,int16 x,int16
 int32 map_get_new_object_id(void);
 int32 map_search_freecell(struct block_list *src, int16 m, int16 *x, int16 *y, int16 rx, int16 ry, int32 flag, int32 tries = 50);
 bool map_closest_freecell(int16 m, int16 *x, int16 *y, int32 type, int32 flag);
+bool map_nearby_freecell(int16 m, int16 &x, int16 &y, int32 type, int32 flag);
 //
 int32 map_quit(map_session_data *);
 // npc

+ 12 - 6
src/map/mob.cpp

@@ -1548,9 +1548,6 @@ int32 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)
@@ -1562,9 +1559,12 @@ int32 mob_unlocktarget(struct mob_data *md, t_tick tick)
 		if (md->ud.walktimer == INVALID_TIMER && md->idle_event[0] && npc_event_do_id(md->idle_event, md->bl.id) > 0)
 			md->idle_event[0] = 0;
 		break;
+	case MSS_LOOT:
+		// Looters that lost their target stop after 0.5-1.5 cells moved
+		unit_stop_walking_soon(md->bl);
+		[[fallthrough]];
 	default:
 		unit_stop_attack( &md->bl );
-		unit_stop_walking_soon(md->bl); //Stop chasing.
 		mob_setstate(*md, MSS_IDLE);
 		if(battle_config.mob_ai&0x8) //Walk instantly after dropping target
 			md->next_walktime = tick+rnd()%1000;
@@ -1580,7 +1580,7 @@ int32 mob_unlocktarget(struct mob_data *md, t_tick tick)
 	md->ud.target_to = 0;
 	
 	if (!md->ud.state.ignore_cell_stack_limit && battle_config.official_cell_stack_limit > 0
-		&& (chasestate || battle_config.mob_ai & 0x8)
+		&& (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);
 	}
@@ -1687,6 +1687,12 @@ int32 mob_randomwalk(struct mob_data *md,t_tick tick)
 	return 1;
 }
 
+/**
+ * Makes a monster move to the closest warp if corresponding configs are set
+ * @param md: Mob that should move to the warp
+ * @param target: Target the mob should follow
+ * @return 0: Do not warp chase, 1: Do warp chase, 2: Already warp chasing
+ */
 int32 mob_warpchase(struct mob_data *md, struct block_list *target)
 {
 	struct npc_data *warp = nullptr;
@@ -1699,7 +1705,7 @@ int32 mob_warpchase(struct mob_data *md, struct block_list *target)
 
 	if (md->ud.walktimer != INVALID_TIMER &&
 		map_getcell(md->bl.m,md->ud.to_x,md->ud.to_y,CELL_CHKNPC))
-		return 1; //Already walking to a warp.
+		return 2; //Already walking to a warp.
 
 	//Search for warps within mob's viewing range.
 	map_foreachinallrange(mob_warpchase_sub, &md->bl,

+ 37 - 29
src/map/unit.cpp

@@ -99,32 +99,33 @@ bool unit_update_chase(block_list& bl, t_tick tick, bool fullcheck) {
 		tbl = map_id2bl(ud->target_to);
 
 	// Reached destination, start attacking
-	if (tbl != nullptr && tbl->m == bl.m && ud->walkpath.path_pos > 0 && check_distance_bl(&bl, tbl, ud->chaserange) && status_check_visibility(&bl, tbl, false)) {
-		ud->to_x = bl.x;
-		ud->to_y = bl.y;
-		ud->target_to = 0;
-		// Aegis uses one before every attack, we should
-		// only need this one for syncing purposes.
-		clif_fixpos(bl);
+	if (tbl != nullptr && tbl->type != BL_ITEM && tbl->m == bl.m && ud->walkpath.path_pos > 0 && check_distance_bl(&bl, tbl, ud->chaserange)) {
+		// We need to make sure the walkpath is cleared here so a monster doesn't continue walking in case it unlocks its target
+		unit_stop_walking(&bl, USW_FIXPOS|USW_FORCE_STOP|USW_RELEASE_TARGET);
 		if (ud->state.attack_continue)
 			unit_attack(&bl, tbl->id, ud->state.attack_continue);
 		return true;
 	}
 	// Cancel chase
 	else if (tbl == nullptr || (fullcheck && !status_check_visibility(&bl, tbl, (bl.type == BL_MOB)))) {
-		ud->to_x = bl.x;
-		ud->to_y = bl.y;
-
-		if (tbl != nullptr && bl.type == BL_MOB) {
+		// Looted items will have no tbl but target ID is still set, that's why we need to check for the ID here
+		if (ud->target_to != 0 && bl.type == BL_MOB) {
 			mob_data& md = reinterpret_cast<mob_data&>(bl);
-			if (mob_warpchase(&md, tbl))
-				return true;
+			if (tbl != nullptr) {
+				int32 warp = mob_warpchase(&md, tbl);
+				// Do warp chase
+				if (warp == 1)
+					return true;
+				// Continue moving to warp
+				else if (warp == 2)
+					return false;
+			}
 			// Make sure monsters properly unlock their target, but still continue movement
 			mob_unlocktarget(&md, tick);
 			return false;
 		}
 
-		ud->target_to = 0;
+		unit_stop_walking(&bl, USW_FIXPOS|USW_FORCE_STOP|USW_RELEASE_TARGET);
 		return true;
 	}
 	// Update chase path
@@ -180,14 +181,9 @@ bool unit_walktoxy_nextcell(block_list& bl, bool sendMove, t_tick tick) {
 			sendMove = true;
 		}
 		if (ud->target_to != 0) {
-			int16 tx = ud->to_x;
-			int16 ty = ud->to_y;
 			// Monsters update their chase path one cell before reaching their final destination
 			if (unit_update_chase(bl, tick, (ud->walkpath.path_pos == ud->walkpath.path_len - 1)))
 				return true;
-			// Continue moving, restore to_x and to_y
-			ud->to_x = tx;
-			ud->to_y = ty;
 		}
 	}
 
@@ -807,7 +803,7 @@ int32 unit_walktoxy( struct block_list *bl, int16 x, int16 y, unsigned char flag
 	if (ud == nullptr)
 		return 0;
 
-	if ((flag&8) && !map_closest_freecell(bl->m, &x, &y, BL_CHAR|BL_NPC, 1)) //This might change x and y
+	if ((flag&8) && !map_nearby_freecell(bl->m, x, y, BL_CHAR|BL_NPC, 1)) //This might change x and y
 		return 0;
 
 	walkpath_data wpd = { 0 };
@@ -1492,19 +1488,31 @@ void unit_stop_walking_soon(struct block_list& bl)
 	if (ud == nullptr)
 		return;
 
-	if (ud->walktimer == INVALID_TIMER)
-		return;
-
-	if (ud->walkpath.path_pos + 1 >= ud->walkpath.path_len)
+	// Less than 1 cell left to walk
+	// We need to make sure to_x and to_y are reset to match the walk path in case they were modified
+	if (ud->walkpath.path_pos + 1 >= ud->walkpath.path_len) {
+		ud->to_x = bl.x;
+		ud->to_y = bl.y;
+		// One more cell to move
+		if (ud->walkpath.path_pos + 1 == ud->walkpath.path_len) {
+			ud->to_x += dirx[ud->walkpath.path[ud->walkpath.path_pos]];
+			ud->to_y += diry[ud->walkpath.path[ud->walkpath.path_pos]];
+		}
 		return;
+	}
 
-	const struct TimerData* td = get_timer(ud->walktimer);
+	// Get how much percent we traversed on the timer
+	// If timer is invalid, we are exactly on cell center (0% traversed)
+	double cell_percent = 0.0;
+	if (ud->walktimer != INVALID_TIMER) {
+		const struct TimerData* td = get_timer(ud->walktimer);
 
-	if (td == nullptr)
-		return;
+		if (td == nullptr)
+			return;
 
-	// Get how much percent we traversed on the timer
-	double cell_percent = 1.0 - ((double)DIFF_TICK(td->tick, gettick()) / (double)td->data);
+		// Get how much percent we traversed on the timer
+		cell_percent = 1.0 - ((double)DIFF_TICK(td->tick, gettick()) / (double)td->data);
+	}
 
 	int16 ox = bl.x, oy = bl.y; // Remember original x and y coordinates
 	int16 path_remain = 1; // Remaining path to walk