169 lines
4.4 KiB
C++
169 lines
4.4 KiB
C++
#include "pch.h"
|
|
#include "printmaps.h"
|
|
#include "convar.h"
|
|
#include "concommand.h"
|
|
#include "modmanager.h"
|
|
#include "tier0.h"
|
|
#include "r2engine.h"
|
|
|
|
#include <filesystem>
|
|
#include <regex>
|
|
|
|
AUTOHOOK_INIT()
|
|
|
|
enum class MapSource_t
|
|
{
|
|
VPK,
|
|
GAMEDIR,
|
|
MOD
|
|
};
|
|
|
|
const std::unordered_map<MapSource_t, const char*> PrintMapSource = {
|
|
{MapSource_t::VPK, "VPK"}, {MapSource_t::MOD, "MOD"}, {MapSource_t::GAMEDIR, "R2"}};
|
|
|
|
struct MapVPKInfo
|
|
{
|
|
std::string name;
|
|
std::string parent;
|
|
MapSource_t source;
|
|
};
|
|
|
|
// our current list of maps in the game
|
|
std::vector<MapVPKInfo> vMapList;
|
|
|
|
void RefreshMapList()
|
|
{
|
|
vMapList.clear();
|
|
|
|
// get modded maps
|
|
// TODO: could probably check mod vpks to get mapnames from there too?
|
|
for (auto& modFilePair : g_pModManager->m_ModFiles)
|
|
{
|
|
ModOverrideFile file = modFilePair.second;
|
|
if (file.m_Path.extension() == ".bsp" && file.m_Path.parent_path().string() == "maps") // only allow mod maps actually in /maps atm
|
|
{
|
|
MapVPKInfo& map = vMapList.emplace_back();
|
|
map.name = file.m_Path.stem().string();
|
|
map.parent = file.m_pOwningMod->Name;
|
|
map.source = MapSource_t::MOD;
|
|
}
|
|
}
|
|
|
|
// get maps in vpk
|
|
{
|
|
const int iNumRetailNonMapVpks = 1;
|
|
static const char* const ppRetailNonMapVpks[] = {
|
|
"englishclient_frontend.bsp.pak000_dir.vpk"}; // don't include mp_common here as it contains mp_lobby
|
|
|
|
// matches directory vpks, and captures their map name in the first group
|
|
static const std::regex rVpkMapRegex("englishclient_([a-zA-Z_]+)\\.bsp\\.pak000_dir\\.vpk", std::regex::icase);
|
|
|
|
for (fs::directory_entry file : fs::directory_iterator("./vpk"))
|
|
{
|
|
std::string pathString = file.path().filename().string();
|
|
|
|
bool bIsValidMapVpk = true;
|
|
for (int i = 0; i < iNumRetailNonMapVpks; i++)
|
|
{
|
|
if (!pathString.compare(ppRetailNonMapVpks[i]))
|
|
{
|
|
bIsValidMapVpk = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bIsValidMapVpk)
|
|
continue;
|
|
|
|
// run our map vpk regex on the filename
|
|
std::smatch match;
|
|
std::regex_match(pathString, match, rVpkMapRegex);
|
|
|
|
if (match.length() < 2)
|
|
continue;
|
|
|
|
std::string mapName = match[1].str();
|
|
// special case: englishclient_mp_common contains mp_lobby, so hardcode the name here
|
|
if (mapName == "mp_common")
|
|
mapName = "mp_lobby";
|
|
|
|
MapVPKInfo& map = vMapList.emplace_back();
|
|
map.name = mapName;
|
|
map.parent = pathString;
|
|
map.source = MapSource_t::VPK;
|
|
}
|
|
}
|
|
|
|
// get maps in game dir
|
|
for (fs::directory_entry file : fs::directory_iterator(fmt::format("{}/maps", R2::g_pModName)))
|
|
{
|
|
if (file.path().extension() == ".bsp")
|
|
{
|
|
MapVPKInfo& map = vMapList.emplace_back();
|
|
map.name = file.path().stem().string();
|
|
map.parent = "R2";
|
|
map.source = MapSource_t::GAMEDIR;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clang-format off
|
|
AUTOHOOK(_Host_Map_f_CompletionFunc, engine.dll + 0x161AE0,
|
|
int, __fastcall, (const char const* cmdname, const char const* partial, char commands[COMMAND_COMPLETION_MAXITEMS][COMMAND_COMPLETION_ITEM_LENGTH]))
|
|
// clang-format on
|
|
{
|
|
// don't update our map list often from this func, only refresh every 10 seconds so we avoid constantly reading fs
|
|
static double flLastAutocompleteRefresh = -999;
|
|
|
|
if (flLastAutocompleteRefresh + 10.0 < Tier0::Plat_FloatTime())
|
|
{
|
|
RefreshMapList();
|
|
flLastAutocompleteRefresh = Tier0::Plat_FloatTime();
|
|
}
|
|
|
|
// use a custom autocomplete func for all map loading commands
|
|
const int cmdLength = strlen(cmdname);
|
|
const char* query = partial + cmdLength;
|
|
const int queryLength = strlen(query);
|
|
|
|
int numMaps = 0;
|
|
for (int i = 0; i < vMapList.size() && numMaps < COMMAND_COMPLETION_MAXITEMS; i++)
|
|
{
|
|
if (!strncmp(query, vMapList[i].name.c_str(), queryLength))
|
|
{
|
|
strcpy(commands[numMaps], cmdname);
|
|
strncpy_s(
|
|
commands[numMaps++] + cmdLength,
|
|
COMMAND_COMPLETION_ITEM_LENGTH,
|
|
&vMapList[i].name[0],
|
|
COMMAND_COMPLETION_ITEM_LENGTH - cmdLength);
|
|
}
|
|
}
|
|
|
|
return numMaps;
|
|
}
|
|
|
|
void ConCommand_maps(const CCommand& args)
|
|
{
|
|
if (args.ArgC() < 2)
|
|
{
|
|
spdlog::info("Usage: maps <substring>");
|
|
spdlog::info("maps * for full listing");
|
|
return;
|
|
}
|
|
|
|
RefreshMapList();
|
|
|
|
for (MapVPKInfo& map : vMapList) // need to figure out a nice way to include parent path without making the formatting awful
|
|
if ((*args.Arg(1) == '*' && !args.Arg(1)[1]) || strstr(map.name.c_str(), args.Arg(1)))
|
|
spdlog::info("({}) {}", PrintMapSource.at(map.source), map.name);
|
|
}
|
|
|
|
void InitialiseMapsPrint()
|
|
{
|
|
AUTOHOOK_DISPATCH()
|
|
|
|
ConCommand* mapsCommand = R2::g_pCVar->FindCommand("maps");
|
|
mapsCommand->m_pCommandCallback = ConCommand_maps;
|
|
}
|