Console improvement & printing
s1 command system"needs work"
This commit is contained in:
Skull 2022-02-04 23:17:56 +02:00
parent a326a70a7f
commit 1b91a2deac
9 changed files with 720 additions and 377 deletions

View File

@ -1,13 +1,10 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "game_console.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
@ -18,10 +15,9 @@ namespace command
{
namespace
{
utils::hook::detour dvar_command_hook;
utils::hook::detour client_command_hook;
std::unordered_map<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
void main_handler()
@ -45,53 +41,78 @@ namespace command
handlers_sv[command](client_num, params);
}
dvar_command_hook.invoke<void>(client_num, a2);
client_command_hook.invoke<void>(client_num, a2);
}
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
// Shamelessly stolen from Quake3
// https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364
void parse_command_line()
{
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
{
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
cb(header);
}), &callback, includeOverride);
}
game::dvar_t* dvar_command_stub()
{
const params args;
if (args.size() <= 0)
static auto parsed = false;
if (parsed)
{
return 0;
return;
}
const auto dvar = game::Dvar_FindVar(args[0]);
static std::string comand_line_buffer = GetCommandLineA();
auto* command_line = comand_line_buffer.data();
if (dvar)
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4); //H1(1.4)
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0); //H1(1.4)
auto inq = false;
com_console_lines[0] = command_line;
com_num_console_lines = 0;
while (*command_line)
{
if (args.size() == 1)
if (*command_line == '"')
{
const auto current = game::Dvar_ValueToString(dvar, dvar->current, 0);
const auto reset = game::Dvar_ValueToString(dvar, dvar->reset, 0);
game_console::print(game_console::con_type_info, "\"%s\" is: \"%s\" default: \"%s\" hash: %i",
args[0], current, reset, dvar->hash);
game_console::print(game_console::con_type_info, " %s\n",
dvars::dvar_get_domain(dvar->type, dvar->domain).data());
inq = !inq;
}
//else
//{
// char command[0x1000] = { 0 };
// game::Dvar_GetCombinedString(command, 1);
// game::Dvar_SetCommand(dvar->name, command);
//}
// look for a + separating character
// if commandLine came from a file, we might have real line seperators
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
{
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
{
break;
}
com_console_lines[com_num_console_lines] = command_line + 1;
com_num_console_lines++;
*command_line = '\0';
}
command_line++;
}
parsed = true;
}
return dvar;
void parse_commandline_stub()
{
parse_command_line();
reinterpret_cast<void(*)()>(0x1400D8210)(); // mwr: test
}
}
void read_startup_variable(const std::string& dvar)
{
// parse the commandline if it's not parsed
parse_command_line();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4); //H1(1.4)
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0); //H1(1.4)
for (int i = 0; i < com_num_console_lines; i++)
{
game::Cmd_TokenizeString(com_console_lines[i]);
// only +set dvar value
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
{
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
}
return 0;
game::Cmd_EndTokenizeString();
}
}
@ -164,8 +185,16 @@ namespace command
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
}
void add_test(const char* name, void (*callback)())
{
static game::cmd_function_s cmd_test;
return game::Cmd_AddCommandInternal(name, callback, &cmd_test);
}
void add(const char* name, const std::function<void(const params&)>& callback)
{
static game::cmd_function_s cmd_test;
const auto command = utils::string::to_lower(name);
if (handlers.find(command) == handlers.end())
@ -207,73 +236,13 @@ namespace command
}
}
void parse_command_line()
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
{
static auto parsed = false;
if (parsed)
{
return;
}
static std::string comand_line_buffer = GetCommandLineA();
auto* command_line = comand_line_buffer.data();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4);
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0);
auto inq = false;
com_console_lines[0] = command_line;
com_num_console_lines = 0;
while (*command_line)
{
if (*command_line == '"')
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
{
inq = !inq;
}
// look for a + separating character
// if commandLine came from a file, we might have real line seperators
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
{
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
{
break;
}
com_console_lines[com_num_console_lines] = command_line + 1;
com_num_console_lines++;
*command_line = '\0';
}
command_line++;
}
parsed = true;
}
void parse_commandline_stub()
{
parse_command_line();
reinterpret_cast<void(*)()>(0x1400D8210)(); // mwr: test
}
void read_startup_variable(const std::string& dvar)
{
// parse the commandline if it's not parsed
parse_command_line();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4);
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0);
for (int i = 0; i < com_num_console_lines; i++)
{
game::Cmd_TokenizeString(com_console_lines[i]);
// only +set dvar value
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
{
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
}
game::Cmd_EndTokenizeString();
}
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
cb(header);
}), &callback, includeOverride);
}
class component final : public component_interface
@ -281,138 +250,447 @@ namespace command
public:
void post_unpack() override
{
//utils::hook::jump(game::base_address + 0x000000, dvar_command_stub, true); // H1
static game::cmd_function_s cmd_test;
game::Cmd_AddCommandInternal("quit", game::Com_Quit_f, &cmd_test);
/*game::Cmd_AddCommandInternal("connect", []() {
}, game::cmd_function_s);*/
//add_raw("quit", main_handler);
//add("quit", game::Com_Quit_f);
//add("startmap", [](const params& params)
//{
// const auto map = params.get(1);
// const auto exists = utils::hook::invoke<bool>(game::base_address + 0x412B50, map, 0);
// if (!exists)
// {
// game_console::print(game_console::con_type_error, "map '%s' not found\n", map);
// return;
// }
// // SV_SpawnServer
// utils::hook::invoke<void>(game::base_address + 0x6B3AA0, map, 0, 0, 0, 0);
//});
/*add("say", [](const params& params)
if (game::environment::is_sp())
{
chat::print(params.join(1));
});*/
add_commands_sp();
}
else
{
utils::hook::call(0x1400D728F, &parse_commandline_stub); // MWR TEST
//add("listassetpool", [](const params& params)
//{
// if (params.size() < 2)
// {
// game_console::print(game_console::con_type_info, "listassetpool <poolnumber>: list all the assets in the specified pool\n");
add_commands_mp();
}
add_commands_generic();
}
// for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
// {
// game_console::print(game_console::con_type_info, "%d %s\n", i, game::g_assetNames[i]);
// }
// }
// else
// {
// const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
private:
static void add_commands_generic()
{
add_test("quit", game::Com_Quit_f);
add("quit_hard", utils::nt::raise_hard_exception);
add("crash", []()
{
*reinterpret_cast<int*>(1) = 0;
});
// if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
// {
// game_console::print(game_console::con_type_info, "Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
// return;
// }
/*add("consoleList", [](const params& params)
{
const std::string input = params.get(1);
// game_console::print(game_console::con_type_info, "Listing assets in pool %s\n", game::g_assetNames[type]);
std::vector<std::string> matches;
game_console::find_matches(input, matches, false);
// enum_assets(type, [type](const game::XAssetHeader header)
// {
// const auto asset = game::XAsset{type, header};
// const auto* const asset_name = game::DB_GetXAssetName(&asset);
// //const auto entry = game::DB_FindXAssetEntry(type, asset_name);
// //TODO: display which zone the asset is from
// game_console::print(game_console::con_type_info, "%s\n", asset_name);
// }, true);
// }
//});
for (auto& match : matches)
{
auto* dvar = game::Dvar_FindVar(match.c_str());
if (!dvar)
{
console::info("[CMD]\t %s\n", match.c_str());
}
else
{
console::info("[DVAR]\t%s \"%s\"\n", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current, 0));
}
}
//add("commandDump", []()
//{
// printf("======== Start command dump =========\n");
console::info("Total %i matches\n", matches.size());
});
// game::cmd_function_s* cmd = (*game::cmd_functions);
add("dvarDump", [](const params& argument)
{
console::info("================================ DVAR DUMP ========================================\n");
std::string filename;
if (argument.size() == 2)
{
filename = "s1x/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
}
for (auto i = 0; i < *game::dvarCount; i++)
{
const auto dvar = game::sortedDvars[i];
if (dvar)
{
if (!filename.empty())
{
const auto line = std::format("{} \"{}\"\r\n", dvar->hash,
game::Dvar_ValueToString(dvar, dvar->current, 0));
utils::io::write_file(filename, line, i != 0);
}
console::info("%s \"%s\"\n", dvar->hash,
game::Dvar_ValueToString(dvar, dvar->current, 0));
}
}
console::info("\n%i dvars\n", *game::dvarCount);
console::info("================================ END DVAR DUMP ====================================\n");
});
// while (cmd)
// {
// if (cmd->name)
// {
// game_console::print(game_console::con_type_info, "%s\n", cmd->name);
// }
add("commandDump", [](const params& argument)
{
console::info("================================ COMMAND DUMP =====================================\n");
game::cmd_function_s* cmd = (*game::cmd_functions);
std::string filename;
if (argument.size() == 2)
{
filename = "s1x/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
}
int i = 0;
while (cmd)
{
if (cmd->name)
{
if (!filename.empty())
{
const auto line = std::format("{}\r\n", cmd->name);
utils::io::write_file(filename, line, i != 0);
}
console::info("%s\n", cmd->name);
i++;
}
cmd = cmd->next;
}
console::info("\n%i commands\n", i);
console::info("================================ END COMMAND DUMP =================================\n");
});
// cmd = cmd->next;
// }
add("listassetpool", [](const params& params)
{
if (params.size() < 2)
{
console::info("listassetpool <poolnumber> [filter]: list all the assets in the specified pool\n");
// printf("======== End command dump =========\n");
//});
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
{
console::info("%d %s\n", i, game::g_assetNames[i]);
}
}
else
{
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
{
console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
return;
}
/*add("god", []()
console::info("Listing assets in pool %s\n", game::g_assetNames[type]);
const std::string filter = params.get(2);
enum_assets(type, [type, filter](const game::XAssetHeader header)
{
const auto asset = game::XAsset{ type, header };
const auto* const asset_name = game::DB_GetXAssetName(&asset);
//const auto entry = game::DB_FindXAssetEntry(type, asset_name);
//TODO: display which zone the asset is from
if (!filter.empty() && !game_console::match_compare(filter, asset_name, false))
{
return;
}
console::info("%s\n", asset_name);
}, true);
}
});
add("vstr", [](const params& params)
{
if (params.size() < 2)
{
console::info("vstr <variablename> : execute a variable command\n");
return;
}
const auto* dvarName = params.get(1);
const auto* dvar = game::Dvar_FindVar(dvarName);
if (dvar == nullptr)
{
console::info("%s doesn't exist\n", dvarName);
return;
}
if (dvar->type != game::dvar_type::string
&& dvar->type != game::dvar_type::enumeration)
{
console::info("%s is not a string-based dvar\n", dvar->hash);
return;
}
execute(dvar->current.string);
});*/
}
static void add_commands_sp()
{
add("god", []()
{
if (!game::SV_Loaded())
{
return;
}
game::mp::g_entities[0].flags ^= game::FL_GODMODE;
game::sp::g_entities[0].flags ^= 1;
game::CG_GameMessage(0, utils::string::va("godmode %s",
game::g_entities[0].flags & game::FL_GODMODE
game::sp::g_entities[0].flags & 1
? "^2on"
: "^1off"));
});
/*add("demigod", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].flags ^= 2;
game::CG_GameMessage(0, utils::string::va("demigod mode %s",
game::sp::g_entities[0].flags & 2
? "^2on"
: "^1off"));
});
add("notarget", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].flags ^= 4;
game::CG_GameMessage(0, utils::string::va("notarget %s",
game::sp::g_entities[0].flags & 4
? "^2on"
: "^1off"));
});
add("noclip", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].client->flags ^= 1;
game::CG_GameMessage(0, utils::string::va("noclip %s",
game::sp::g_entities[0].client->flags & 1
? "^2on"
: "^1off"));
});
add("ufo", []()
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].client->flags ^= 2;
game::CG_GameMessage(0, utils::string::va("ufo %s",
game::sp::g_entities[0].client->flags & 2
? "^2on"
: "^1off"));
});
add("give", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(0);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(0, wp);
}
}
});
add("take", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(0);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
game::G_TakePlayerWeapon(ps, wp);
}
});*/
}
static void add_commands_mp()
{
//client_command_hook.create(0x1402E98F0, &client_command);
/*add_sv("god", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
//add("notarget", []()
//{
// if (!game::SV_Loaded())
// {
// return;
// }
game::mp::g_entities[client_num].flags ^= 1;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"godmode %s\"",
game::mp::g_entities[client_num].flags & 1
? "^2on"
: "^1off"));
});
// game::g_entities[0].flags ^= game::FL_NOTARGET;
// game::CG_GameMessage(0, utils::string::va("notarget %s",
// game::g_entities[0].flags & game::FL_NOTARGET
// ? "^2on"
// : "^1off"));
//});
add_sv("demigod", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
//add("noclip", []()
//{
// if (!game::SV_Loaded())
// {
// return;
// }
game::mp::g_entities[client_num].flags ^= 2;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"demigod mode %s\"",
game::mp::g_entities[client_num].flags & 2
? "^2on"
: "^1off"));
});
// game::g_entities[0].client->flags ^= 1;
// game::CG_GameMessage(0, utils::string::va("noclip %s",
// game::g_entities[0].client->flags & 1
// ? "^2on"
// : "^1off"));
//});
add_sv("notarget", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].flags ^= 4;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"notarget %s\"",
game::mp::g_entities[client_num].flags & 4
? "^2on"
: "^1off"));
});
add_sv("noclip", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].client->flags ^= 1;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"noclip %s\"",
game::mp::g_entities[client_num].client->flags & 1
? "^2on"
: "^1off"));
});
add_sv("ufo", [](const int client_num, const params_sv&)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].client->flags ^= 2;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"ufo %s\"",
game::mp::g_entities[client_num].client->flags & 2
? "^2on"
: "^1off"));
});
add_sv("give", [](const int client_num, const params_sv& params)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 2)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"You did not specify a weapon name\"");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(client_num, wp);
}
}
});
add_sv("take", [](const int client_num, const params_sv& params)
{
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 2)
{
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"You did not specify a weapon name\"");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
{
game::G_TakePlayerWeapon(ps, wp);
}
}); */
}
};
}
REGISTER_COMPONENT(command::component)
REGISTER_COMPONENT(command::component)

