diff --git a/CMakeLists.txt b/CMakeLists.txt index 4105777..6dfbea8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,12 @@ project(jupiter_bot) set(CMAKE_CXX_STANDARD 20) +set(TAR_FILENAME "Jupiter Bot Binaries.zip") +if (DEFINED JUPITER_VERSION_SHORT) + add_compile_definitions(JUPITER_VERSION_SHORT="${JUPITER_VERSION_SHORT}") + set(TAR_FILENAME "Jupiter Bot Binaries.${JUPITER_VERSION_SHORT}.zip") +endif() + message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") include(build/CMakeLists.txt) @@ -13,9 +19,10 @@ add_subdirectory(src) add_custom_target(PackagedBuild) add_dependencies(PackagedBuild jupiter Bot) -if(NOT DEFINED ${PACKAGED_BUILD_PATH}) +if(NOT DEFINED PACKAGED_BUILD_PATH) set(PACKAGED_BUILD_PATH "${CMAKE_CURRENT_SOURCE_DIR}/bin") endif() +message(STATUS "PackagedBuild Path: ${PACKAGED_BUILD_PATH}") add_custom_command(TARGET PackagedBuild PRE_BUILD @@ -36,3 +43,12 @@ foreach(plugin ${JUPITER_PLUGINS}) POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${PACKAGED_BUILD_PATH}/Plugins/) endforeach() + +# Wrap it all into a .zip +add_custom_command(TARGET PackagedBuild + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E chdir "${PACKAGED_BUILD_PATH}/" tar cf ../${TAR_FILENAME} --format=zip -- *) + +add_custom_command(TARGET PackagedBuild + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rename "${PACKAGED_BUILD_PATH}/../${TAR_FILENAME}" "${PACKAGED_BUILD_PATH}/${TAR_FILENAME}") diff --git a/baseline/Configs/RenX.Commands.ini b/baseline/Configs/RenX.Commands.ini index b473105..8161012 100644 --- a/baseline/Configs/RenX.Commands.ini +++ b/baseline/Configs/RenX.Commands.ini @@ -1,12 +1,14 @@ ; File: RenX.Commands.ini ; -; TBanTime=Integer (Default: 86400; Time in seconds) +; DefaultTBanTime=Integer (Default: 1d; 1 day) +; MaxTBanTime=Integer (Default: 1w; 1 week) ; PlayerInfoFormat=String (Default: 03[Player Info]{TCOLOR} Name: {RNAME} - ID: {ID} - Team: {TEAML} - Vehicle Kills: {VEHICLEKILLS} - Building Kills {BUILDINGKILLS} - Kills {KILLS} - Deaths: {DEATHS} - KDR: {KDR} - Access: {ACCESS}") ; AdminPlayerInfoFormat=String (Default: PlayerInfoFormat - IP: {IP} - Steam ID: {STEAM}) ; BuildingInfoFormat=String (Default: {BCOLOR} {BNAME} - 07{BHP}%) ; StaffTitle=String (Default: Moderator) ; -TBanTime=86400 +DefaultTBanTime=1d +MaxTBanTime=1w ;EOF \ No newline at end of file diff --git a/baseline/Configs/RenX.Relay.ini b/baseline/Configs/RenX.Relay.ini index b0c8c96..bdae855 100644 --- a/baseline/Configs/RenX.Relay.ini +++ b/baseline/Configs/RenX.Relay.ini @@ -19,7 +19,7 @@ ; SanitizeIPs=Bool (Default: true; sanitizes all player IPs from all messages) ; SanitizeHWIDs=Bool (Default: true; sanitizes all player HWIDs from all messages) ; SanitizeSteamIDs=Bool (Default: true; sanitizes all player SteamIDs from all messages) -; SanitizeUnknownCmds=Bool (Default: true; sanitizes all unknown commands coming from devbot) -; SanitizeBlacklistedCmds=Bool (Default: true; sanitizes all non-informational commands coming from devbot) +; SuppressUnknownCmds=Bool (Default: true; sanitizes all unknown commands coming from devbot) +; SuppressBlacklistedCmds=Bool (Default: true; sanitizes all non-informational commands coming from devbot) ; SuppressChatLogs=Bool (Default: true; suppresses all chat logs) ; diff --git a/src/Plugins/RenX/RenX.Commands/RenX_Commands.cpp b/src/Plugins/RenX/RenX.Commands/RenX_Commands.cpp index a66e919..ef57535 100644 --- a/src/Plugins/RenX/RenX.Commands/RenX_Commands.cpp +++ b/src/Plugins/RenX/RenX.Commands/RenX_Commands.cpp @@ -32,6 +32,7 @@ #include "RenX_Tags.h" using namespace Jupiter::literals; +using namespace jessilib::literals; const Jupiter::ReferenceString RxCommandsSection = "RenX.Commands"_jrs; @@ -69,7 +70,10 @@ void RenX_CommandsPlugin::RenX_OnDie(RenX::Server &server, const RenX::PlayerInf bool RenX_CommandsPlugin::initialize() { - RenX_CommandsPlugin::_defaultTempBanTime = std::chrono::seconds(this->config.get("TBanTime"_jrs, 86400)); + auto default_tban_time = this->config.get("DefaultTBanTime"_jrs, "1d"_jrs); + auto max_tban_time = this->config.get("MaxTBanTime"_jrs, "1w"_jrs); + RenX_CommandsPlugin::m_defaultTempBanTime = jessilib::duration_from_string(default_tban_time.ptr(), default_tban_time.ptr() + default_tban_time.size()).duration; + RenX_CommandsPlugin::m_maxTempBanTime = std::max(jessilib::duration_from_string(max_tban_time.ptr(), max_tban_time.ptr() + max_tban_time.size()).duration, m_defaultTempBanTime); RenX_CommandsPlugin::playerInfoFormat = this->config.get("PlayerInfoFormat"_jrs, IRCCOLOR "03[Player Info]" IRCCOLOR "{TCOLOR} Name: " IRCBOLD "{RNAME}" IRCBOLD " - ID: {ID} - Team: " IRCBOLD "{TEAML}" IRCBOLD " - Vehicle Kills: {VEHICLEKILLS} - Building Kills {BUILDINGKILLS} - Kills {KILLS} - Deaths: {DEATHS} - KDR: {KDR} - Access: {ACCESS}"_jrs); RenX_CommandsPlugin::adminPlayerInfoFormat = this->config.get("AdminPlayerInfoFormat"_jrs, Jupiter::StringS::Format("%.*s - IP: " IRCBOLD "{IP}" IRCBOLD " - HWID: " IRCBOLD "{HWID}" IRCBOLD " - RDNS: " IRCBOLD "{RDNS}" IRCBOLD " - Steam ID: " IRCBOLD "{STEAM}", RenX_CommandsPlugin::playerInfoFormat.size(), RenX_CommandsPlugin::playerInfoFormat.ptr())); RenX_CommandsPlugin::buildingInfoFormat = this->config.get("BuildingInfoFormat"_jrs, ""_jrs IRCCOLOR + RenX::tags->buildingTeamColorTag + RenX::tags->buildingNameTag + IRCCOLOR " - " IRCCOLOR "07"_jrs + RenX::tags->buildingHealthPercentageTag + "%"_jrs); @@ -87,9 +91,12 @@ int RenX_CommandsPlugin::OnRehash() return this->initialize() ? 0 : -1; } -std::chrono::seconds RenX_CommandsPlugin::getTBanTime() const -{ - return RenX_CommandsPlugin::_defaultTempBanTime; +std::chrono::seconds RenX_CommandsPlugin::getDefaultTBanTime() const { + return RenX_CommandsPlugin::m_defaultTempBanTime; +} + +std::chrono::seconds RenX_CommandsPlugin::getMaxTBanTime() const { + return RenX_CommandsPlugin::m_maxTempBanTime; } const Jupiter::ReadableString &RenX_CommandsPlugin::getPlayerInfoFormat() const @@ -2050,6 +2057,41 @@ IRC_COMMAND_INIT(BanSearchIRCCommand) // TempBan IRC Command +struct player_search_result { + RenX::Server* server{}; // Set to the server the player is on, if one is found, last server checked otherwise + RenX::PlayerInfo* player{}; // Set if a player is found +}; + +player_search_result findPlayerByPartName(Jupiter::ArrayList& servers, const Jupiter::ReadableString& name) { + if (servers.size() == 0) { + return {}; + } + + player_search_result result; + for (size_t index = 0; index != servers.size(); ++index) { + auto server = servers.get(index); + if (server != nullptr) { + result.server = server; + result.player = server->getPlayerByPartName(name); + if (result.player != nullptr) { + break; + } + } + } + + return result; +} + +player_search_result findPlayerByPartName(IRC_Bot* source, const Jupiter::ReadableString& channel, const Jupiter::ReadableString& name) { + Jupiter::IRC::Client::Channel *chan = source->getChannel(channel); + if (chan == nullptr) { + return {}; + } + + Jupiter::ArrayList servers = RenX::getCore()->getServers(chan->getType()); + return findPlayerByPartName(servers, name); +} + void TempBanIRCCommand::create() { this->addTrigger(STRING_LITERAL_AS_REFERENCE("tban")); @@ -2060,46 +2102,53 @@ void TempBanIRCCommand::create() void TempBanIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString ¶meters) { - if (parameters.isNotEmpty()) - { - Jupiter::IRC::Client::Channel *chan = source->getChannel(channel); - if (chan != nullptr) - { - Jupiter::ArrayList servers = RenX::getCore()->getServers(chan->getType()); - if (servers.size() != 0) - { - RenX::PlayerInfo *player; - RenX::Server *server; - unsigned int kicks = 0; - Jupiter::ReferenceString name = Jupiter::ReferenceString::getWord(parameters, 0, WHITESPACE); - Jupiter::ReferenceString reason = parameters.wordCount(WHITESPACE) > 1 ? Jupiter::ReferenceString::gotoWord(parameters, 1, WHITESPACE) : "No reason"_jrs; - Jupiter::String banner(nick.size() + 4); - banner += nick; - banner += "@IRC"; - for (size_t i = 0; i != servers.size(); i++) - { - server = servers.get(i); - if (server != nullptr) - { - player = server->getPlayerByPartName(name); - if (player != nullptr) - { - server->banPlayer(*player, banner, reason, pluginInstance.getTBanTime()); - kicks++; - } - } - } - source->sendMessage(channel, Jupiter::StringS::Format("%u players kicked.", kicks)); - } - else source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Channel not attached to any connected Renegade X servers.")); + if (parameters.isEmpty()) { + source->sendNotice(nick, STRING_LITERAL_AS_REFERENCE("Error: Too Few Parameters. Syntax: TempBan [Duration] [Reason]")); + return; + } + + std::chrono::seconds duration = pluginInstance.getDefaultTBanTime(); + Jupiter::ReferenceString name = Jupiter::ReferenceString::getWord(parameters, 0, WHITESPACE); + size_t word_count = parameters.wordCount(WHITESPACE); + size_t reason_start_word = 1; + + // Try searching by name first + auto search_result = findPlayerByPartName(source, channel, name); + if (search_result.server != nullptr + && search_result.player == nullptr + && word_count > 1) { + // Try reading token as a duration instead, and search the name token if duration > 0 + duration = jessilib::duration_from_string(name.ptr(), name.ptr() + name.size()).duration; + if (duration.count() > 0) { + // It reads as a duration; sanity check & try searching again + duration = std::min(duration, pluginInstance.getMaxTBanTime()); + name = Jupiter::ReferenceString::getWord(parameters, 1, WHITESPACE); + ++reason_start_word; + search_result = findPlayerByPartName(source, channel, name); } } - else source->sendNotice(nick, STRING_LITERAL_AS_REFERENCE("Error: Too Few Parameters. Syntax: TempBan [Reason]")); + + if (search_result.server == nullptr) { + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Channel not attached to any connected Renegade X servers.")); + return; + } + + if (search_result.player == nullptr) { + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Could not find player.")); + return; + } + + Jupiter::ReferenceString reason = word_count > reason_start_word ? Jupiter::ReferenceString::gotoWord(parameters, reason_start_word, WHITESPACE) : "No reason"_jrs; + Jupiter::String banner(nick.size() + 4); + banner += nick; + banner += "@IRC"; + search_result.server->banPlayer(*search_result.player, banner, reason, duration); + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Player banned.")); } const Jupiter::ReadableString &TempBanIRCCommand::getHelp(const Jupiter::ReadableString &) { - static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Kicks and temporarily bans a player from the game. Syntax: TempBan [Reason]"); + static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Kicks and temporarily bans a player from the game. Syntax: TempBan [Duration] [Reason]"); return defaultHelp; } @@ -2118,47 +2167,53 @@ void TempChatBanIRCCommand::create() void TempChatBanIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString ¶meters) { - if (parameters.isNotEmpty()) - { - Jupiter::IRC::Client::Channel *chan = source->getChannel(channel); - if (chan != nullptr) - { - Jupiter::ArrayList servers = RenX::getCore()->getServers(chan->getType()); - if (servers.size() != 0) - { - RenX::PlayerInfo *player; - RenX::Server *server; - unsigned int mutes = 0; - Jupiter::ReferenceString name = Jupiter::ReferenceString::getWord(parameters, 0, WHITESPACE); - Jupiter::ReferenceString reason = parameters.wordCount(WHITESPACE) > 1 ? Jupiter::ReferenceString::gotoWord(parameters, 1, WHITESPACE) : "No reason"_jrs; - Jupiter::String banner(nick.size() + 4); - banner += nick; - banner += "@IRC"; - for (size_t i = 0; i != servers.size(); i++) - { - server = servers.get(i); - if (server != nullptr) - { - player = server->getPlayerByPartName(name); - if (player != nullptr) - { - server->mute(*player); - RenX::banDatabase->add(server, *player, banner, reason, pluginInstance.getTBanTime(), RenX::BanDatabase::Entry::FLAG_TYPE_CHAT); - mutes++; - } - } - } - source->sendMessage(channel, Jupiter::StringS::Format("%u players chat banned.", mutes)); - } - else source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Channel not attached to any connected Renegade X servers.")); + if (parameters.isEmpty()) { + source->sendNotice(nick, STRING_LITERAL_AS_REFERENCE("Error: Too Few Parameters. Syntax: TempChatBan [Duration] [Reason]")); + return; + } + + std::chrono::seconds duration = pluginInstance.getDefaultTBanTime(); + Jupiter::ReferenceString name = Jupiter::ReferenceString::getWord(parameters, 0, WHITESPACE); + size_t word_count = parameters.wordCount(WHITESPACE); + size_t reason_start_word = 1; + + // Try searching by name first + auto search_result = findPlayerByPartName(source, channel, name); + if (search_result.server != nullptr + && search_result.player == nullptr + && word_count > 1) { + // Try reading token as a duration instead, and search the name token if duration > 0 + duration = jessilib::duration_from_string(name.ptr(), name.ptr() + name.size()).duration; + if (duration.count() > 0) { + // It reads as a duration; sanity check & try searching again + duration = std::min(duration, pluginInstance.getMaxTBanTime()); + name = Jupiter::ReferenceString::getWord(parameters, 1, WHITESPACE); + ++reason_start_word; + search_result = findPlayerByPartName(source, channel, name); } } - else source->sendNotice(nick, STRING_LITERAL_AS_REFERENCE("Error: Too Few Parameters. Syntax: TempChatBan [Reason]")); + + if (search_result.server == nullptr) { + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Channel not attached to any connected Renegade X servers.")); + return; + } + + if (search_result.player == nullptr) { + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Could not find player.")); + return; + } + + Jupiter::ReferenceString reason = word_count > reason_start_word ? Jupiter::ReferenceString::gotoWord(parameters, reason_start_word, WHITESPACE) : "No reason"_jrs; + Jupiter::String banner(nick.size() + 4); + banner += nick; + banner += "@IRC"; + RenX::banDatabase->add(search_result.server, *search_result.player, banner, reason, duration, RenX::BanDatabase::Entry::FLAG_TYPE_CHAT); + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Player chat banned.")); } const Jupiter::ReadableString &TempChatBanIRCCommand::getHelp(const Jupiter::ReadableString &) { - static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Mutes and temporarily chat bans a player from the game. Syntax: TempChatBan [Reason]"); + static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Mutes and temporarily chat bans a player from the game. Syntax: TempChatBan [Duration] [Reason]"); return defaultHelp; } @@ -3941,23 +3996,40 @@ void TempBanGameCommand::trigger(RenX::Server *source, RenX::PlayerInfo *player, if (parameters.isNotEmpty()) { Jupiter::StringS name = Jupiter::StringS::getWord(parameters, 0, WHITESPACE); + std::chrono::seconds duration = pluginInstance.getDefaultTBanTime(); Jupiter::StringS reason; - if (parameters.wordCount(WHITESPACE) > 1) { - reason = Jupiter::StringS::gotoWord(parameters, 1, WHITESPACE); + auto word_count = parameters.wordCount(WHITESPACE); + size_t reason_pos = 1; + + RenX::PlayerInfo *target = source->getPlayerByPartName(name); + if (target == nullptr + && word_count > 1) { + // Try reading first token as duration + duration = jessilib::duration_from_string(name.ptr(), name.ptr() + name.size()).duration; + if (duration.count() > 0) { + duration = std::min(duration, pluginInstance.getMaxTBanTime()); + ++reason_pos; + name = Jupiter::StringS::getWord(parameters, 1, WHITESPACE); + target = source->getPlayerByPartName(name); + } + } + + if (word_count > reason_pos) { + reason = Jupiter::StringS::gotoWord(parameters, reason_pos, WHITESPACE); } else { reason = STRING_LITERAL_AS_REFERENCE("No reason"); } - RenX::PlayerInfo *target = source->getPlayerByPartName(name); + if (target == nullptr) source->sendMessage(*player, "Error: Player not found."_jrs); else if (player == target) - source->sendMessage(*player, "Error: You can not ban yourself."_jrs); + source->sendMessage(*player, "Error: You can't ban yourself."_jrs); else if (target->access >= player->access) - source->sendMessage(*player, "Error: You can not ban higher level "_jrs + pluginInstance.getStaffTitle() + "s."_jrs); + source->sendMessage(*player, "Error: You can't ban higher level "_jrs + pluginInstance.getStaffTitle() + "s."_jrs); else { - source->banPlayer(*target, player->name, reason, pluginInstance.getTBanTime()); + source->banPlayer(*target, player->name, reason, duration); source->sendMessage(*player, "Player has been temporarily banned and kicked from the game."_jrs); } } @@ -3989,14 +4061,31 @@ void TempChatBanGameCommand::trigger(RenX::Server *source, RenX::PlayerInfo *pla if (parameters.isNotEmpty()) { Jupiter::StringS name = Jupiter::StringS::getWord(parameters, 0, WHITESPACE); + std::chrono::seconds duration = pluginInstance.getDefaultTBanTime(); Jupiter::StringS reason; - if (parameters.wordCount(WHITESPACE) > 1) { - reason = Jupiter::StringS::gotoWord(parameters, 1, WHITESPACE); + auto word_count = parameters.wordCount(WHITESPACE); + size_t reason_pos = 1; + + RenX::PlayerInfo *target = source->getPlayerByPartName(name); + if (target == nullptr + && word_count > 1) { + // Try reading first token as duration + duration = jessilib::duration_from_string(name.ptr(), name.ptr() + name.size()).duration; + if (duration.count() > 0) { + duration = std::min(duration, pluginInstance.getMaxTBanTime()); + ++reason_pos; + name = Jupiter::StringS::getWord(parameters, 1, WHITESPACE); + target = source->getPlayerByPartName(name); + } + } + + if (word_count > reason_pos) { + reason = Jupiter::StringS::gotoWord(parameters, reason_pos, WHITESPACE); } else { reason = STRING_LITERAL_AS_REFERENCE("No reason"); } - RenX::PlayerInfo *target = source->getPlayerByPartName(name); + if (target == nullptr) source->sendMessage(*player, "Error: Player not found."_jrs); else if (player == target) @@ -4006,17 +4095,17 @@ void TempChatBanGameCommand::trigger(RenX::Server *source, RenX::PlayerInfo *pla else { source->mute(*target); - RenX::banDatabase->add(source, *target, player->name, reason, pluginInstance.getTBanTime(), RenX::BanDatabase::Entry::FLAG_TYPE_CHAT); + RenX::banDatabase->add(source, *target, player->name, reason, duration, RenX::BanDatabase::Entry::FLAG_TYPE_CHAT); source->sendMessage(*player, "Player has been temporarily muted and chat banned from the game."_jrs); } } else - source->sendMessage(*player, "Error: Too few parameters. Syntax: tchatban [Reason]"_jrs); + source->sendMessage(*player, "Error: Too few parameters. Syntax: tchatban [Duration] [Reason]"_jrs); } const Jupiter::ReadableString &TempChatBanGameCommand::getHelp(const Jupiter::ReadableString &) { - static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Mutes and temporarily chat bans a player from the game. Syntax: tchatban [Reason]"); + static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Mutes and temporarily chat bans a player from the game. Syntax: tchatban [Duration] [Reason]"); return defaultHelp; } @@ -4060,12 +4149,12 @@ void KickBanGameCommand::trigger(RenX::Server *source, RenX::PlayerInfo *player, } } else - source->sendMessage(*player, "Error: Too few parameters. Syntax: ban [reason]"_jrs); + source->sendMessage(*player, "Error: Too few parameters. Syntax: ban [Duration] [reason]"_jrs); } const Jupiter::ReadableString &KickBanGameCommand::getHelp(const Jupiter::ReadableString &) { - static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Kicks and bans a player from the game. Syntax: ban [reason]"); + static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Kicks and bans a player from the game. Syntax: ban [Duration] [reason]"); return defaultHelp; } diff --git a/src/Plugins/RenX/RenX.Commands/RenX_Commands.h b/src/Plugins/RenX/RenX.Commands/RenX_Commands.h index da7a18a..d7a6871 100644 --- a/src/Plugins/RenX/RenX.Commands/RenX_Commands.h +++ b/src/Plugins/RenX/RenX.Commands/RenX_Commands.h @@ -37,14 +37,16 @@ public: // Jupiter::Plugin int OnRehash() override; public: - std::chrono::seconds getTBanTime() const; + std::chrono::seconds getDefaultTBanTime() const; + std::chrono::seconds getMaxTBanTime() const; const Jupiter::ReadableString &getPlayerInfoFormat() const; const Jupiter::ReadableString &getAdminPlayerInfoFormat() const; const Jupiter::ReadableString &getBuildingInfoFormat() const; const Jupiter::ReadableString &getStaffTitle() const; private: - std::chrono::seconds _defaultTempBanTime; + std::chrono::seconds m_defaultTempBanTime; + std::chrono::seconds m_maxTempBanTime; Jupiter::StringS playerInfoFormat; Jupiter::StringS adminPlayerInfoFormat; Jupiter::StringS buildingInfoFormat;