diff --git a/include/API/API.hpp b/include/API/API.hpp index 773b88a8d..8fd642fc7 100644 --- a/include/API/API.hpp +++ b/include/API/API.hpp @@ -10,3 +10,4 @@ #include "API/FLServer/ZoneUtilities.hpp" #include "API/FLHook/MailManager.hpp" +#include "API/Utils/IniUtils.hpp" \ No newline at end of file diff --git a/plugins/arena/Arena.hpp b/plugins/arena/Arena.hpp index f96f8b120..77a3a0d3a 100644 --- a/plugins/arena/Arena.hpp +++ b/plugins/arena/Arena.hpp @@ -22,7 +22,10 @@ namespace Plugins std::string targetBase; std::string targetSystem; std::string restrictedSystem; + + Serialize(Config, targetBase, targetSystem, restrictedSystem); }; + std::unique_ptr config; // Non-reflectable fields uint targetBaseId = 0; diff --git a/plugins/autobuy/Autobuy.cpp b/plugins/autobuy/Autobuy.cpp index 9be7e7bd5..cbfe5a5b1 100644 --- a/plugins/autobuy/Autobuy.cpp +++ b/plugins/autobuy/Autobuy.cpp @@ -37,9 +37,15 @@ namespace Plugins { + + + void Autobuy::LoadPlayerAutobuy(ClientId client) { + + //TODO: Implement chardata database retrieval for this as IniUtils is being deprecated for 4.1 AutobuyInfo playerAutobuyInfo{}; + /* playerAutobuyInfo.missiles = StringUtils::Cast(Hk::IniUtils::c()->GetFromPlayerFile(client, L"autobuy.missiles").value()); playerAutobuyInfo.torps = StringUtils::Cast(Hk::IniUtils::c()->GetFromPlayerFile(client, L"autobuy.torps").value()); playerAutobuyInfo.cd = StringUtils::Cast(Hk::IniUtils::c()->GetFromPlayerFile(client, L"autobuy.cd").value()); @@ -47,6 +53,7 @@ namespace Plugins playerAutobuyInfo.bb = StringUtils::Cast(Hk::IniUtils::c()->GetFromPlayerFile(client, L"autobuy.bb").value()); playerAutobuyInfo.repairs = StringUtils::Cast(Hk::IniUtils::c()->GetFromPlayerFile(client, L"autobuy.repairs").value()); autobuyInfo[client] = playerAutobuyInfo; + */ } void Autobuy::ClearClientInfo(ClientId& client) { autobuyInfo.erase(client); } @@ -109,9 +116,9 @@ namespace Plugins auto& equip = Players[client].equipDescList.equip; - if (&equip != &Players[client].lShadowEquipDescList.equip) + if (&equip != &Players[client].shadowEquipDescList.equip) { - Players[client].lShadowEquipDescList.equip = equip; + Players[client].shadowEquipDescList.equip = equip; } st6::vector eqVector; @@ -127,12 +134,12 @@ namespace Plugins HookClient->Send_FLPACKET_SERVER_SETEQUIPMENT(client, eqVector); } - if (auto& playerCollision = Players[client].collisionGroupDesc.data; !playerCollision.empty()) + if (auto& playerCollision = Players[client].collisionGroupDesc; !playerCollision.empty()) { st6::list componentList; for (auto& colGrp : playerCollision) { - auto* newColGrp = reinterpret_cast(colGrp.data); + auto* newColGrp = reinterpret_cast(colGrp); newColGrp->componentHp = 1.0f; componentList.push_back(*newColGrp); } @@ -185,9 +192,9 @@ namespace Plugins // shield bats & nanobots uint nanobotsId; - pub::GetGoodID(nanobotsId, config->nanobot_nickname.c_str()); + pub::GetGoodID(nanobotsId, config->nanobotNickname.c_str()); uint shieldBatsId; - pub::GetGoodID(shieldBatsId, config->shield_battery_nickname.c_str()); + pub::GetGoodID(shieldBatsId, config->shieldBatteryNickname.c_str()); bool nanobotsFound = false; bool shieldBattsFound = false; for (auto& item : cargo) diff --git a/plugins/autobuy/Autobuy.hpp b/plugins/autobuy/Autobuy.hpp index d83748276..3a2e4432d 100644 --- a/plugins/autobuy/Autobuy.hpp +++ b/plugins/autobuy/Autobuy.hpp @@ -25,13 +25,15 @@ namespace Plugins }; //! Configurable fields for this plugin - struct Config + struct Config final { // Reflectable fields //! Nickname of the nanobot item being used when performing the automatic purchase - std::string nanobot_nickname = "ge_s_repair_01"; + std::string nanobotNickname = "ge_s_repair_01"; //! Nickname of the shield battery item being used when performing the automatic purchase - std::string shield_battery_nickname = "ge_s_battery_01"; + std::string shieldBatteryNickname = "ge_s_battery_01"; + + Serialize(Config, nanobotNickname, shieldBatteryNickname); }; void UserCmdAutobuy(std::wstring_view autobuyType, std::wstring_view newState); diff --git a/plugins/betting/Betting.cpp b/plugins/betting/Betting.cpp index eee6cf232..a0ec927f6 100644 --- a/plugins/betting/Betting.cpp +++ b/plugins/betting/Betting.cpp @@ -27,468 +27,432 @@ */ #include "PCH.hpp" -#include "Betting.h" -namespace Plugins::Betting +#include "Betting.hpp" + +namespace Plugins { - const std::unique_ptr global = std::make_unique(); - - /** @ingroup Betting - * @brief If the player who died is in an FreeForAll, mark them as a loser. Also handles payouts to winner. - */ - void ProcessFFA(ClientId client) - { - for (const auto& [system, freeForAll] : global->freeForAlls) - { - if (global->freeForAlls[system].contestants[client].accepted && !global->freeForAlls[system].contestants[client].loser) - { - if (global->freeForAlls[system].contestants.contains(client)) - { - global->freeForAlls[system].contestants[client].loser = true; - PrintLocalUserCmdText(client, - std::wstring(reinterpret_cast(Players.GetActiveCharacterName(client))) + L" has been knocked out the FFA.", - 100000); - } - - // Is the FreeForAll over? - int count = 0; - uint contestantId = 0; - for (const auto& [id, contestant] : global->freeForAlls[system].contestants) - { - if (contestant.loser == false && contestant.accepted == true) - { - count++; - contestantId = id; - } - } - - // Has the FreeForAll been won? - if (count <= 1) - { - if (Hk::Client::IsValidClientID(contestantId)) - { - // Announce and pay winner - std::wstring winner = reinterpret_cast(Players.GetActiveCharacterName(contestantId)); - Hk::Player::AddCash(winner, global->freeForAlls[system].pot); - const std::wstring message = - winner + L" has won the FFA and receives " + std::to_wstring(global->freeForAlls[system].pot) + L" credits."; - PrintLocalUserCmdText(contestantId, message, 100000); - } - else - { - PlayerData* playerData = nullptr; - while ((playerData = Players.traverse_active(playerData))) - { - ClientId localClient = playerData->onlineId; - if (SystemId systemId = Hk::Player::GetSystem(localClient).value(); system == systemId) - PrintUserCmdText(localClient, L"No one has won the FFA."); - } - } - // Delete event - global->freeForAlls.erase(system); - return; - } - } - } - } - - /** @ingroup Betting - * @brief This method is called when a player types /ffa in an attempt to start a pvp event - */ - void UserCmdStartFreeForAll(ClientId& client, const std::wstring& param) - { - // Convert string to uint - uint amount = MultiplyUIntBySuffix(param); - - // Check its a valid amount of cash - if (amount == 0) - { - PrintUserCmdText(client, L"Must specify a cash amount. Usage: /ffa e.g. /ffa 5000"); - return; - } - - // Check the player can afford it - std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); - const auto cash = Hk::Player::GetCash(client); - if (cash.has_error()) - { - PrintUserCmdText(client, Hk::Err::ErrGetText(cash.error())); - return; - } - if (amount > 0 && cash.value() < amount) - { - PrintUserCmdText(client, L"You don't have enough credits to create this FFA."); - return; - } - - // Get the player's current system and location in the system. - SystemId systemId = Hk::Player::GetSystem(client).value(); - - // Look in FreeForAll map, is an ffa happening in this system already? - // If system doesn't have an ongoing ffa - if (!global->freeForAlls.contains(systemId)) - { - // Get a list of other players in the system - // Add them and the player into the ffa map - PlayerData* playerData = nullptr; - while ((playerData = Players.traverse_active(playerData))) - { - // Get the this player's current system - ClientId client2 = playerData->onlineId; - if (SystemId clientSystemId = Hk::Player::GetSystem(client2).value(); systemId != clientSystemId) - continue; - - // Add them to the contestants freeForAlls - global->freeForAlls[systemId].contestants[client2].loser = false; - - if (client == client2) - global->freeForAlls[systemId].contestants[client2].accepted = true; - else - { - global->freeForAlls[systemId].contestants[client2].accepted = false; - PrintUserCmdText(client2, - std::format( - L"{} has started a Free-For-All tournament. Cost to enter is {} credits. Type \"/acceptffa\" to enter.", characterName, amount)); - } - } - - // Are there any other players in this system? - if (!global->freeForAlls[systemId].contestants.empty()) - { - PrintUserCmdText(client, L"Challenge issued. Waiting for others to accept."); - global->freeForAlls[systemId].entryAmount = amount; - global->freeForAlls[systemId].pot = amount; - Hk::Player::RemoveCash(characterName, amount); - } - else - { - global->freeForAlls.erase(systemId); - PrintUserCmdText(client, L"There are no other players in this system."); - } - } - else - PrintUserCmdText(client, L"There is an FFA already happening in this system."); - } - - /** @ingroup Betting - * @brief This method is called when a player types /acceptffa - */ - void UserCmdAcceptFFA(ClientId& client, [[maybe_unused]] const std::wstring& param) - { - // Is player in space? - if (const uint ship = Hk::Player::GetShip(client).value(); !ship) - { - PrintUserCmdText(client, L"You must be in space to accept this."); - return; - } - - // Get the player's current system and location in the system. - SystemId systemId = Hk::Player::GetSystem(client).value(); - - if (!global->freeForAlls.contains(systemId)) - { - PrintUserCmdText(client, L"There isn't an FFA in this system. Use /ffa to create one."); - } - else - { - std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); - - // Check the player can afford it - const auto cash = Hk::Player::GetCash(client); - if (cash.has_error()) - { - PrintUserCmdText(client, Hk::Err::ErrGetText(cash.error())); - return; - } - if (global->freeForAlls[systemId].entryAmount > 0 && cash.value() < global->freeForAlls[systemId].entryAmount) - { - PrintUserCmdText(client, L"You don't have enough credits to join this FFA."); - return; - } - - // Accept - if (global->freeForAlls[systemId].contestants[client].accepted == false) - { - global->freeForAlls[systemId].contestants[client].accepted = true; - global->freeForAlls[systemId].contestants[client].loser = false; - global->freeForAlls[systemId].pot = global->freeForAlls[systemId].pot + global->freeForAlls[systemId].entryAmount; - PrintUserCmdText(client, - std::to_wstring(global->freeForAlls[systemId].entryAmount) + - L" credits have been deducted from " - L"your Neural Net account."); - const std::wstring msg = - characterName + L" has joined the FFA. Pot is now at " + std::to_wstring(global->freeForAlls[systemId].pot) + L" credits."; - PrintLocalUserCmdText(client, msg, 100000); - - // Deduct cash - Hk::Player::RemoveCash(characterName, global->freeForAlls[systemId].entryAmount); - } - else - PrintUserCmdText(client, L"You have already accepted the FFA."); - } - } - - /** @ingroup Betting - * @brief Removes any duels with this client and handles payouts. - */ - void ProcessDuel(ClientId client) - { - auto duel = global->duels.begin(); - while (duel != global->duels.end()) - { - uint clientKiller = 0; - - if (duel->client == client) - clientKiller = duel->client2; - - if (duel->client2 == client) - clientKiller = duel->client; - - if (clientKiller == 0) - { - duel++; - continue; - } - - if (duel->accepted) - { - // Get player names - std::wstring victim = reinterpret_cast(Players.GetActiveCharacterName(client)); - std::wstring killer = reinterpret_cast(Players.GetActiveCharacterName(clientKiller)); - - // Prepare and send message - const std::wstring msg = killer + L" has won a duel against " + victim + L" for " + std::to_wstring(duel->amount) + L" credits."; - PrintLocalUserCmdText(clientKiller, msg, 10000); - - // Change cash - Hk::Player::AddCash(killer, duel->amount); - Hk::Player::RemoveCash(victim, duel->amount); - } - else - { - PrintUserCmdText(duel->client, L"Duel cancelled."); - PrintUserCmdText(duel->client2, L"Duel cancelled."); - } - duel = global->duels.erase(duel); - return; - } - } - - /** @ingroup Betting - * @brief This method is called when a player types /duel in an attempt to start a duel - */ - void UserCmdDuel(ClientId& client, const std::wstring& param) - { - // Get the object the player is targetting - const auto targetShip = Hk::Player::GetTarget(client); - if (targetShip.has_error()) - { - PrintUserCmdText(client, Hk::Err::ErrGetText(targetShip.error())); - return; - } - - // Check ship is a player - const auto clientTarget = Hk::Client::GetClientIdByShip(targetShip.value()); - if (clientTarget.has_error()) - { - PrintUserCmdText(client, Hk::Err::ErrGetText(clientTarget.error())); - return; - } - - // Convert string to uint - const uint amount = MultiplyUIntBySuffix(param); - - // Check its a valid amount of cash - if (amount == 0) - { - PrintUserCmdText(client, - L"Must specify a cash amount. Usage: /duel " - L" e.g. /duel 5000"); - return; - } - - const std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); - - // Check the player can afford it - auto const cash = Hk::Player::GetCash(client); - if (cash.has_error()) - { - PrintUserCmdText(client, Hk::Err::ErrGetText(cash.error())); - return; - } - if (amount > 0 && cash.value() < amount) - { - PrintUserCmdText(client, L"You don't have enough credits to issue this challenge."); - return; - } - - // Do either players already have a duel? - for (const auto& duel : global->duels) - { - // Target already has a bet - if (duel.client == clientTarget || duel.client2 == clientTarget) - { - PrintUserCmdText(client, L"This player already has an ongoing duel."); - return; - } - // Player already has a bet - if (duel.client == client || duel.client2 == client) - { - PrintUserCmdText(client, L"You already have an ongoing duel. Type /cancel"); - return; - } - } - - // Create duel - Duel duel; - duel.client = client; - duel.client2 = clientTarget.value(); - duel.amount = amount; - duel.accepted = false; - global->duels.push_back(duel); - - // Message players - const std::wstring characterName2 = reinterpret_cast(Players.GetActiveCharacterName(clientTarget.value())); - const std::wstring message = characterName + L" has challenged " + characterName2 + L" to a duel for " + std::to_wstring(amount) + L" credits."; - PrintLocalUserCmdText(client, message, 10000); - PrintUserCmdText(clientTarget.value(), L"Type \"/acceptduel\" to accept."); - } - - /** @ingroup Betting - * @brief This method is called when a player types /acceptduel to accept a duel request. - */ - void UserCmdAcceptDuel(ClientId& client, [[maybe_unused]] const std::wstring& param) - { - // Is player in space? - if (const uint ship = Hk::Player::GetShip(client).value(); !ship) - { - PrintUserCmdText(client, L"You must be in space to accept this."); - return; - } - - for (auto& duel : global->duels) - { - if (duel.client2 == client) - { - // Has player already accepted the bet? - if (duel.accepted == true) - { - PrintUserCmdText(client, L"You have already accepted the challenge."); - return; - } - - // Check the player can afford it - const std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); - const auto cash = Hk::Player::GetCash(client); - if (cash.has_error()) - { - PrintUserCmdText(client, Hk::Err::ErrGetText(cash.error())); - return; - } - - if (cash.value() < duel.amount) - { - PrintUserCmdText(client, L"You don't have enough credits to accept this challenge"); - return; - } - - duel.accepted = true; - const std::wstring message = characterName + L" has accepted the duel with " + - reinterpret_cast(Players.GetActiveCharacterName(duel.client)) + L" for " + std::to_wstring(duel.amount) + L" credits."; - PrintLocalUserCmdText(client, message, 10000); - return; - } - } - PrintUserCmdText(client, - L"You have no duel requests. To challenge " - L"someone, target them and type /duel "); - } - - /** @ingroup Betting - * @brief This method is called when a player types /cancel to cancel a duel/ffa request. - */ - void UserCmdCancel(ClientId& client, [[maybe_unused]] const std::wstring& param) - { - ProcessFFA(client); - ProcessDuel(client); - } - - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Client command processing - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - const std::vector commands = {CreateUserCommand(L"/acceptduel", L"", UserCmdAcceptDuel, L"Accepts the current duel request."), - CreateUserCommand(L"/acceptffa", L"", UserCmdAcceptFFA, L"Accept the current ffa request."), - CreateUserCommand(L"/cancel", L"", UserCmdCancel, L"Cancel the current duel/ffa request."), - CreateUserCommand(L"/duel", L"", UserCmdDuel, L"Create a duel request to the targeted player. Winner gets the pot."), - CreateUserCommand(L"/ffa", L"", UserCmdStartFreeForAll, L"Create an ffa and send an invite to everyone in the system. Winner gets the pot.")}; - - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Hooks - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - /** @ingroup Betting - * @brief Hook for dock call. Treats a player as if they died if they were part of a duel - */ - int __cdecl DockCall(unsigned int const& ship, [[maybe_unused]] unsigned int const& dock, [[maybe_unused]] const int& cancel, - [[maybe_unused]] const DOCK_HOST_RESPONSE& response) - { - if (const auto client = Hk::Client::GetClientIdByShip(ship); client.has_value() && Hk::Client::IsValidClientID(client.value())) - { - ProcessFFA(client.value()); - ProcessDuel(client.value()); - } - return 0; - } - - /** @ingroup Betting - * @brief Hook for disconnect. Treats a player as if they died if they were part of a duel - */ - void DisConnect(ClientId& client, [[maybe_unused]] const EFLConnection& state) - { - ProcessFFA(client); - ProcessDuel(client); - } - - /** @ingroup Betting - * @brief Hook for char info request (F1). Treats a player as if they died if they were part of a duel - */ - void CharacterInfoReq(ClientId& client, [[maybe_unused]] const bool& param2) - { - ProcessFFA(client); - ProcessDuel(client); - } - - /** @ingroup Betting - * @brief Hook for death to kick player out of duel - */ - void SendDeathMessage([[maybe_unused]] const std::wstring& message, [[maybe_unused]] const uint& system, ClientId& clientVictim, - [[maybe_unused]] const ClientId& clientKiller) - { - ProcessDuel(clientVictim); - ProcessFFA(clientVictim); - } -} // namespace Plugins::Betting - -using namespace Plugins::Betting; + + /** @ingroup Betting + * @brief If the player who died is in an FreeForAll, mark them as a loser. Also handles payouts to winner. + */ + void Betting::ProcessFFA(ClientId client) + { + + for (const auto& [system, freeForAll] : freeForAlls) + { + if (freeForAlls[system].contestants[client].accepted && !freeForAlls[system].contestants[client].loser) + { + if (freeForAlls[system].contestants.contains(client)) + { + freeForAlls[system].contestants[client].loser = true; + PrintLocalUserCmdText(client, + std::wstring(reinterpret_cast(Players.GetActiveCharacterName(client))) + + L" has been knocked out the FFA.", + 100000); + } + + // Is the FreeForAll over? + int count = 0; + uint contestantId = 0; + for (const auto& [id, contestant] : freeForAlls[system].contestants) + { + if (contestant.loser == false && contestant.accepted == true) + { + count++; + contestantId = id; + } + } + + // Has the FreeForAll been won? + if (count <= 1) + { + if (Hk::Client::IsValidClientID(contestantId)) + { + // Announce and pay winner + std::wstring winner = reinterpret_cast(Players.GetActiveCharacterName(contestantId)); + Hk::Player::AddCash(winner, freeForAlls[system].pot); + const std::wstring message = winner + L" has won the FFA and receives " + std::to_wstring(freeForAlls[system].pot) + L" credits."; + PrintLocalUserCmdText(contestantId, message, 100000); + } + else + { + PlayerData* playerData = nullptr; + while ((playerData = Players.traverse_active(playerData))) + { + ClientId localClient = playerData->onlineId; + if (SystemId systemId = Hk::Player::GetSystem(localClient).Handle(); system == systemId) + { + PrintUserCmdText(localClient, L"No one has won the FFA."); + } + } + } + // Delete event + freeForAlls.erase(system); + return; + } + } + } + } + + /** @ingroup Betting + * @brief This method is called when a player types /ffa in an attempt to start a pvp event + */ + void Betting::UserCmdStartFreeForAll(uint amount) + { + + // Check its a valid amount of cash + if (amount == 0) + { + PrintUserCmdText(client, L"Must specify a cash amount. Usage: /ffa e.g. /ffa 5000"); + return; + } + + // Check the player can afford it + std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); + const auto cash = Hk::Player::GetCash(client).Unwrap(); + if (amount > 0 && cash < amount) + { + PrintUserCmdText(client, L"You don't have enough credits to create this FFA."); + return; + } + + // Get the player's current system and location in the system. + SystemId systemId = Hk::Player::GetSystem(client).Handle(); + + // Look in FreeForAll map, is an ffa happening in this system already? + // If system doesn't have an ongoing ffa + if (!freeForAlls.contains(systemId)) + { + // Get a list of other players in the system + // Add them and the player into the ffa map + PlayerData* playerData = nullptr; + while ((playerData = Players.traverse_active(playerData))) + { + // Get the this player's current system + ClientId client2 = playerData->onlineId; + if (SystemId clientSystemId = Hk::Player::GetSystem(client2).Handle(); systemId != clientSystemId) + { + continue; + } + + // Add them to the contestants freeForAlls + freeForAlls[systemId].contestants[client2].loser = false; + + if (client == client2) + { + freeForAlls[systemId].contestants[client2].accepted = true; + } + else + { + freeForAlls[systemId].contestants[client2].accepted = false; + PrintUserCmdText(client2, + std::format(L"{} has started a Free-For-All tournament. Cost to enter is {} credits. Type \"/acceptffa\" to enter.", + characterName, + amount)); + } + } + + // Are there any other players in this system? + if (!freeForAlls[systemId].contestants.empty()) + { + PrintUserCmdText(client, L"Challenge issued. Waiting for others to accept."); + freeForAlls[systemId].entryAmount = amount; + freeForAlls[systemId].pot = amount; + Hk::Player::RemoveCash(characterName, amount); + } + else + { + freeForAlls.erase(systemId); + PrintUserCmdText(client, L"There are no other players in this system."); + } + } + else + { + PrintUserCmdText(client, L"There is an FFA already happening in this system."); + } + } + + /** @ingroup Betting + * @brief This method is called when a player types /acceptffa + */ + void Betting::UserCmdAcceptFFA() + { + // Is player in space? + if (const uint ship = Hk::Player::GetShip(client).Unwrap(); !ship) + { + PrintUserCmdText(client, L"You must be in space to accept this."); + return; + } + + // Get the player's current system and location in the system. + SystemId systemId = Hk::Player::GetSystem(client).Handle(); + + if (!freeForAlls.contains(systemId)) + { + PrintUserCmdText(client, L"There isn't an FFA in this system. Use /ffa to create one."); + } + else + { + std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); + + // Check the player can afford it + const auto cash = Hk::Player::GetCash(client).Unwrap(); + if (freeForAlls[systemId].entryAmount > 0 && cash < freeForAlls[systemId].entryAmount) + { + PrintUserCmdText(client, L"You don't have enough credits to join this FFA."); + return; + } + + // Accept + if (freeForAlls[systemId].contestants[client].accepted == false) + { + freeForAlls[systemId].contestants[client].accepted = true; + freeForAlls[systemId].contestants[client].loser = false; + freeForAlls[systemId].pot = freeForAlls[systemId].pot + freeForAlls[systemId].entryAmount; + PrintUserCmdText(client, + std::to_wstring(freeForAlls[systemId].entryAmount) + L" credits have been deducted from " + L"your Neural Net account."); + const std::wstring msg = characterName + L" has joined the FFA. Pot is now at " + std::to_wstring(freeForAlls[systemId].pot) + L" credits."; + PrintLocalUserCmdText(client, msg, 100000); + + // Deduct cash + Hk::Player::RemoveCash(characterName, freeForAlls[systemId].entryAmount); + } + else + { + PrintUserCmdText(client, L"You have already accepted the FFA."); + } + } + } + + /** @ingroup Betting + * @brief Removes any duels with this client and handles payouts. + */ + void Betting::ProcessDuel(ClientId client) + { + auto duel = duels.begin(); + while (duel != duels.end()) + { + uint clientKiller = 0; + + if (duel->client == client) + { + clientKiller = duel->client2; + } + + if (duel->client2 == client) + { + clientKiller = duel->client; + } + + if (clientKiller == 0) + { + duel++; + continue; + } + + if (duel->accepted) + { + // Get player names + std::wstring victim = reinterpret_cast(Players.GetActiveCharacterName(client)); + std::wstring killer = reinterpret_cast(Players.GetActiveCharacterName(clientKiller)); + + // Prepare and send message + const std::wstring msg = killer + L" has won a duel against " + victim + L" for " + std::to_wstring(duel->amount) + L" credits."; + PrintLocalUserCmdText(clientKiller, msg, 10000); + + // Change cash + Hk::Player::AddCash(killer, duel->amount); + Hk::Player::RemoveCash(victim, duel->amount); + } + else + { + PrintUserCmdText(duel->client, L"Duel cancelled."); + PrintUserCmdText(duel->client2, L"Duel cancelled."); + } + duel = duels.erase(duel); + return; + } + } + + /** @ingroup Betting + * @brief This method is called when a player types /duel in an attempt to start a duel + */ + void Betting::UserCmdDuel(uint amount) + { + // Get the object the player is targetting + const auto targetShip = Hk::Player::GetTarget(client).Handle(); + + // Check ship is a player + const auto clientTarget = Hk::Client::GetClientIdByShip(targetShip).Handle(); + + // Check its a valid amount of cash + if (amount == 0) + { + PrintUserCmdText(client, + L"Must specify a cash amount. Usage: /duel " + L" e.g. /duel 5000"); + return; + } + + const std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); + + // Check the player can afford it + const auto cash = Hk::Player::GetCash(client).Handle(); + + if (amount > 0 && cash < amount) + { + PrintUserCmdText(client, L"You don't have enough credits to issue this challenge."); + return; + } + + // Do either players already have a duel? + for (const auto& duel : duels) + { + // Target already has a bet + if (duel.client == clientTarget || duel.client2 == clientTarget) + { + PrintUserCmdText(client, L"This player already has an ongoing duel."); + return; + } + // Player already has a bet + if (duel.client == client || duel.client2 == client) + { + PrintUserCmdText(client, L"You already have an ongoing duel. Type /cancel"); + return; + } + } + + // Create duel + Duel duel; + duel.client = client; + duel.client2 = clientTarget; + duel.amount = amount; + duel.accepted = false; + duels.push_back(duel); + + // Message players + const std::wstring characterName2 = reinterpret_cast(Players.GetActiveCharacterName(clientTarget)); + const std::wstring message = characterName + L" has challenged " + characterName2 + L" to a duel for " + std::to_wstring(amount) + L" credits."; + PrintLocalUserCmdText(client, message, 10000); + PrintUserCmdText(clientTarget, L"Type \"/acceptduel\" to accept."); + } + + /** @ingroup Betting + * @brief This method is called when a player types /acceptduel to accept a duel request. + */ + void Betting::UserCmdAcceptDuel() + { + // Is player in space? + if (const uint ship = Hk::Player::GetShip(client).Unwrap(); !ship) + { + PrintUserCmdText(client, L"You must be in space to accept this."); + return; + } + + for (auto& duel : duels) + { + if (duel.client2 == client) + { + // Has player already accepted the bet? + if (duel.accepted == true) + { + PrintUserCmdText(client, L"You have already accepted the challenge."); + return; + } + + // Check the player can afford it + const std::wstring characterName = reinterpret_cast(Players.GetActiveCharacterName(client)); + const auto cash = Hk::Player::GetCash(client).Unwrap(); + + if (cash < duel.amount) + { + PrintUserCmdText(client, L"You don't have enough credits to accept this challenge"); + return; + } + + duel.accepted = true; + const std::wstring message = characterName + L" has accepted the duel with " + + reinterpret_cast(Players.GetActiveCharacterName(duel.client)) + L" for " + + std::to_wstring(duel.amount) + L" credits."; + PrintLocalUserCmdText(client, message, 10000); + return; + } + } + PrintUserCmdText(client, + L"You have no duel requests. To challenge " + L"someone, target them and type /duel "); + } + + /** @ingroup Betting + * @brief This method is called when a player types /cancel to cancel a duel/ffa request. + */ + void Betting::UserCmdCancel() + { + ProcessFFA(client); + ProcessDuel(client); + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Hooks + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** @ingroup Betting + * @brief Hook for dock call. Treats a player as if they died if they were part of a duel + */ + int Betting::DockCall(const unsigned int& ship, [[maybe_unused]] const unsigned int& dock, [[maybe_unused]] const int& cancel, + [[maybe_unused]] const DOCK_HOST_RESPONSE& response) + { + if (const auto client = Hk::Client::GetClientIdByShip(ship).Unwrap(); client && Hk::Client::IsValidClientID(client)) + { + ProcessFFA(client); + ProcessDuel(client); + } + return 0; + } + + /** @ingroup Betting + * @brief Hook for disconnect. Treats a player as if they died if they were part of a duel + */ + void Betting::DisConnect(ClientId& client, [[maybe_unused]] const EFLConnection& state) + { + ProcessFFA(client); + ProcessDuel(client); + } + + /** @ingroup Betting + * @brief Hook for char info request (F1). Treats a player as if they died if they were part of a duel + */ + void Betting::CharacterInfoReq(ClientId& client, [[maybe_unused]] const bool& param2) + { + ProcessFFA(client); + ProcessDuel(client); + } + + /** @ingroup Betting + * @brief Hook for death to kick player out of duel + */ + void Betting::SendDeathMessage([[maybe_unused]] const std::wstring& message, [[maybe_unused]] const uint& system, ClientId& clientVictim, + [[maybe_unused]] const ClientId& clientKiller) + { + ProcessDuel(clientVictim); + ProcessFFA(clientVictim); + } +} // namespace Plugins + + +using namespace Plugins; DefaultDllMain(); -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Functions to hook -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +const PluginInfo Info(L"Betting", L"betting", PluginMajorVersion::VERSION_04, PluginMinorVersion::VERSION_01); + -extern "C" EXPORT void ExportPluginInfo(PluginInfo* pi) +Betting::Betting(const PluginInfo& info) : Plugin(info) { - pi->name("Betting"); - pi->shortName("betting"); - pi->mayUnload(true); - pi->returnCode(&global->returnCode); - pi->commands(&commands); - pi->versionMajor(PluginMajorVersion::VERSION_04); - pi->versionMinor(PluginMinorVersion::VERSION_00); - pi->emplaceHook(HookedCall::IEngine__SendDeathMessage, &SendDeathMessage); - pi->emplaceHook(HookedCall::IServerImpl__CharacterInfoReq, &CharacterInfoReq); - pi->emplaceHook(HookedCall::IEngine__DockCall, &DockCall); - pi->emplaceHook(HookedCall::IServerImpl__DisConnect, &DisConnect); + EmplaceHook(HookedCall::IEngine__SendDeathMessage, &Betting::SendDeathMessage, HookStep::After); + EmplaceHook(HookedCall::IServerImpl__CharacterInfoReq, &Betting::CharacterInfoReq, HookStep::After); + EmplaceHook(HookedCall::IEngine__DockCall, &Betting::DockCall, HookStep::After); + EmplaceHook(HookedCall::IServerImpl__DisConnect, &Betting::DisConnect, HookStep::After); } \ No newline at end of file diff --git a/plugins/betting/Betting.h b/plugins/betting/Betting.h deleted file mode 100644 index f835a0999..000000000 --- a/plugins/betting/Betting.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include -#include - -namespace Plugins::Betting -{ - //! A struct to hold a duel between two players. This holds the amount of cash they're betting on, and whether it's been accepted or not - struct Duel - { - uint client; - uint client2; - uint amount; - bool accepted; - }; - - //! A struct to hold a contestant for a Free-For-All - struct Contestant - { - bool accepted; - bool loser; - }; - - //! A struct to hold a Free-For-All competition. This holds the contestants, how much it costs to enter, and the total pot to be won by the eventual winner - struct FreeForAll - { - std::map contestants; - uint entryAmount; - uint pot; - }; - - //! Global data for this plugin - struct Global final - { - ReturnCode returnCode = ReturnCode::Default; - std::list duels; - std::map freeForAlls; // uint is systemId - }; -} // namespace Plugins::Betting diff --git a/plugins/betting/Betting.hpp b/plugins/betting/Betting.hpp new file mode 100644 index 000000000..d103cd40b --- /dev/null +++ b/plugins/betting/Betting.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + +namespace Plugins +{ + class Betting final : public Plugin, public AbstractUserCommandProcessor + { + void ProcessFFA(ClientId client); + void ProcessDuel(ClientId client); + int DockCall(const unsigned int& ship, [[maybe_unused]] const unsigned int& dock, [[maybe_unused]] const int& cancel, + [[maybe_unused]] const DOCK_HOST_RESPONSE& response); + void DisConnect(ClientId& client, [[maybe_unused]] const EFLConnection& state); + void CharacterInfoReq(ClientId& client, [[maybe_unused]] const bool& param2); + void SendDeathMessage([[maybe_unused]] const std::wstring& message, [[maybe_unused]] const uint& system, ClientId& clientVictim, + [[maybe_unused]] const ClientId& clientKiller); + + + //! A struct to hold a duel between two players. This holds the amount of cash they're betting on, and whether it's been accepted or not + struct Duel + { + uint client; + uint client2; + uint amount; + bool accepted; + }; + + //! A struct to hold a contestant for a Free-For-All + struct Contestant + { + bool accepted; + bool loser; + }; + + //! A struct to hold a Free-For-All competition. This holds the contestants, how much it costs to enter, and the total pot to be won by the eventual + //! winner + struct FreeForAll + { + std::map contestants; + uint entryAmount; + uint pot; + }; + + //! Global data for this plugin + + ReturnCode returnCode = ReturnCode::Default; + std::list duels; + std::map freeForAlls; // uint is systemId + + + // User Commands. + void UserCmdStartFreeForAll(uint amount); + void UserCmdAcceptFFA(); + void UserCmdDuel(uint amount); + void UserCmdAcceptDuel(); + void UserCmdCancel(); + + constexpr inline static std::array, 5> commands = { + {AddCommand(Betting, L"/ffa", UserCmdStartFreeForAll, L"Create an ffa and send an invite to everyone in the system. Winner gets the pot."), + AddCommand(Betting, L"/acceptffa", UserCmdAcceptFFA, L"Accept the current ffa request."), + AddCommand(Betting, L"/duel", UserCmdDuel, L"Create a duel request to the targeted player. Winner gets the pot."), + AddCommand(Betting, L"/acceptduel", UserCmdAcceptDuel, L"Accepts the current duel request."), + AddCommand(Betting, L"/cancel", UserCmdCancel, L"Cancel the current duel/ffa request.")} + }; + + public: + explicit Betting(const PluginInfo& info); + + }; +} // namespace Plugins \ No newline at end of file diff --git a/plugins/betting/betting.vcxproj b/plugins/betting/betting.vcxproj index d0f84b971..c97d455ba 100644 --- a/plugins/betting/betting.vcxproj +++ b/plugins/betting/betting.vcxproj @@ -80,7 +80,7 @@ - + diff --git a/source/API/FLServer/Player.cpp b/source/API/FLServer/Player.cpp index 78fd91afc..ce1b88528 100644 --- a/source/API/FLServer/Player.cpp +++ b/source/API/FLServer/Player.cpp @@ -1427,9 +1427,9 @@ namespace Hk::Player } // Update FLHook's lists to make anticheat pleased. - if (&equip != &Players[client].lShadowEquipDescList.equip) + if (&equip != &Players[client].shadowEquipDescList.equip) { - Players[client].lShadowEquipDescList.equip = equip; + Players[client].shadowEquipDescList.equip = equip; } if (&equip != &Players[client].equipDescList.equip) @@ -1515,7 +1515,7 @@ namespace Hk::Player ed.id = Players[client].lastEquipId; ed.count = 1; ed.archId = goodId; - Players[client].lShadowEquipDescList.add_equipment_item(ed, false); + Players[client].shadowEquipDescList.add_equipment_item(ed, false); return { {} }; }