View File

@ -46,11 +46,11 @@ namespace console
void dispatch_message(const int type, const std::string& message)
{
/*if (rcon::message_redirect(message))
{
return;
}*/
game_console::print(type, message);
messages.access([&message](message_queue& msgs)
{
msgs.emplace(message);
});
}
void append_text(const char* text)
@ -172,7 +172,6 @@ namespace console
});
}
void log_messages()
{
/*while*/
@ -203,10 +202,6 @@ namespace console
{
OutputDebugStringA(message.data());
game::Conbuf_AppendText(message.data());
FILE* pFile = fopen("debug.log", "a");
fprintf(pFile, "%s\n", message.data());
fclose(pFile);
}
void runner()

View File

@ -416,9 +416,13 @@ namespace demonware
void post_unpack() override
{
/*
mwr has upgraded some networking methods and the gethostbyname import from winsock library is no longer used
gethostbyname has been replaced with getaddrinfo
btw, still you can't get online..
*/
utils::hook::jump(SELECT_VALUE(0x140610320, 0x1407400B0), bd_logger_stub); // H1MP64(1.4)
//singleplayer not supported so far.
if (game::environment::is_sp())
{
utils::hook::set<uint8_t>(0x1405FCA00, 0xC3); // bdAuthSteam H1(1.4)

View File

@ -1,15 +1,18 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game_console.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "game_console.hpp"
#include "command.hpp"
#include "scheduler.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include <utils/concurrency.hpp>
#include "version.hpp"
#define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18)
#define material_white game::Material_RegisterHandle("white")
@ -20,66 +23,68 @@ namespace game_console
{
struct console_globals
{
float x;
float y;
float left_x;
float font_height;
bool may_auto_complete;
char auto_complete_choice[64];
int info_line_count;
float x{};
float y{};
float left_x{};
float font_height{};
bool may_auto_complete{};
char auto_complete_choice[64]{};
int info_line_count{};
};
using output_queue = std::deque<std::string>;
struct ingame_console
{
char buffer[256];
int cursor;
int font_height;
int visible_line_count;
int visible_pixel_width;
float screen_min[2]; //left & top
float screen_max[2]; //right & bottom
console_globals globals;
bool output_visible;
int display_line_offset;
int line_count;
std::deque<std::string> output;
char buffer[256]{};
int cursor{};
int font_height{};
int visible_line_count{};
int visible_pixel_width{};
float screen_min[2]{}; //left & top
float screen_max[2]{}; //right & bottom
console_globals globals{};
bool output_visible{};
int display_line_offset{};
int line_count{};
utils::concurrency::container<output_queue, std::recursive_mutex> output{};
};
ingame_console con;
ingame_console con{};
std::int32_t history_index = -1;
std::deque<std::string> history;
std::deque<std::string> history{};
std::string fixed_input;
std::vector<std::string> matches;
std::string fixed_input{};
std::vector<std::string> matches{};
float color_white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float color_title[4] = { 0.9f, 0.9f, 0.5f, 1.0f };
void clear()
{
strncpy_s(con.buffer, "", 256);
strncpy_s(con.buffer, "", sizeof(con.buffer));
con.cursor = 0;
fixed_input = "";
matches.clear();
}
void print(const std::string& data)
void print_internal(const std::string& data)
{
if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count))
{
con.display_line_offset++;
}
con.output.push_back(data);
printf("%s\n", data.data());
if (con.output.size() > 1024)
{
con.output.pop_front();
}
con.output.access([&](output_queue& output)
{
if (con.visible_line_count > 0
&& con.display_line_offset == (output.size() - con.visible_line_count))
{
con.display_line_offset++;
}
output.push_back(data);
if (output.size() > 512)
{
output.pop_front();
}
});
}
void toggle_console()
@ -146,6 +151,8 @@ namespace game_console
color);
}
void draw_input_text_and_over(const char* str, float* color)
{
game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x,
@ -171,6 +178,7 @@ namespace game_console
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0);
}
bool match_compare(const std::string& input, const std::string& text, const bool exact)
{
if (exact && text == input) return true;
@ -230,7 +238,7 @@ namespace game_console
con.globals.left_x = con.screen_min[0] + 6.0f;
draw_input_box(1, dvars::con_inputBoxColor->current.vector);
draw_input_text_and_over("H1-Mod >", color_title);
draw_input_text_and_over("H1-Mod: " VERSION ">", color_title);
con.globals.left_x = con.globals.x;
con.globals.auto_complete_choice[0] = 0;
@ -240,8 +248,8 @@ namespace game_console
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0,
con.cursor, '|');
//game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x,
// con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0);
game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x,
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0);
// check if using a prefixed '/' or not
@ -328,19 +336,19 @@ namespace game_console
}
}
void draw_output_scrollbar(const float x, float y, const float width, const float height)
void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output)
{
const auto _x = (x + width) - 10.0f;
draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector);
auto _height = height;
if (con.output.size() > con.visible_line_count)
if (output.size() > con.visible_line_count)
{
const auto percentage = static_cast<float>(con.visible_line_count) / con.output.size();
const auto percentage = static_cast<float>(con.visible_line_count) / output.size();
_height *= percentage;
const auto remainingSpace = height - _height;
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (con.output.size() - con.
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (output.size() - con.
visible_line_count);
y = y + (remainingSpace * percentageAbove);
@ -349,42 +357,45 @@ namespace game_console
draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector);
}
void draw_output_text(const float x, float y)
void draw_output_text(const float x, float y, output_queue& output)
{
const auto offset = con.output.size() >= con.visible_line_count
const auto offset = output.size() >= con.visible_line_count
? 0.0f
: (con.font_height * (con.visible_line_count - con.output.size()));
: (con.font_height * (con.visible_line_count - output.size()));
for (auto i = 0; i < con.visible_line_count; i++)
{
y = console_font->pixelHeight + y;
const auto index = i + con.display_line_offset;
if (index >= con.output.size())
if (index >= output.size())
{
break;
}
game::R_AddCmdDrawText(con.output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
game::R_AddCmdDrawText(output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
0.0f, color_white, 0);
}
}
void draw_output_window()
{
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
con.output.access([](output_queue& output)
{
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
const auto x = con.screen_min[0] + 6.0f;
const auto y = (con.screen_min[1] + 32.0f) + 6.0f;
const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
const auto x = con.screen_min[0] + 6.0f;
const auto y = (con.screen_min[1] + 32.0f) + 6.0f;
const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x,
((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0);
game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x,
((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0);
draw_output_scrollbar(x, y, width, height);
draw_output_text(x, y);
draw_output_scrollbar(x, y, width, height, output);
draw_output_text(x, y, output);
});
}
void draw_console()
@ -408,7 +419,7 @@ namespace game_console
}
}
void print(const int type, const char* fmt, ...)
void print_internal(const char* fmt, ...)
{
char va_buffer[0x200] = { 0 };
@ -420,22 +431,38 @@ namespace game_console
const auto formatted = std::string(va_buffer);
const auto lines = utils::string::split(formatted, '\n');
for (auto& line : lines)
for (const auto& line : lines)
{
print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
print_internal(line);
}
}
bool console_char_event(const int localClientNum, const int key)
void print(const int type, const std::string& data)
{
if (key == '`' || key == '~' || key == '|' || key == '\\')
try
{
return false;
if (game::environment::is_dedi())
{
return;
}
}
catch (std::exception&)
{
return;
}
if (key > 127)
const auto lines = utils::string::split(data, '\n');
for (const auto& line : lines)
{
return true;
print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
}
}
bool console_char_event(const int local_client_num, const int key)
{
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
{
return false;
}
if (*game::keyCatchers & 1)
@ -444,13 +471,13 @@ namespace game_console
{
if (con.globals.may_auto_complete)
{
const auto firstChar = con.buffer[0];
const auto first_char = con.buffer[0];
clear();
if (firstChar == '\\' || firstChar == '/')
if (first_char == '\\' || first_char == '/')
{
con.buffer[0] = firstChar;
con.buffer[0] = first_char;
con.buffer[1] = '\0';
}
@ -473,9 +500,9 @@ namespace game_console
return false;
}
for (auto i = 0; i < clipboard.length(); i++)
for (size_t i = 0; i < clipboard.length(); i++)
{
console_char_event(localClientNum, clipboard[i]);
console_char_event(local_client_num, clipboard[i]);
}
return false;
@ -485,7 +512,11 @@ namespace game_console
{
clear();
con.line_count = 0;
con.output.clear();
con.display_line_offset = 0;
con.output.access([](output_queue& output)
{
output.clear();
});
history_index = -1;
history.clear();
@ -527,13 +558,18 @@ namespace game_console
return true;
}
void execute(const char* cmd)
bool console_key_event(const int local_client_num, const int key, const int down)
{
game::Cbuf_AddText(0, utils::string::va("%s \n", cmd));
}
if (key == game::keyNum_t::K_F10)
{
if (game::mp::svs_clients[local_client_num].header.state >= 1)
{
return false;
}
game::Cmd_ExecuteSingleCommand(local_client_num, 0, "lui_open menu_systemlink_join\n");
}
bool console_key_event(const int localClientNum, const int key, const int down)
{
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
{
if (!down)
@ -541,7 +577,7 @@ namespace game_console
return false;
}
if (game::playerKeys[localClientNum].keys[game::keyNum_t::K_SHIFT].down)
if (game::playerKeys[local_client_num].keys[game::keyNum_t::K_SHIFT].down)
{
if (!(*game::keyCatchers & 1))
toggle_console();
@ -613,24 +649,29 @@ namespace game_console
//scroll through output
if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP)
{
if (con.output.size() > con.visible_line_count && con.display_line_offset > 0)
{
con.display_line_offset--;
}
con.output.access([](output_queue& output)
{
if (output.size() > con.visible_line_count && con.display_line_offset > 0)
{
con.display_line_offset--;
}
});
}
else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN)
{
if (con.output.size() > con.visible_line_count && con.display_line_offset < (con.output.size() -
con.
visible_line_count))
{
con.display_line_offset++;
}
con.output.access([](output_queue& output)
{
if (output.size() > con.visible_line_count
&& con.display_line_offset < (output.size() - con.visible_line_count))
{
con.display_line_offset++;
}
});
}
if (key == game::keyNum_t::K_ENTER)
{
execute(fixed_input.data());
game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data()));
if (history_index != -1)
{
@ -644,7 +685,7 @@ namespace game_console
history.push_front(con.buffer);
print(""s.append(con.buffer));
console::info("]%s\n", con.buffer);
if (history.size() > 10)
{
@ -661,13 +702,31 @@ namespace game_console
return true;
}
class component final : public component_interface
{
public:
void post_load() override
{
if (game::environment::is_dedi())
{
return;
}
//scheduler::loop(draw_console, scheduler::pipeline::renderer);
}
void post_unpack() override
{
scheduler::loop(draw_console, scheduler::pipeline::renderer);
if (game::environment::is_dedi())
{
return;
}
// initialize our structs
con.cursor = 0;
con.visible_line_count = 0;
con.output_visible = false;
@ -683,70 +742,49 @@ namespace game_console
con.globals.info_line_count = 0;
strncpy_s(con.globals.auto_complete_choice, "", 64);
// //add clear command
//command::add("clear", [&]()
//{
//clear();
//con.line_count = 0;
//con.output.clear();
//history_index = -1;
//history.clear();
//});
// add clear command
command::add("clear", [&]()
{
clear();
con.line_count = 0;
con.display_line_offset = 0;
con.output.access([](output_queue& output)
{
output.clear();
});
history_index = -1;
history.clear();
});
// add our dvars
dvars::con_inputBoxColor = dvars::register_vec4(
"con_inputBoxColor",
0.2f, 0.2f, 0.2f, 0.9f,
dvars::con_inputBoxColor = dvars::register_vec4("con_inputBoxColor", 0.2f, 0.2f, 0.2f, 0.9f, 0.0f, 1.0f,
game::DVAR_FLAG_SAVED,
"color of console input box");
dvars::con_inputHintBoxColor = dvars::register_vec4("con_inputHintBoxColor", 0.3f, 0.3f, 0.3f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputHintBoxColor = dvars::register_vec4(
"con_inputHintBoxColor",
0.3f, 0.3f, 0.3f, 1.0f,
game::DVAR_FLAG_SAVED, "color of console input hint box");
dvars::con_outputBarColor = dvars::register_vec4("con_outputBarColor", 0.5f, 0.5f, 0.5f, 0.6f, 0.0f,
1.0f, game::DVAR_FLAG_SAVED,
"color of console output bar");
dvars::con_outputSliderColor = dvars::register_vec4("con_outputSliderColor", 1.0f, 0.8f, 0.0f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_outputBarColor = dvars::register_vec4(
"con_outputBarColor",
0.5f, 0.5f, 0.5f, 0.6f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_outputSliderColor = dvars::register_vec4(
"con_outputSliderColor",
0.9f, 0.9f, 0.5f, 1.00f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_outputWindowColor = dvars::register_vec4(
"con_outputWindowColor",
0.25f, 0.25f, 0.25f, 0.85f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputDvarMatchColor = dvars::register_vec4(
"con_inputDvarMatchColor",
1.0f, 1.0f, 0.8f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputDvarValueColor = dvars::register_vec4(
"con_inputDvarValueColor",
1.0f, 1.0f, 0.8f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
game::DVAR_FLAG_SAVED, "color of console output slider");
dvars::con_outputWindowColor = dvars::register_vec4("con_outputWindowColor", 0.25f, 0.25f, 0.25f, 0.85f,
0.0f,
1.0f, game::DVAR_FLAG_SAVED, "color of console output window");
dvars::con_inputDvarMatchColor = dvars::register_vec4("con_inputDvarMatchColor", 1.0f, 1.0f, 0.8f, 1.0f,
0.0f,
1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar");
dvars::con_inputDvarValueColor = dvars::register_vec4("con_inputDvarValueColor", 1.0f, 1.0f, 0.8f, 1.0f,
0.0f,
1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar value");
dvars::con_inputDvarInactiveValueColor = dvars::register_vec4(
"con_inputDvarInactiveValueColor",
0.8f, 0.8f, 0.8f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
dvars::con_inputCmdMatchColor = dvars::register_vec4(
"con_inputCmdMatchColor",
0.80f, 0.80f, 1.0f, 1.0f,
0.0f, 1.0f,
game::DVAR_FLAG_SAVED);
"con_inputDvarInactiveValueColor", 0.8f, 0.8f,
0.8f, 1.0f, 0.0f, 1.0f, game::DVAR_FLAG_SAVED,
"color of console inactive dvar value");
dvars::con_inputCmdMatchColor = dvars::register_vec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f,
0.0f,
1.0f, game::DVAR_FLAG_SAVED, "color of console matched command");
}
};
}

View File

@ -9,10 +9,12 @@ namespace game_console
con_type_info = 7
};
void print(int type, const char* fmt, ...);
//void print(int type, const char* fmt, ...);
bool console_char_event(int local_client_num, int key);
bool console_key_event(int local_client_num, int key, int down);
bool match_compare(const std::string& input, const std::string& text, const bool exact);
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact);
void execute(const char* cmd);
}

View File

@ -9,6 +9,7 @@
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <game/dvars.hpp>
#include <component/console.hpp>
namespace network
{
@ -268,7 +269,7 @@ namespace network
}
else
{
game_console::print(game_console::con_type_info, "%s\n", message.data());
console::info("%s\n", message.data()); //test
}
});
}

View File

@ -46,6 +46,26 @@ namespace dvars
}
}
namespace
{
template <typename T>
T* find_dvar(std::unordered_map<std::string, T>& map, const std::string& name)
{
auto i = map.find(name);
if (i != map.end())
{
return &i->second;
}
return nullptr;
}
bool find_dvar(std::unordered_set<std::string>& set, const std::string& name)
{
return set.find(name) != set.end();
}
}
std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain)
{
std::string str;

View File

@ -932,7 +932,7 @@ namespace game
struct dvar_t
{
int hash;
const char* hash;
unsigned int flags;
dvar_type type;
bool modified;

View File

@ -46,7 +46,7 @@ namespace game
WEAK symbol<bool()> CL_IsCgameInitialized{ 0x14017EE30, 0x140245650 }; // H1(1.4)
WEAK symbol<unsigned int(int)> Live_SyncOnlineDataFlags{ 0, 0x14059A700 }; // H1(1.4)
WEAK symbol<void()> Sys_Milliseconds{ 0x1403E2B10, 0x140513710 }; // H1(1.4)
WEAK symbol<int()> Sys_Milliseconds{ 0x1403E2B10, 0x140513710 }; // H1(1.4)
WEAK symbol<bool()> Sys_IsDatabaseReady2{ 0, 0x14042B090 }; // H1(1.4)
WEAK symbol<void(netadr_s* from)> SV_DirectConnect{ 0, 0x140480860 }; // H1(1.4)
@ -151,6 +151,11 @@ namespace game
WEAK symbol<void()> Sys_ShowConsole{ 0x1403E3B90, 0x140514910 }; // H1(1.4)
WEAK symbol<const char* (const char*)> UI_GetMapDisplayName{ 0, 0x140408CC0 }; // H1(1.4)
WEAK symbol<const char* (const char*)> UI_GetGameTypeDisplayName{ 0, 0x1404086A0 }; // H1(1.4)
WEAK symbol<const char* (const char* string)> UI_SafeTranslateString{ 0x140350430, 0x1405A2930 }; // H1(1.4)
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x140648FD4, 0x14089EED0 }; // H1(1.4)