← Back to file list Raw

src/CCommandProcessor.cpp

#include "CCommandProcessor.h"
#include "CConVarManager.h"
#include "CEngine.h"
#include "U_String.h"
#include "CLogger.h"
#include "CWrapable.h"
#include <sstream>
#include <chrono>
#include <thread>
#include <unordered_map>
std::unordered_map<std::string, CCommand::CType> CmdStringTypes =
{
{ "client", CCommand::CType::Client },
{ "serverpreprocess", CCommand::CType::ServerPreProcess},
{ "server", CCommand::CType::Server }
};
std::unordered_map<std::string, CCommand::CmdRight> CmdStringRights =
{
{ "client", CCommand::CmdRight::Client },
{ "clientonly", CCommand::CmdRight::ClientOnly },
{ "admin", CCommand::CmdRight::Admin },
{ "adminonly", CCommand::CmdRight::AdminOnly },
{ "console", CCommand::CmdRight::Console }
};
std::unordered_map<std::string, CCommand::CmdStatus> CmdStringStatuses =
{
{ "ok", CCommand::CmdStatus::OK },
{ "incorrect", CCommand::CmdStatus::Incorrect },
{ "error", CCommand::CmdStatus::Error },
{ "server", CCommand::CmdStatus::Server }
};
bool CCommandProcessor::V_ScriptInit(std::shared_ptr<sol::state> _state, sol::table table)
{
if(_state->get<sol::object>("command") == sol::lua_nil)
{
sol::table command = _state->create_named_table("command");
sol::table type = command.create_named("type");
sol::table rights = command.create_named("rights");
sol::table status = command.create_named("status");
for(auto& kv : CmdStringTypes) { type.set(kv.first, int(kv.second)); }
for(auto& kv : CmdStringRights) { rights.set(kv.first, int(kv.second)); }
for(auto& kv : CmdStringStatuses) { status.set(kv.first, int(kv.second)); }
}
table.set_function("register", [this](const std::string& cmdname, int cmdtype, int cmdrights, sol::function cmdfunc) -> int
{
if(cmdname.empty() || Commands.IsCommandPresent(cmdname)) { return 1; }
if(!cmdfunc.valid()) { return 2; }
Commands.CreateLuaCommand(cmdname, (CCommand::CType)cmdtype, (CCommand::CmdRight)cmdrights, cmdfunc);
return 0;
});
table.set_function("process", [this](const std::wstring& cmdline) -> void //TODO maybe return statuses + ability to specify sender
{
ProcessCommandLine(cmdline, CCommandSender::Self);
});
return true;
}
void CCommandProcessor::AddCommandToQueue(const std::wstring& cmd)
{
std::lock_guard lock(Mutex);
Queue.push_back(cmd);
History.SentCommand(cmd);
}
void CCommandProcessor::ProcessQueue()
{
std::vector<std::wstring> queue_copy;
{
std::lock_guard lock(Mutex);
if(Queue.empty()) { return; }
queue_copy.swap(Queue);
}
bool echo = true;
COMPONENT_CALL_GET(echo, CConVarManager, GetConVarValue<bool>("con.echo"));
for (auto& cmd : queue_copy)
{
if(echo) { COMPONENT_CALL(CLogger, Outln(L"> " + cmd)); }
ProcessCommandLine(cmd, CCommandSender::Self);
}
}
void CCommandProcessor::V_Update()
{
ProcessQueue();
}
std::wstring CCommandProcessor::ProcessSpecialChars(const std::wstring& cmdline)
{
std::wstring line = cmdline;
StringUtils::replace<std::wstring>(line, L"\\", L"\\\\");
StringUtils::replace<std::wstring>(line, L"\n", L"\\n");
StringUtils::replace<std::wstring>(line, L"\r", L"\\r");
StringUtils::replace<std::wstring>(line, L"\"", L"\\\"");
StringUtils::replace<std::wstring>(line, L";", L"\\;");
return line;
}
CCommand::CmdStatus CCommandProcessor::ProcessSingleCommand(const std::string& cmd, const CCommandArgsWrapper& args, const CCommandSender& sender)
{
if(!m_customProcs.empty()) { m_customProcs.top()->ProcessSingleCommand(cmd, args, sender); return CCommand::CmdStatus::OK; }
CConVarManager::CVarNode* cvar = nullptr;
COMPONENT_CALL_GET(cvar, CConVarManager, Root.GetNode(cmd));
auto italias = Aliases.find(cmd);
if(italias != Aliases.end()) //TODO aliases only for self sender (console)
{
std::wstring cmdline = italias->second;
for(size_t i = 0; i < args.size(); i++)
{
std::wstring thisArg = L"%ARG" + std::to_wstring(i) + L"%";
StringUtils::replace(cmdline, thisArg, args.at(i));
}
ProcessCommandLine(cmdline, sender);
return CCommand::CmdStatus::OK;
}
bool exist = Commands.IsCommandPresent(cmd);
if(cvar && !exist)
{
if(args.empty())
{
std::wstringstream ws;
ws << StringUtils::StrToWstr(cmd) << L" is a node " << (cvar->Value.Wrapable ? (L"with value \"" + cvar->Value.Wrapable->GetValueStr() + L"\"") : L"without value") << '\n';
COMPONENT_CALL(CLogger, Out(ws.str()));
}
else
{
std::wstring val = args.Merge(0, args.size() - 1);
if(cvar->Value.Wrapable)
{
cvar->Value.Wrapable->SetValueStr(val);
}
}
return CCommand::CmdStatus::OK;
}
if (!exist) { return CCommand::CmdStatus::Incorrect; }
auto& command = Commands.GetCommand(cmd);
//TODO command/user rights check
CCommand::CmdStatus status = command->CallHandler(args, sender);
return status;
}
std::vector<std::vector<std::wstring>> CCommandProcessor::PreProcessString(const std::wstring& cmdline) //TODO rewrite it
{
if (StringUtils::IsEmpty(cmdline)) { return {}; }
bool inString = false;
std::vector<std::wstring> cmdlines;
std::wstring procString = cmdline;
std::vector<std::vector<std::wstring>> ret;
wchar_t reat = procString.front();
while (reat == L' ')
{
procString.erase(procString.begin());
reat = procString.front();
}
reat = procString.back();
while (reat == L' ')
{
procString.erase(procString.begin() + (procString.size() - 1));
reat = procString.back();
}
for (size_t i = 0; i < procString.size(); ++i)
{
if ((i > 0 && procString.at(i) == L'\"' && procString.at(i - 1) != L'\\') || (i == 0 && procString.at(0) == L'\"'))
{
inString = !inString;
continue;
}
if (i > 0 && !inString && (procString.at(i) == L' ' || procString.at(i) == L'\t') && (procString.at(i - 1) == L' ' || procString.at(i - 1) == L'\t'))
{
procString.erase(procString.begin() + (i - 1));
i--;
}
}
inString = false;
for (size_t i = 0; i < procString.size(); ++i)
{
if ((i > 0 && procString.at(i) == L'\"' && procString.at(i - 1) != L'\\') || (i == 0 && procString.at(0) == L'\"'))
{
inString = !inString;
continue;
}
if ((i + 1) < procString.size() && (procString.at(i) == L' ' || procString.at(i) == L'\t') && procString.at(i + 1) == L';')
{
procString.erase(procString.begin() + i);
i--;
}
}
inString = false;
std::wstring curCmd;
for (size_t i = 0; i < procString.size(); ++i)
{
if ((i > 0 && procString.at(i) == L'\"' && procString.at(i - 1) != L'\\') || (i == 0 && procString.at(0) == L'\"'))
{
inString = !inString;
}
if (procString.at(i) == L';' && ((i == 0) || (procString.at(i - 1) != L'\\')) && !inString)
{
procString.erase(procString.begin() + i);
if (!StringUtils::IsEmpty(curCmd))
{
cmdlines.push_back(curCmd);
//Log::Instance() << "cmdlines.push_back(\"" << curCmd << "\")\n";
curCmd.clear();
}
//continue; TODO hack (when commented)
}
curCmd += procString.at(i);
if ((i + 1) >= procString.size())
{
if (!StringUtils::IsEmpty(curCmd))
{
cmdlines.push_back(curCmd);
//Log::Instance() << "cmdlines.push_back2(\"" << curCmd << "\")\n";
curCmd.clear();
}
}
}
for (std::wstring& s : cmdlines)
{
bool inStr = false;
std::wstring curline;
std::vector<std::wstring> toadd;
for (size_t i = 0; i < s.size(); ++i)
{
if ((i > 0 && s.at(i) == '\"' && s.at(i - 1) != '\\') || (i == 0 && s.at(0) == '\"'))
{
inStr = !inStr;
if ((i + 1) >= s.size())
{
toadd.push_back(curline);
curline.clear();
}
continue;
}
if ((s.at(i) == ' ' || s.at(i) == '\t') && !inStr)
{
if (!StringUtils::IsEmpty(curline))
{
toadd.push_back(curline);
curline.clear();
}
continue;
}
curline += s.at(i);
if ((i + 1) >= s.size())
{
if (!StringUtils::IsEmpty(curline))
{
toadd.push_back(curline);
curline.clear();
}
}
}
for (std::wstring& ins : toadd)
{
StringUtils::replace<std::wstring>(ins, L"\\\\", L"\\");
StringUtils::replace<std::wstring>(ins, L"\\n", L"\n");
StringUtils::replace<std::wstring>(ins, L"\\r", L"\r");
StringUtils::replace<std::wstring>(ins, L"\\\"", L"\"");
StringUtils::replace<std::wstring>(ins, L"\\;", L";");
}
if (!toadd.empty())
{
ret.push_back(toadd);
}
}
return ret;
}
void CCommandProcessor::ProcessCommandLine(const std::wstring& cmdline, const CCommandSender& sender) //TODO maybe return vector<CmdStatus>
{
std::vector<std::vector<std::wstring>> commands = PreProcessString(cmdline);
for (std::vector<std::wstring>& args : commands)
{
if (args.empty()) { continue; }
std::wstring wcommand = args.front();
std::string command = StringUtils::WstrToStr(args.front());
args.erase(args.begin());
CCommand::CmdStatus status = ProcessSingleCommand(command, args, sender);
if (status == CCommand::CmdStatus::Incorrect)
{
//TODO status notification
}
}
}
CCommandProcessor::CCommandsHistory::CCommandsHistory()
{
Commands.reserve(64);
COMPONENT_CALL(CConVarManager, AddConVar("con.history.max", new CWrapable<unsigned int>(64)));
COMPONENT_CALL(CConVarManager, AddConVar("con.history.edit.keepindex", new CWrapable<unsigned int>(1)));
COMPONENT_CALL(CConVarManager, AddConVar("con.history.edit.replace", new CWrapable<unsigned int>(0)));
}
void CCommandProcessor::CCommandsHistory::Add(const std::wstring& cmd)
{
if (cmd.empty()) { return; }
unsigned int historyMax = 64;
COMPONENT_CALL_GET(historyMax, CConVarManager, GetConVarValue<unsigned int>("con.history.max", 64));
if (Commands.size() + 1 >= historyMax)
{
Commands.erase(Commands.begin());
}
Commands.push_back(cmd);
}
std::wstring CCommandProcessor::CCommandsHistory::GetNext()
{
if (Commands.empty()) { return CurrentInput; }
if (Index + 1 < Commands.size()) { Index++; }
return *(Commands.rbegin() + Index);
}
std::wstring CCommandProcessor::CCommandsHistory::GetPrev()
{
if (Commands.empty()) { return CurrentInput; }
if (Index - 1 >= -1) { Index--; }
if (Index < 0) { return CurrentInput; }
return *(Commands.rbegin() + Index);
}
void CCommandProcessor::CCommandsHistory::SentCommand(const std::wstring& cmd, bool add)
{
Index = -1;
CurrentInput.clear();
if (add)
{
Add(cmd);
}
}
void CCommandProcessor::CCommandsHistory::EditedCommandAsync(const std::wstring& cmd)
{
std::lock_guard lock(Mutex);
EditedCommand(cmd);
}
void CCommandProcessor::CCommandsHistory::EditedCommand(const std::wstring& cmd)
{
bool keepindex = true, editreplace = false;
COMPONENT_CALL_GET(keepindex, CConVarManager, GetConVarValue<unsigned int>("con.history.edit.keepindex", 1));
COMPONENT_CALL_GET(editreplace, CConVarManager, GetConVarValue<unsigned int>("con.history.edit.replace", 0));
if (keepindex && editreplace && !cmd.empty())
{
Commands.at(Index) = cmd;
}
else if (!keepindex)
{
CurrentInput = cmd;
Index = -1;
}
}
void CCommandProcessor::CCommandsHistory::Clear()
{
Index = -1;
Commands.clear();
}
void CCommandProcessor::CCommandsHistory::Reset()
{
Index = -1;
CurrentInput.clear();
}
void CCommandProcessor::AddCustomProcessor(std::unique_ptr<CCustomCommandProcessor>&& ptr)
{
m_customProcs.push(std::move(ptr));
}
void CCommandProcessor::QuitCustomProcessor()
{
if(m_customProcs.empty()) { return; }
m_customProcs.top()->OnQuit(m_customProcs.top().get());
m_customProcs.pop();
}
void CCustomCommandProcessor::Quit()
{
COMPONENT_CALL(CCommandProcessor, QuitCustomProcessor());
}
void CCustomCommandProcessor::ProcessSingleCommand(const std::string& cmd, const CCommandArgsWrapper& args, const CCommandSender& sender)
{
if(cmd == "quit" || cmd == "exit") { return Quit(); }
return m_process(cmd, args, sender);
}
void CCommandProcessor::AddAlias(const std::string& _cmd1, const std::wstring& _cmd2)
{
auto it = Aliases.find(_cmd1);
if(it != Aliases.end())
{
Aliases.erase(it);
}
Aliases.emplace(_cmd1, _cmd2);
}
std::wstring CCommandProcessor::GetAlias(const std::string& _alias) const
{
auto it = Aliases.find(_alias);
return it != Aliases.end() ? it->second : std::wstring{};
}
bool CCommandProcessor::AliasExists(const std::string& _alias) const
{
return Aliases.find(_alias) != Aliases.end();
}
void CCommandProcessor::RemoveAlias(const std::string& _alias)
{
auto it = Aliases.find(_alias);
if(it != Aliases.end())
{
Aliases.erase(it);
}
}
sol::object CCommandProcessor::V_GetScriptUserType(sol::state_view state)
{
return sol::make_object(state, this);
}
LINK_COMPONENT_TO_CLASS(CCommandProcessor, commandprocessor);