diff --git a/include/API/FLHook/ClientList.hpp b/include/API/FLHook/ClientList.hpp index 2ea938872..06ce64af0 100644 --- a/include/API/FLHook/ClientList.hpp +++ b/include/API/FLHook/ClientList.hpp @@ -73,7 +73,7 @@ struct ClientData // Your randomly assigned formation tag, e.g. Navy Lambda 1-6 uint formationNumber1 = 0; uint formationNumber2 = 0; - uint formationTag = 0; + AllowedFormation formationTag = AllowedFormation::Alpha; ClientData() { diff --git a/include/Core/Commands/AdminCommandProcessor.hpp b/include/Core/Commands/AdminCommandProcessor.hpp index ad0b07343..4472744d5 100644 --- a/include/Core/Commands/AdminCommandProcessor.hpp +++ b/include/Core/Commands/AdminCommandProcessor.hpp @@ -81,9 +81,10 @@ class AdminCommandProcessor final : public Singleton, pub std::wstring Chase(std::wstring_view characterName); std::wstring Beam(std::wstring_view characterName, std::wstring_view baseName); std::wstring Pull(std::wstring_view characterName); + std::wstring SetDamageType(std::wstring_view newDamageType); // std::wstring Move(const std::wstring& characterName, Vector position); - constexpr inline static std::array commands = { + constexpr inline static std::array commands = { {AddAdminCommand(L"getcash", GetCash, GameAndConsole, Cash, L".getcash ", L"Gets the cash of the target cash"), AddAdminCommand(L"setcash", SetCash, GameAndConsole, Cash, L".setcash ", L"Sets the cash of the target cash"), AddAdminCommand(L"kick", KickPlayer, GameAndConsole, Expel, L".kick ", L"Kick the specified character from the server."), @@ -126,7 +127,9 @@ class AdminCommandProcessor final : public Singleton, pub L"Teleport the admin to the location of the specified character. Does not traverse systems."), AddAdminCommand(L"beam", Beam, GameAndConsole, Movement, L".beam ", L"Teleport the specified character to the specified base."), AddAdminCommand(L"pull", Pull, GameOnly, Movement, L".pull ", - L"Pulls the specified character to your location. Does not traverse systems.")} + L"Pulls the specified character to your location. Does not traverse systems."), + AddAdminCommand(L"damagemode", SetDamageType, GameAndConsole, SuperAdmin, L".damagemode ", + L"Sets the source of allowed damage on the server.")} // AddAdminCommand(L"move", Move, GameOnly, Movement) }; diff --git a/include/Core/FLHook.hpp b/include/Core/FLHook.hpp index 54114efe5..8444938e0 100644 --- a/include/Core/FLHook.hpp +++ b/include/Core/FLHook.hpp @@ -108,7 +108,6 @@ class FLHook final bool LoadBaseMarket(); uint damageToClientId; - uint damageToSpaceId; bool messagePrivate; bool messageSystem; diff --git a/include/Defs/Enums.hpp b/include/Defs/Enums.hpp index fdd98ecfd..a0072434b 100644 --- a/include/Defs/Enums.hpp +++ b/include/Defs/Enums.hpp @@ -121,3 +121,84 @@ enum class IdType Arch, Reputation }; + +enum class DamageMode +{ + None, + PvE = 1 << 0, + PvP = 1 << 1, + All = PvE | PvP +}; + +NLOHMANN_JSON_SERIALIZE_ENUM(DamageMode, { + {DamageMode::None, 0}, + { DamageMode::PvE, "pve"}, + { DamageMode::PvP, "pvp"}, + { DamageMode::All, "all"}, +}); + +enum class AllowedFormation +{ + Alpha = 1, + Beta, + Gamma, + Delta, + Epsilon, + Zeta, + Theta, + Iota, + Kappa, + Lambda, + Omicron, + Sigma, + Omega, + Red, + Blue, + Gold, + Green, + Silver, + Black, + White, + Yellow, + Matsu, + Sakura, + Fuji, + Botan, + Hagi, + Susuki, + Kiku, + Yanagi +}; + +NLOHMANN_JSON_SERIALIZE_ENUM(AllowedFormation, + { + { AllowedFormation::Alpha, "alpha"}, + { AllowedFormation::Beta, "beta"}, + { AllowedFormation::Gamma, "gamma"}, + { AllowedFormation::Delta, "delta"}, + {AllowedFormation::Omicron, "omicron"}, + { AllowedFormation::Omega, "omega"}, + { AllowedFormation::Sigma, "sigma"}, + {AllowedFormation::Epsilon, "epsilon"}, + { AllowedFormation::Zeta, "zeta"}, + { AllowedFormation::Theta, "theta"}, + { AllowedFormation::Kappa, "kappa"}, + { AllowedFormation::Lambda, "lambda"}, + { AllowedFormation::Iota, "iota"}, + { AllowedFormation::Red, "red"}, + { AllowedFormation::Blue, "blue"}, + { AllowedFormation::Gold, "gold"}, + { AllowedFormation::Green, "green"}, + { AllowedFormation::Silver, "silver"}, + { AllowedFormation::Black, "black"}, + { AllowedFormation::White, "white"}, + { AllowedFormation::Yellow, "yellow"}, + { AllowedFormation::Matsu, "matsu"}, + { AllowedFormation::Sakura, "sakura"}, + { AllowedFormation::Fuji, "fuji"}, + { AllowedFormation::Botan, "botan"}, + { AllowedFormation::Hagi, "hagi"}, + { AllowedFormation::Susuki, "susuki"}, + { AllowedFormation::Kiku, "kiku"}, + { AllowedFormation::Yanagi, "yanagi"}, +}) diff --git a/include/Defs/FLHookConfig.hpp b/include/Defs/FLHookConfig.hpp index c02dd449d..8b4bed8e9 100644 --- a/include/Defs/FLHookConfig.hpp +++ b/include/Defs/FLHookConfig.hpp @@ -23,8 +23,10 @@ struct DLL FLHookConfig final : Singleton //! Time of invulnerability upon undock in milliseconds. //! Protected player can also not inflict any damage. uint antiDockKill = 4000; - //! Number of milliseconds the player character remains in space after disconnecting. - uint antiF1 = 0; + + //! The type of damage allowed on this server. Allowed values: 'All', 'None', 'PvP' and 'PvE' + DamageMode damageMode = DamageMode::All; + //! Disable the cruise disrupting effects. bool changeCruiseDisruptorBehaviour = false; @@ -52,14 +54,21 @@ struct DLL FLHookConfig final : Singleton std::vector noPVPSystemsHashed; + Serialize(General, antiDockKill, changeCruiseDisruptorBehaviour, damageMode, disableCharfileEncryption, disconnectDelay, disableNPCSpawns, + maxGroupSize, persistGroup, torpMissileBaseDamageMultiplier, tempBansEnabled, chatSuppressList, noPVPSystems); + }; + + struct AutoKicks final + { //! Amount of time spent idly on a base resulting in a server kick, in seconds. uint antiBaseIdle = 600; //! Amount of time spent idly on character select screen resulting in a server kick, in seconds. uint antiCharMenuIdle = 600; - Serialize(General, antiDockKill, antiF1, changeCruiseDisruptorBehaviour, disableCharfileEncryption, disconnectDelay, disableNPCSpawns, - maxGroupSize, persistGroup, torpMissileBaseDamageMultiplier, tempBansEnabled, chatSuppressList, noPVPSystems, antiBaseIdle, - antiCharMenuIdle); + //! Number of milliseconds the player character remains in space after disconnecting. + uint antiF1 = 0; + + Serialize(AutoKicks, antiBaseIdle, antiCharMenuIdle, antiF1) }; struct Plugins final @@ -179,8 +188,13 @@ struct DLL FLHookConfig final : Singleton struct Callsign final { //! The mapping of numbers to formations. 1, min = Alpha. 29, max = Yanagi - std::vector allowedFormations = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 + std::vector allowedFormations = { + AllowedFormation::Alpha, AllowedFormation::Beta, AllowedFormation::Gamma, AllowedFormation::Delta, AllowedFormation::Epsilon, + AllowedFormation::Zeta, AllowedFormation::Theta, AllowedFormation::Iota, AllowedFormation::Kappa, AllowedFormation::Lambda, + AllowedFormation::Omicron, AllowedFormation::Sigma, AllowedFormation::Omega, AllowedFormation::Red, AllowedFormation::Blue, + AllowedFormation::Gold, AllowedFormation::Green, AllowedFormation::Silver, AllowedFormation::Black, AllowedFormation::White, + AllowedFormation::Yellow, AllowedFormation::Matsu, AllowedFormation::Sakura, AllowedFormation::Fuji, AllowedFormation::Botan, + AllowedFormation::Hagi, AllowedFormation::Susuki, AllowedFormation::Kiku, AllowedFormation::Yanagi }; //! If true, formations and numbers will not be assigned to ships. All ships will be alpha 1-1. @@ -194,6 +208,7 @@ struct DLL FLHookConfig final : Singleton Debug debug; General general; + AutoKicks autoKicks; Plugins plugins; MessageQueue messageQueue; UserCommands userCommands; @@ -201,5 +216,5 @@ struct DLL FLHookConfig final : Singleton ChatConfig chatConfig; Callsign callsign; - Serialize(FLHookConfig, debug, general, plugins, messageQueue, userCommands, bans, chatConfig, callsign); + Serialize(FLHookConfig, debug, general, autoKicks, plugins, messageQueue, userCommands, bans, chatConfig, callsign); }; diff --git a/include/PCH.hpp b/include/PCH.hpp index 0357a731a..e53de5576 100644 --- a/include/PCH.hpp +++ b/include/PCH.hpp @@ -43,6 +43,7 @@ #define MAGIC_ENUM_USING_ALIAS_STRING_VIEW using string_view = std::wstring_view; #define MAGIC_ENUM_USING_ALIAS_STRING using string = std::wstring; #include +#include #include diff --git a/source/Core/Commands/AdminCommandProcessor.cpp b/source/Core/Commands/AdminCommandProcessor.cpp index 9e04a7a9f..45de1299a 100644 --- a/source/Core/Commands/AdminCommandProcessor.cpp +++ b/source/Core/Commands/AdminCommandProcessor.cpp @@ -4,8 +4,6 @@ #include "Core/Commands/AdminCommandProcessor.hpp" -#include "API/FLHook/ClientList.hpp" - // TODO: General, a lot of these functions are agnostic about whether or not the player is online and thus has a clientId, so along with the player database // rework a lot of these functions need to be reworked to account for that. @@ -378,6 +376,47 @@ std::wstring AdminCommandProcessor::Pull(std::wstring_view characterName) return std::format(L"player {} pulled to {} at x={:.0f} y={:.0f} z={:.0f}", characterName, currentUser, pos.x, pos.y, pos.z); } +std::wstring AdminCommandProcessor::SetDamageType(std::wstring_view newDamageType) +{ + static std::wstring usage = L"Sets what can be damaged on the server. Valid values are 'None', 'All', PvP, 'PvE'."; + if (newDamageType.empty()) + { + return usage; + } + + auto* config = FLHookConfig::i(); + const auto lower = StringUtils::ToLower(newDamageType); + if (lower == L"none") + { + config->general.damageMode = DamageMode::None; + Serializer::SaveToJson(*config, L"flhook.json"); + return L"Set damage mode to None. No player ship can take damage, but NPCs can still hurt each other."; + } + + if (lower == L"all") + { + config->general.damageMode = DamageMode::All; + Serializer::SaveToJson(*config, L"flhook.json"); + return L"Set damage mode to All. All ships can take damage."; + } + + if (lower == L"pvp") + { + config->general.damageMode = DamageMode::PvP; + Serializer::SaveToJson(*config, L"flhook.json"); + return L"Set damage mode to PvP. Players can hurt players, and NPCs can hurt NPCs, but they cannot hurt each other."; + } + + if (lower == L"pve") + { + config->general.damageMode = DamageMode::PvE; + Serializer::SaveToJson(*config, L"flhook.json"); + return L"Set damage mode to PvE. Players cannot hurt each other, but can hurt and be hurt by NPCs."; + } + + return usage; +} + // std::wstring AdminCommandProcessor::Move(std::wstring_view characterName, Vector position) //{ // std::wstring targetPlayer; diff --git a/source/Core/Timers.cpp b/source/Core/Timers.cpp index 66b68efc0..ed975e988 100644 --- a/source/Core/Timers.cpp +++ b/source/Core/Timers.cpp @@ -96,17 +96,17 @@ void FLHook::TimerCheckKick() } const auto* config = FLHookConfig::c(); - if (config->general.antiBaseIdle) + if (config->autoKicks.antiBaseIdle) { // anti base-idle check - if (client.baseEnterTime && time - client.baseEnterTime >= config->general.antiBaseIdle) + if (client.baseEnterTime && time - client.baseEnterTime >= config->autoKicks.antiBaseIdle) { client.id.Kick(L"Base idling", 10); client.baseEnterTime = 0; } } - if (config->general.antiCharMenuIdle) + if (config->autoKicks.antiCharMenuIdle) { // anti charmenu-idle check if (!client.characterName.empty()) @@ -115,7 +115,7 @@ void FLHook::TimerCheckKick() { client.charMenuEnterTime = static_cast(time); } - else if (time - client.charMenuEnterTime >= config->general.antiCharMenuIdle) + else if (time - client.charMenuEnterTime >= config->autoKicks.antiCharMenuIdle) { client.id.Kick(); client.charMenuEnterTime = 0; diff --git a/source/FLHook.cpp b/source/FLHook.cpp index fb7da1582..fb7d0f7a3 100644 --- a/source/FLHook.cpp +++ b/source/FLHook.cpp @@ -56,8 +56,8 @@ BOOL WINAPI DllMain([[maybe_unused]] const HINSTANCE& hinstDLL, [[maybe_unused]] } FLHook::FLHook() - : damageToClientId(0), damageToSpaceId(0), messagePrivate(false), messageSystem(false), messageUniverse(false), serverLoadInMs(0), playerCount(0), - disableNpcs(false), flhookReady(false) + : damageToClientId(0), messagePrivate(false), messageSystem(false), messageUniverse(false), serverLoadInMs(0), playerCount(0), disableNpcs(false), + flhookReady(false) { Logger::Init(); // Set module references @@ -292,6 +292,9 @@ void FLHook::LoadSettings() config->general.noPVPSystemsHashed.emplace_back(systemId); } + // Resave to add any missing properties that have been added + Serializer::SaveToJson(*config, L"flhook.json"); + // Explicitly replace the config FLHookConfig::i(&config); } diff --git a/source/Hooks/ClientServerInterface/Character.cpp b/source/Hooks/ClientServerInterface/Character.cpp index 59854bfe6..b63e62014 100644 --- a/source/Hooks/ClientServerInterface/Character.cpp +++ b/source/Hooks/ClientServerInterface/Character.cpp @@ -75,15 +75,23 @@ void IServerImplHook::CharacterSelectInnerAfter([[maybe_unused]] const CHARACTER // Assign their random formation id. // Numbers are between 0-20 (inclusive) // Formations are between 1-29 (inclusive) - std::random_device dev; - std::mt19937 rng(dev()); + static std::random_device dev; + static std::mt19937 rng(dev()); std::uniform_int_distribution distNum(1, 20); const auto* conf = FLHookConfig::c(); std::uniform_int_distribution distForm(0, conf->callsign.allowedFormations.size() - 1); + if (conf->callsign.allowedFormations.empty()) + { + info.formationTag = AllowedFormation::Alpha; + } + else + { + info.formationTag = conf->callsign.allowedFormations[distForm(rng)]; + } + info.formationNumber1 = distNum(rng); info.formationNumber2 = distNum(rng); - info.formationTag = conf->callsign.allowedFormations[distForm(rng)]; } } CatchHook({}) @@ -142,8 +150,7 @@ void __stdcall IServerImplHook::DestroyCharacter(const CHARACTER_ID& cid, Client void __stdcall IServerImplHook::RequestRankLevel(ClientId client, uint unk1, int unk2) { - Logger::Log(LogLevel::Trace, - std::format(L"RequestRankLevel(\n\tClientId client = {}\n\tuint unk1 = 0x{:08X}\n\tint unk2 = {}\n)", client, unk1, unk2)); + Logger::Log(LogLevel::Trace, std::format(L"RequestRankLevel(\n\tClientId client = {}\n\tuint unk1 = 0x{:08X}\n\tint unk2 = {}\n)", client, unk1, unk2)); if (const auto skip = CallPlugins(&Plugin::OnRequestRankLevel, client, unk1, unk2); !skip) { @@ -156,8 +163,7 @@ void __stdcall IServerImplHook::RequestRankLevel(ClientId client, uint unk1, int void __stdcall IServerImplHook::RequestPlayerStats(ClientId client, uint unk1, int unk2) { - Logger::Log(LogLevel::Trace, - std::format(L"RequestPlayerStats(\n\tClientId client = {}\n\tuint unk1 = 0x{:08X}\n\tint unk2 = {}\n)", client, unk1, unk2)); + Logger::Log(LogLevel::Trace, std::format(L"RequestPlayerStats(\n\tClientId client = {}\n\tuint unk1 = 0x{:08X}\n\tint unk2 = {}\n)", client, unk1, unk2)); if (const auto skip = CallPlugins(&Plugin::OnRequestPlayerStats, client, unk1, unk2); !skip) { @@ -185,7 +191,7 @@ bool IServerImplHook::CharacterInfoReqInner(ClientId client, bool) if (shipId) { // in space - info.f1Time = TimeUtils::UnixTime() + FLHookConfig::i()->general.antiF1; + info.f1Time = TimeUtils::UnixTime() + FLHookConfig::i()->autoKicks.antiF1; return false; } } diff --git a/source/Hooks/HkIEngineDamage.cpp b/source/Hooks/HkIEngineDamage.cpp index 50dc7b341..675859cc9 100644 --- a/source/Hooks/HkIEngineDamage.cpp +++ b/source/Hooks/HkIEngineDamage.cpp @@ -97,6 +97,13 @@ void __stdcall IEngineHook::AddDamageEntry(DamageList* dmgList, unsigned short s return; } + if (const auto damageMode = FLHookConfig::i()->general.damageMode; + !magic_enum::enum_flags_test(damageMode, DamageMode::PvE) && (dmgList->is_inflictor_a_player() && !FLHook::dmgToClient) || + (!dmgList->is_inflictor_a_player() && FLHook::dmgToClient)) + { + return; + } + // check if we got damaged by a cd with changed behaviour if (dmgList->get_cause() == DamageCause::DummyDisrupter) { @@ -231,9 +238,18 @@ bool IEngineHook::AllowPlayerDamage(ClientId client, ClientId clientTarget) } const auto* config = FLHookConfig::c(); + if (config->general.damageMode == DamageMode::None) + { + return false; + } if (clientTarget) { + if (!magic_enum::enum_flags_test(config->general.damageMode, DamageMode::PvP)) + { + return false; + } + auto time = TimeUtils::UnixTime(); // anti-dockkill check