Explorar el Código

Improved Monster Search and Summon (#8880)

- When searching for a monster with the exact AegisName, it will now return this monster, even if it doesn't give Exp
- When searching for an array of monsters, it will now prioritize fully matching names first
- When summoning a monster using the exact AegisName, it will now summon exactly that monster instead of any with that name
- Small performance optimization when searching for a monster to summon
- Added helper function "strtoint32def" that converts a char pointer to an int32 and returns a default value on failure
  * This ensures that a parameter such as "1001Poring" does not result in monster 1001 (Scorpion) being summoned / returned
  * Using the solution via default value allows easy usage in a single line

---------

Co-authored-by: Lemongrass3110
Playtester hace 4 meses
padre
commit
aa9a19bc33
Se han modificado 5 ficheros con 84 adiciones y 28 borrados
  1. 15 0
      src/common/utilities.cpp
  2. 10 0
      src/common/utilities.hpp
  3. 13 3
      src/map/atcommand.cpp
  4. 24 12
      src/map/clif.cpp
  5. 22 13
      src/map/mob.cpp

+ 15 - 0
src/common/utilities.cpp

@@ -127,6 +127,21 @@ std::string rathena::util::string_left_pad(const std::string& original, char pad
 	return std::string( num - std::min( num, original.length() ), padding ) + original;
 }
 
+int32 rathena::util::strtoint32def(const char* str, int32 def) {
+	char* str_end{};
+	int32 result = std::strtol(str, &str_end, 10);
+
+	if (str_end != nullptr && *str_end != '\0') {
+		return def;
+	}
+
+	if (errno == ERANGE) {
+		return def;
+	}
+
+	return result;
+}
+
 constexpr char base62_dictionary[] = {
 	'0', '1', '2', '3', '4', '5', '6', '7',
 	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',

+ 10 - 0
src/common/utilities.hpp

@@ -320,6 +320,16 @@ namespace rathena {
 		*/
 		std::string string_left_pad(const std::string& original, char padding, size_t num);
 
+		/**
+		* Converts a string (char pointer) to an int32 value
+		* Returns the given default value when conversion fails or string is not a number
+		* @param str: String to convert
+		* @param def: Default value that should be returned on failure
+		*
+		* @return Converted int32 value
+		*/
+		int32 strtoint32def(const char* str, int32 def = 0);
+
 		/**
 		* Encode base10 number to base62. Originally by lututui
 		* @param val: Base10 Number

+ 13 - 3
src/map/atcommand.cpp

@@ -2273,8 +2273,17 @@ ACMD_FUNC(monster)
 		return -1;
 	}
 
-	if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number)
-		mob_id = mobdb_checkid(atoi(monster));
+	// If AegisName matches exactly, summon that monster
+	std::shared_ptr<s_mob_db> mob = mobdb_search_aegisname(monster);
+	if (mob != nullptr)
+		mob_id = mob->id;
+	else {
+		// Otherwise, search for monster with that ID or name
+		// Check for ID first as this is faster; if search string is not a number it will return 0
+		mob_id = util::strtoint32def(monster);
+		if (mob_id == 0 || mobdb_checkid(mob_id) == 0)
+			mob_id = mobdb_searchname(monster);
+	}
 
 	if (mob_id == 0) {
 		clif_displaymessage(fd, msg_txt(sd,40)); // Invalid monster ID or name.
@@ -7814,7 +7823,8 @@ ACMD_FUNC(mobinfo)
 	}
 
 	// If monster identifier/name argument is a name
-	if ((i = mobdb_checkid(strtoul(message, nullptr, 10))))
+	i = util::strtoint32def(message);
+	if (i != 0 && (i = mobdb_checkid(i)))
 	{
 		mob_ids[0] = i;
 		count = 1;

+ 24 - 12
src/map/clif.cpp

@@ -14787,7 +14787,6 @@ void clif_parse_GMRecall2(int32 fd, map_session_data* sd)
 void clif_parse_GM_Item_Monster(int32 fd, map_session_data *sd)
 {
 	struct s_packet_db* info = &packet_db[RFIFOW(fd,0)];
-	int32 mob_id = 0;
 	StringBuf command;
 	char *str;
 //#if PACKETVER >= 20131218
@@ -14831,17 +14830,30 @@ void clif_parse_GM_Item_Monster(int32 fd, map_session_data *sd)
 	}
 
 	// Monster
-	if ((mob_id = mobdb_searchname(str)) == 0)
-		mob_id = mobdb_checkid(atoi(str));
-
-	std::shared_ptr<s_mob_db> mob = mob_db.find(mob_id);
-
-	if( mob != nullptr ) {
-		StringBuf_Init(&command);
-		StringBuf_Printf(&command, "%cmonster %s", atcommand_symbol, mob->sprite.c_str());
-		is_atcommand(fd, sd, StringBuf_Value(&command), 1);
-		StringBuf_Destroy(&command);
-		return;
+	if (pc_can_use_command(sd, "monster", COMMAND_ATCOMMAND)) {
+		// If AegisName matches exactly, summon that monster (official behavior)
+		std::shared_ptr<s_mob_db> mob = mobdb_search_aegisname(str);
+		// Otherwise, search for monster with that ID or name (rAthena added behavior)
+		if (mob == nullptr) {
+			// Check for ID first as this is faster; if search string is not a number it will return 0
+			int32 mob_id = util::strtoint32def(str);
+			if (mob_id == 0 || mobdb_checkid(mob_id) == 0) {
+				mob_id = mobdb_searchname(str);
+
+				if (mob_id != 0) {
+					mob = mob_db.find(mob_id);
+				}
+			}
+			else
+				mob = mob_db.find(mob_id);
+		}
+		// Call corresponding atcommand when a valid monster was found
+		if (mob != nullptr) {
+			char command[CHAT_SIZE_MAX];
+			safesnprintf(command, sizeof(command), "%cmonster %s", atcommand_symbol, mob->sprite.c_str());
+			is_atcommand(fd, sd, command, 1);
+			return;
+		}
 	}
 }
 

+ 22 - 13
src/map/mob.cpp

@@ -269,13 +269,14 @@ static bool mobdb_searchname_sub(uint16 mob_id, const char * const str, bool ful
 	
 	if( mobdb_checkid(mob_id) <= 0 )
 		return false; // invalid mob_id (includes clone check)
+	if (strcmpi(mob->sprite.c_str(), str) == 0)
+		return true; // If AegisName matches exactly, always return true
 	if(!mob->base_exp && !mob->job_exp && !mob_has_spawn(mob_id))
 		return false; // Monsters with no base/job exp and no spawn point are, by this criteria, considered "slave mobs" and excluded from search results
 	if( full_cmp ) {
 		// str must equal the db value
 		if( strcmpi(mob->name.c_str(), str) == 0 || 
-			strcmpi(mob->jname.c_str(), str) == 0 || 
-			strcmpi(mob->sprite.c_str(), str) == 0 )
+			strcmpi(mob->jname.c_str(), str) == 0)
 			return true;
 	} else {
 		// str must be in the db value
@@ -316,31 +317,39 @@ std::shared_ptr<s_mob_db> mobdb_search_aegisname( const char* str ){
 }
 
 /*==========================================
- * Searches up to N matches. Returns number of matches [Skotlex]
+ * Searches up to N matches. Prioritizing full matches first. Returns the number of matches
  *------------------------------------------*/
-uint16 mobdb_searchname_array_(const char *str, uint16 * out, uint16 size, bool full_cmp)
+uint16 mobdb_searchname_array(const char *str, uint16 * out, uint16 size)
 {
 	uint16 count = 0;
 	const auto &mob_list = mob_db.getCache();
 
-	for( const auto &mob : mob_list ) {
+	// Full compare first
+	for (const auto& mob : mob_list) {
 		if (mob == nullptr)
 			continue;
-		if( mobdb_searchname_sub(mob->id, str, full_cmp) ) {
-			if( count < size )
+		if (mobdb_searchname_sub(mob->id, str, true)) {
+			out[count] = mob->id;
+			if (++count >= size)
+				return count;
+		}
+	}
+	// If there are still free places, check if search string is contained in a name but not equal
+	if (count < size) {
+		for (const auto& mob : mob_list) {
+			if (mob == nullptr)
+				continue;
+			if (mobdb_searchname_sub(mob->id, str, false) && !mobdb_searchname_sub(mob->id, str, true)) {
 				out[count] = mob->id;
-			count++;
+				if (++count >= size)
+					return count;
+			}
 		}
 	}
 
 	return count;
 }
 
-uint16 mobdb_searchname_array(const char *str, uint16 * out, uint16 size)
-{
-	return mobdb_searchname_array_(str, out, size, false);
-}
-
 /*==========================================
  * Id Mob is checked.
  *------------------------------------------*/