Implemented @warp suggestions.

This will display map name suggestions when attempting to @warp to a non-existent map.
To enable, turn on 'feature.warp_suggestions' in 'conf/battle/feature.conf'.

Signed-off-by: Euphy <euphy.raliel@rathena.org>
This commit is contained in:
Euphy 2014-02-13 21:17:57 -05:00
parent 222b773c20
commit f3777cce0e
7 changed files with 110 additions and 1 deletions

View File

@ -19,6 +19,10 @@ feature.search_stores: on
// Show suggestions when typing an incomplete command?
feature.atcommand_suggestions: off
// Warp suggestions (Note 1)
// Show suggestions when attempting to @warp to a non-existent map?
feature.warp_suggestions: off
// Banking (Note 1)
// Requires: 2013-07-24aRagexe or later
feature.banking: on

View File

@ -324,3 +324,28 @@ unsigned int get_percentage(const unsigned int A, const unsigned int B)
return (unsigned int)floor(result);
}
/**
* Calculates the Levenshtein distance of two strings.
* @author http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#C
*/
int levenshtein(const char *s1, const char *s2) {
unsigned int s1len, s2len, x, y, lastdiag, olddiag, i;
unsigned int *column;
s1len = strlen(s1);
s2len = strlen(s2);
column = malloc((s1len+1) * sizeof(unsigned int));
for (y = 1; y <= s1len; y++)
column[y] = y;
for (x = 1; x <= s2len; x++) {
column[0] = x;
for (y = 1, lastdiag = x-1; y <= s1len; y++) {
olddiag = column[y];
column[y] = min(min(column[y] + 1, column[y-1] + 1), lastdiag + (s1[y-1] == s2[x-1] ? 0 : 1));
lastdiag = olddiag;
}
}
i = column[s1len];
free(column);
return i;
}

View File

@ -31,4 +31,6 @@ extern uint32 MakeDWord(uint16 word0, uint16 word1);
uint32 date2version(int date);
int levenshtein(const char *s1, const char *s2);
#endif /* _UTILS_H_ */

View File

@ -11,7 +11,7 @@
/// Max number of items on @autolootid list
#define AUTOLOOTITEM_SIZE 10
/// The maximum number of atcommand suggestions
/// The maximum number of atcommand and @warp suggestions
#define MAX_SUGGESTIONS 10
/// Comment to disable the official walk path

View File

@ -87,6 +87,7 @@ static char atcmd_player_name[NAME_LENGTH];
static AtCommandInfo* get_atcommandinfo_byname(const char *name); // @help
static const char* atcommand_checkalias(const char *aliasname); // @help
static void atcommand_get_suggestions(struct map_session_data* sd, const char *name, bool atcommand); // @help
static void warp_get_suggestions(struct map_session_data* sd, const char *name); // @rura, @warp, @mapmove
// @commands (script-based)
struct atcmd_binding_data* get_atcommandbind_byname(const char* name) {
@ -370,6 +371,77 @@ ACMD_FUNC(send)
#undef GET_VALUE
}
/**
* Retrieves map name suggestions for a given string.
* This will first check if any map names contain the given string, and will
* print out MAX_SUGGESTIONS results if any maps are found.
* Otherwise, suggestions will be calculated through Levenshtein distance,
* and up to 5 of the closest matches will be printed.
*
* @author Euphy
*/
static void warp_get_suggestions(struct map_session_data* sd, const char *name) {
char buffer[512];
int i, count = 0;
if (strlen(name) < 2)
return;
// build the suggestion string
strcpy(buffer, msg_txt(sd, 205)); // Maybe you meant:
strcat(buffer, "\n");
// check for maps that contain string
for (i = 0; i < MAX_MAP_PER_SERVER; i++) {
if (count < MAX_SUGGESTIONS && strstr(map[i].name, name) != NULL) {
strcat(buffer, map[i].name);
strcat(buffer, " ");
if (++count >= MAX_SUGGESTIONS)
break;
}
}
// if no maps found, search by edit distance
if (!count) {
unsigned int distance[MAX_MAP_PER_SERVER][2];
int j, min;
// calculate Levenshtein distance for all maps
for (i = 0; i < MAX_MAP_PER_SERVER; i++) {
if (strlen(map[i].name) < 4) // invalid map name?
distance[i][0] = INT_MAX;
else {
distance[i][0] = levenshtein(map[i].name, name);
distance[i][1] = i;
}
}
// selection sort elements as needed
count = min(MAX_SUGGESTIONS, 5); // results past 5 aren't worth showing
for (i = 0; i < count; i++) {
min = i;
for (j = i+1; j < MAX_MAP_PER_SERVER; j++) {
if (distance[j][0] < distance[min][0])
min = j;
}
// print map name
if (distance[min][0] > 4) { // awful results, don't bother
if (!i) return;
break;
}
strcat(buffer, map[distance[min][1]].name);
strcat(buffer, " ");
// swap elements
swap(distance[i][0], distance[min][0]);
swap(distance[i][1], distance[min][1]);
}
}
clif_displaymessage(sd->fd, buffer);
}
/*==========================================
* @rura, @warp, @mapmove
*------------------------------------------*/
@ -397,6 +469,10 @@ ACMD_FUNC(mapmove)
if (!mapindex) { // m < 0 means on different server! [Kevin]
clif_displaymessage(fd, msg_txt(sd,1)); // Map not found.
if (battle_config.warp_suggestions_enabled)
warp_get_suggestions(sd, map_name);
return -1;
}

View File

@ -7369,6 +7369,7 @@ static const struct _battle_data {
{ "atcommand_enable_npc", &battle_config.atcommand_enable_npc, 0, 0, 100, },
{ "path_blown_halt", &battle_config.path_blown_halt, 1, 0, 1, },
{ "rental_mount_speed_boost", &battle_config.rental_mount_speed_boost, 25, 0, 100, },
{ "feature.warp_suggestions", &battle_config.warp_suggestions_enabled, 0, 0, 1, },
};
#ifndef STATS_OPT_OUT
/**

View File

@ -532,6 +532,7 @@ extern struct Battle_Config
int atcommand_enable_npc;
int path_blown_halt;
int rental_mount_speed_boost;
int warp_suggestions_enabled;
} battle_config;
void do_init_battle(void);