From f107ff9cd2fefeae1fa51aad3db1f4b4fa8e9968 Mon Sep 17 00:00:00 2001 From: Nicholas Hastings Date: Sat, 27 Jun 2015 13:10:47 -0400 Subject: [PATCH 1/4] Expose FindMap/ResolveFuzzyMapName to plugins. --- core/HalfLife2.cpp | 76 +++++++++++++++++++++++------------- core/HalfLife2.h | 5 +++ core/smn_halflife.cpp | 11 ++++++ plugins/include/halflife.inc | 31 +++++++++++++++ plugins/testsuite/findmap.sp | 31 +++++++++++++++ 5 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 plugins/testsuite/findmap.sp diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 0e129448..8fb9c646 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -1208,53 +1208,73 @@ const char *CHalfLife2::GetEntityClassname(CBaseEntity *pEntity) return *(const char **)(((unsigned char *)pEntity) + offset); } -#if SOURCE_ENGINE >= SE_LEFT4DEAD -static bool ResolveFuzzyMapName(const char *fuzzyName, char *outFullname, int size) +#if SOURCE_ENGINE != SE_TF2 +enum eFindMapResult { + eFindMap_Found, + eFindMap_NotFound, + eFindMap_FuzzyMatch, + eFindMap_NonCanonical, + eFindMap_PossiblyAvailable +}; +#endif + +eFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) { +#if SOURCE_ENGINE >= SE_LEFT4DEAD + static char mapNameTmp[PLATFORM_MAX_PATH]; + g_SourceMod.Format(mapNameTmp, sizeof(mapNameTmp), "maps%c%s.bsp", PLATFORM_SEP_CHAR, pMapName); + if (filesystem->FileExists(mapNameTmp, "GAME")) + { + // If this is already an exact match, don't attempt to autocomplete it further (de_dust -> de_dust2). + // ... but still check that map file is actually valid. + // We check FileExists first to avoid console message about IsMapValid with invalid map. + return engine->IsMapValid(pMapName) == 0 ? eFindMap_NotFound : eFindMap_Found; + } + static ConCommand *pHelperCmd = g_pCVar->FindCommand("changelevel"); + + // This shouldn't happen. if (!pHelperCmd || !pHelperCmd->CanAutoComplete()) - return false; + { + return engine->IsMapValid(pMapName) == 0 ? eFindMap_NotFound : eFindMap_Found; + } static size_t helperCmdLen = strlen(pHelperCmd->GetName()); CUtlVector results; - pHelperCmd->AutoCompleteSuggest(fuzzyName, results); + pHelperCmd->AutoCompleteSuggest(pMapName, results); if (results.Count() == 0) - return false; + return eFindMap_NotFound; // Results come back as you'd see in autocomplete. (ie. "changelevel fullmapnamehere"), // so skip ahead to start of map path/name // Like the engine, we're only going to deal with the first match. - strncopy(outFullname, &results[0][helperCmdLen + 1], size); - - return true; -} + bool bExactMatch = Q_strcmp(pMapName, &results[0][helperCmdLen + 1]) == 0; + if (bExactMatch) + { + return eFindMap_Found; + } + else + { + strncopy(pMapName, &results[0][helperCmdLen + 1], nMapNameMax); + return eFindMap_FuzzyMatch; + } +#elif SOURCE_ENGINE == SE_TF2 + return engine->FindMap(pMapName, nMapNameMax); +#else + return engine->IsMapValid(pMapName) == 0 ? eFindMap_NotFound : eFindMap_Found; #endif +} bool CHalfLife2::IsMapValid(const char *map) { if (!map || !map[0]) return false; - - bool ret; -#if SOURCE_ENGINE == SE_TF2 - char szTmp[PLATFORM_MAX_PATH]; + + static char szTmp[PLATFORM_MAX_PATH]; strncopy(szTmp, map, sizeof(szTmp)); - ret = engine->FindMap(szTmp, sizeof(szTmp)) != eFindMap_NotFound; -#else - ret = engine->IsMapValid(map); -#if SOURCE_ENGINE >= SE_LEFT4DEAD - if (!ret) - { - static char szFuzzyName[PLATFORM_MAX_PATH]; - if (ResolveFuzzyMapName(map, szFuzzyName, sizeof(szFuzzyName))) - { - ret = engine->IsMapValid(szFuzzyName); - } - } -#endif -#endif // SE_TF2 - return ret; + + return FindMap(szTmp, sizeof(szTmp)) != eFindMap_NotFound; } diff --git a/core/HalfLife2.h b/core/HalfLife2.h index 2529f76b..114f4886 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -129,6 +129,10 @@ public: #endif }; +// See TF2 eiface.h for description. +// Not yet in other games, but eventually in others on same branch. +enum eFindMapResult : int; + class CHalfLife2 : public SMGlobalClass, public IGameHelpers @@ -174,6 +178,7 @@ public: //IGameHelpers const char *GetEntityClassname(edict_t *pEdict); const char *GetEntityClassname(CBaseEntity *pEntity); bool IsMapValid(const char *map); + eFindMapResult FindMap(char *pMapName, int nMapNameMax); public: void AddToFakeCliCmdQueue(int client, int userid, const char *cmd); void ProcessFakeCliCmdQueue(); diff --git a/core/smn_halflife.cpp b/core/smn_halflife.cpp index 41667aff..31404789 100644 --- a/core/smn_halflife.cpp +++ b/core/smn_halflife.cpp @@ -67,6 +67,16 @@ static cell_t IsMapValid(IPluginContext *pContext, const cell_t *params) return g_HL2.IsMapValid(map); } +static cell_t FindMap(IPluginContext *pContext, const cell_t *params) +{ + char *pMapname; + pContext->LocalToString(params[1], &pMapname); + + cell_t size = params[2]; + + return g_HL2.FindMap(pMapname, size); +} + static cell_t IsDedicatedServer(IPluginContext *pContext, const cell_t *params) { return engine->IsDedicatedServer(); @@ -626,6 +636,7 @@ REGISTER_NATIVES(halflifeNatives) {"GetRandomInt", GetRandomInt}, {"IsDedicatedServer", IsDedicatedServer}, {"IsMapValid", IsMapValid}, + {"FindMap", FindMap}, {"SetFakeClientConVar", SetFakeClientConVar}, {"SetRandomSeed", SetRandomSeed}, {"PrecacheModel", PrecacheModel}, diff --git a/plugins/include/halflife.inc b/plugins/include/halflife.inc index 1fe6edcd..5c736379 100644 --- a/plugins/include/halflife.inc +++ b/plugins/include/halflife.inc @@ -91,6 +91,26 @@ enum EngineVersion Engine_BlackMesa, /**< Black Mesa Multiplayer */ }; +enum FindMapResult +{ + // A direct match for this name was found + FindMap_Found, + // No match for this map name could be found. + FindMap_NotFound, + // A fuzzy match for this mapname was found and pMapName was updated to the full name. + // Ex: cp_dust -> cp_dustbowl + // Supported on many newer games. + FindMap_FuzzyMatch, + // A match for this map name was found, and the map name was updated to the canonical version of the + // name. + // Ex: workshop/1234 -> workshop/cp_qualified_name.ugc1234 + // Only supported on "Orangebox" games with workshop support. + FindMap_NonCanonical, + // No currently available match for this map name could be found, but it may be possible to load + // Only supported on "Orangebox" games with workshop support. + FindMap_PossiblyAvailable +}; + #define INVALID_ENT_REFERENCE 0xFFFFFFFF /** @@ -136,6 +156,17 @@ native GetRandomInt(nmin, nmax); */ native bool:IsMapValid(const String:map[]); +/** + * Returns whether a full or partial map name is found or can be resolved + * + * @param map Map path relative to maps/ folder, excluding .bsp extension. + * If result is FindMap_FuzzyMatch or FindMap_NonCanonical, + this will be updated to the full path. + * @param maxlen Maximum length to write to map var. + * @return Result of the find operation. Not all result types are supported on all games. + */ +native FindMapResult FindMap(char[] map, int maxlen); + /** * Returns whether the server is dedicated. * diff --git a/plugins/testsuite/findmap.sp b/plugins/testsuite/findmap.sp new file mode 100644 index 00000000..534e2b54 --- /dev/null +++ b/plugins/testsuite/findmap.sp @@ -0,0 +1,31 @@ +#include + +public void OnPluginStart() +{ + RegServerCmd("test_findmap", test_findmap); +} + +public Action test_findmap( int argc ) +{ + char mapName[PLATFORM_MAX_PATH]; + GetCmdArg(1, mapName, sizeof(mapName)); + + char resultName[16]; + switch (FindMap(mapName, sizeof(mapName))) + { + case FindMap_Found: + strcopy(resultName, sizeof(resultName), "Found"); + case FindMap_NotFound: + strcopy(resultName, sizeof(resultName), "NotFound"); + case FindMap_FuzzyMatch: + strcopy(resultName, sizeof(resultName), "FuzzyMatch"); + case FindMap_NonCanonical: + strcopy(resultName, sizeof(resultName), "NonCanonical"); + case FindMap_PossiblyAvailable: + strcopy(resultName, sizeof(resultName), "PossiblyAvailable"); + } + + PrintToServer("FindMap says %s - \"%s\"", resultName, mapName); + + return Plugin_Handled; +} \ No newline at end of file From c383f1dc43f5ab19be388e70883069412b0251c8 Mon Sep 17 00:00:00 2001 From: Nicholas Hastings Date: Sat, 27 Jun 2015 19:58:14 -0400 Subject: [PATCH 2/4] Fix compile errors on Clang by implementing own enum. --- core/HalfLife2.cpp | 28 +++++++++------------------- core/HalfLife2.h | 12 +++++++++--- core/smn_halflife.cpp | 2 +- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 8fb9c646..87745032 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -1208,17 +1208,7 @@ const char *CHalfLife2::GetEntityClassname(CBaseEntity *pEntity) return *(const char **)(((unsigned char *)pEntity) + offset); } -#if SOURCE_ENGINE != SE_TF2 -enum eFindMapResult { - eFindMap_Found, - eFindMap_NotFound, - eFindMap_FuzzyMatch, - eFindMap_NonCanonical, - eFindMap_PossiblyAvailable -}; -#endif - -eFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) +SMFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) { #if SOURCE_ENGINE >= SE_LEFT4DEAD static char mapNameTmp[PLATFORM_MAX_PATH]; @@ -1228,7 +1218,7 @@ eFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) // If this is already an exact match, don't attempt to autocomplete it further (de_dust -> de_dust2). // ... but still check that map file is actually valid. // We check FileExists first to avoid console message about IsMapValid with invalid map. - return engine->IsMapValid(pMapName) == 0 ? eFindMap_NotFound : eFindMap_Found; + return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found; } static ConCommand *pHelperCmd = g_pCVar->FindCommand("changelevel"); @@ -1236,7 +1226,7 @@ eFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) // This shouldn't happen. if (!pHelperCmd || !pHelperCmd->CanAutoComplete()) { - return engine->IsMapValid(pMapName) == 0 ? eFindMap_NotFound : eFindMap_Found; + return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found; } static size_t helperCmdLen = strlen(pHelperCmd->GetName()); @@ -1244,7 +1234,7 @@ eFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) CUtlVector results; pHelperCmd->AutoCompleteSuggest(pMapName, results); if (results.Count() == 0) - return eFindMap_NotFound; + return SMFindMapResult::NotFound; // Results come back as you'd see in autocomplete. (ie. "changelevel fullmapnamehere"), // so skip ahead to start of map path/name @@ -1254,17 +1244,17 @@ eFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) bool bExactMatch = Q_strcmp(pMapName, &results[0][helperCmdLen + 1]) == 0; if (bExactMatch) { - return eFindMap_Found; + return SMFindMapResult::Found; } else { strncopy(pMapName, &results[0][helperCmdLen + 1], nMapNameMax); - return eFindMap_FuzzyMatch; + return SMFindMapResult::FuzzyMatch; } #elif SOURCE_ENGINE == SE_TF2 - return engine->FindMap(pMapName, nMapNameMax); + return static_cast(engine->FindMap(pMapName, nMapNameMax)); #else - return engine->IsMapValid(pMapName) == 0 ? eFindMap_NotFound : eFindMap_Found; + return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found; #endif } @@ -1276,5 +1266,5 @@ bool CHalfLife2::IsMapValid(const char *map) static char szTmp[PLATFORM_MAX_PATH]; strncopy(szTmp, map, sizeof(szTmp)); - return FindMap(szTmp, sizeof(szTmp)) != eFindMap_NotFound; + return FindMap(szTmp, sizeof(szTmp)) != SMFindMapResult::NotFound; } diff --git a/core/HalfLife2.h b/core/HalfLife2.h index 114f4886..f48f687d 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -129,9 +129,15 @@ public: #endif }; -// See TF2 eiface.h for description. +// Corresponds to TF2's eFindMapResult in eiface.h // Not yet in other games, but eventually in others on same branch. -enum eFindMapResult : int; +enum class SMFindMapResult : cell_t { + Found, + NotFound, + FuzzyMatch, + NonCanonical, + PossiblyAvailable +}; class CHalfLife2 : public SMGlobalClass, @@ -178,7 +184,7 @@ public: //IGameHelpers const char *GetEntityClassname(edict_t *pEdict); const char *GetEntityClassname(CBaseEntity *pEntity); bool IsMapValid(const char *map); - eFindMapResult FindMap(char *pMapName, int nMapNameMax); + SMFindMapResult FindMap(char *pMapName, int nMapNameMax); public: void AddToFakeCliCmdQueue(int client, int userid, const char *cmd); void ProcessFakeCliCmdQueue(); diff --git a/core/smn_halflife.cpp b/core/smn_halflife.cpp index 31404789..b32cb9e2 100644 --- a/core/smn_halflife.cpp +++ b/core/smn_halflife.cpp @@ -74,7 +74,7 @@ static cell_t FindMap(IPluginContext *pContext, const cell_t *params) cell_t size = params[2]; - return g_HL2.FindMap(pMapname, size); + return static_cast(g_HL2.FindMap(pMapname, size)); } static cell_t IsDedicatedServer(IPluginContext *pContext, const cell_t *params) From 60ac7e23d0d9707687419a78158a2e3cca545af5 Mon Sep 17 00:00:00 2001 From: Nicholas Hastings Date: Sat, 27 Jun 2015 20:04:20 -0400 Subject: [PATCH 3/4] Updated FindMap function doc. --- plugins/include/halflife.inc | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/include/halflife.inc b/plugins/include/halflife.inc index 5c736379..b50a4f84 100644 --- a/plugins/include/halflife.inc +++ b/plugins/include/halflife.inc @@ -97,12 +97,11 @@ enum FindMapResult FindMap_Found, // No match for this map name could be found. FindMap_NotFound, - // A fuzzy match for this mapname was found and pMapName was updated to the full name. - // Ex: cp_dust -> cp_dustbowl - // Supported on many newer games. + // A fuzzy match for this map name was found. + // Ex: cp_dust -> cp_dustbowl, c1m1 -> c1m1_hotel + // Only supported for maps that the engine knows about. (This excludes workshop maps on Orangebox). FindMap_FuzzyMatch, - // A match for this map name was found, and the map name was updated to the canonical version of the - // name. + // A non-canonical match for this map name was found. // Ex: workshop/1234 -> workshop/cp_qualified_name.ugc1234 // Only supported on "Orangebox" games with workshop support. FindMap_NonCanonical, @@ -159,9 +158,9 @@ native bool:IsMapValid(const String:map[]); /** * Returns whether a full or partial map name is found or can be resolved * - * @param map Map path relative to maps/ folder, excluding .bsp extension. - * If result is FindMap_FuzzyMatch or FindMap_NonCanonical, - this will be updated to the full path. + * @param map Map name (usually same as map path relative to maps/ dir, + * excluding .bsp extension). If result is FindMap_FuzzyMatch + * or FindMap_NonCanonical, this will be updated to the full path. * @param maxlen Maximum length to write to map var. * @return Result of the find operation. Not all result types are supported on all games. */ From 80838af4a225a08cda020d9eb77b5023cf51909f Mon Sep 17 00:00:00 2001 From: Nicholas Hastings Date: Sun, 28 Jun 2015 09:48:01 -0400 Subject: [PATCH 4/4] Work around eFindMap_FuzzyMatch never actually being returned in TF2. --- core/HalfLife2.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 87745032..0f4695b3 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -1252,7 +1252,16 @@ SMFindMapResult CHalfLife2::FindMap(char *pMapName, int nMapNameMax) return SMFindMapResult::FuzzyMatch; } #elif SOURCE_ENGINE == SE_TF2 - return static_cast(engine->FindMap(pMapName, nMapNameMax)); + // Save off name passed in so that we can compare to output. + // There is a bug where eFindMap_FuzzyMap is never returned, even for fuzzy matches. + char *pOriginal = sm_strdup(pMapName); + SMFindMapResult res = static_cast(engine->FindMap(pMapName, nMapNameMax)); + bool bExactMatch = strcmp(pOriginal, pMapName) == 0; + delete [] pOriginal; + if (res == SMFindMapResult::Found && !bExactMatch) + return SMFindMapResult::FuzzyMatch; + else + return res; #else return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found; #endif