diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.cpp index 19111490..f84e692b 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.cpp @@ -1,6 +1,5 @@ #include "CDActivitiesTable.h" - void CDActivitiesTable::LoadValuesFromDatabase() { // First, get the size of the table uint32_t size = 0; @@ -56,3 +55,13 @@ std::vector CDActivitiesTable::Query(std::function CDActivitiesTable::GetActivity(const uint32_t activityID) { + auto& entries = GetEntries(); + for (const auto& entry : entries) { + if (entry.ActivityID == activityID) { + return entry; + } + } + return std::nullopt; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.h index 3e1d4c37..86731bba 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDActivitiesTable.h @@ -2,6 +2,7 @@ // Custom Classes #include "CDTable.h" +#include struct CDActivities { uint32_t ActivityID; @@ -31,4 +32,5 @@ public: // Queries the table with a custom "where" clause std::vector Query(std::function predicate); + std::optional GetActivity(const uint32_t activityID); }; diff --git a/dGame/dComponents/ActivityComponent.h b/dGame/dComponents/ActivityComponent.h index dc249068..bcaa5bfc 100644 --- a/dGame/dComponents/ActivityComponent.h +++ b/dGame/dComponents/ActivityComponent.h @@ -215,6 +215,10 @@ public: */ int GetActivityID() { return m_ActivityInfo.ActivityID; } + // Whether or not team loot should be dropped on death for this activity + // if true, and a player is supposed to get loot, they are skipped + bool GetNoTeamLootOnDeath() const { return m_ActivityInfo.noTeamLootOnDeath; } + /** * Returns if this activity has a lobby, e.g. if it needs to instance players to some other map * @return true if this activity has a lobby, false otherwise diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 47aa0c90..86969c5a 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -88,6 +88,7 @@ DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t compone RegisterMsg(this, &DestroyableComponent::OnGetObjectReportInfo); RegisterMsg(this, &DestroyableComponent::OnSetFaction); + RegisterMsg(this, &DestroyableComponent::OnIsDead); } DestroyableComponent::~DestroyableComponent() { @@ -1191,3 +1192,9 @@ bool DestroyableComponent::OnSetFaction(GameMessages::GameMsg& msg) { SetFaction(modifyFaction.factionID, modifyFaction.bIgnoreChecks); return true; } + +bool DestroyableComponent::OnIsDead(GameMessages::GameMsg& msg) { + auto& isDeadMsg = static_cast(msg); + isDeadMsg.bDead = m_IsDead || (GetHealth() == 0 && GetArmor() == 0); + return true; +} diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 9b3e46af..0b4d2ba8 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -472,6 +472,7 @@ public: bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); bool OnSetFaction(GameMessages::GameMsg& msg); + bool OnIsDead(GameMessages::GameMsg& msg); void SetIsDead(const bool value) { m_IsDead = value; } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 3ae9ffce..3b27b9f1 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -955,5 +955,11 @@ namespace GameMessages { LWOOBJID childID{}; }; + + struct IsDead : public GameMsg { + IsDead() : GameMsg(MessageType::Game::IS_DEAD) {} + + bool bDead{}; + }; }; #endif // GAMEMESSAGES_H diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index ae6f6177..2fe7b796 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -21,6 +21,8 @@ #include "TeamManager.h" #include "CDObjectsTable.h" #include "ObjectIDManager.h" +#include "CDActivitiesTable.h" +#include "ScriptedActivityComponent.h" namespace { std::unordered_set CachedMatrices; @@ -142,13 +144,17 @@ void DropFactionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { // Drops 1 token for each player on a team // token drops are always given to every player on the team. -void DropFactionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { +void DropFactionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg, const bool noTeamLootOnDeath = false) { for (const auto member : team.members) { GameMessages::GetPosition memberPosMsg{}; memberPosMsg.target = member; memberPosMsg.Send(); if (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS) continue; + GameMessages::IsDead isDeadMsg{}; + // Skip dead players + if (noTeamLootOnDeath && isDeadMsg.Send(member) && isDeadMsg.bDead) continue; + GameMessages::GetFactionTokenType factionTokenType{}; factionTokenType.target = member; // If we're not in a faction, this message will return false @@ -186,7 +192,7 @@ void DropPowerupLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { // Drop the power up with no owner // Power ups can be picked up by anyone on a team, however unlike actual loot items, // if multiple clients say they picked one up, we let them pick it up. -void DropPowerupLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { +void DropPowerupLoot(const Team& team, GameMessages::DropClientLoot& lootMsg, const bool noTeamLootOnDeath = false) { lootMsg.lootID = ObjectIDManager::GenerateObjectID(); lootMsg.ownerID = LWOOBJID_EMPTY; // By setting ownerID to empty, any client that gets this DropClientLoot message can pick up the item. CalcFinalDropPos(lootMsg); @@ -198,6 +204,10 @@ void DropPowerupLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { memberPosMsg.Send(); if (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS) continue; + GameMessages::IsDead isDeadMsg{}; + // Skip dead players + if (noTeamLootOnDeath && isDeadMsg.Send(member) && isDeadMsg.bDead) continue; + lootMsg.target = member; // By sending this message with the same ID to all players on the team, all players on the team are allowed to pick it up. lootMsg.Send(); @@ -230,7 +240,7 @@ void DropMissionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { // Check if the item needs to be dropped for anyone on the team // Only players who need the item will have it dropped -void DropMissionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { +void DropMissionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg, const bool noTeamLootOnDeath = false) { GameMessages::MissionNeedsLot needMsg{}; needMsg.item = lootMsg.item; for (const auto member : team.members) { @@ -239,6 +249,10 @@ void DropMissionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { memberPosMsg.Send(); if (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS) continue; + GameMessages::IsDead isDeadMsg{}; + // Skip dead players + if (noTeamLootOnDeath && isDeadMsg.Send(member) && isDeadMsg.bDead) continue; + needMsg.target = member; // Will return false if the item is not required if (needMsg.Send()) { @@ -274,19 +288,24 @@ void DropRegularLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { // Drop a regular piece of loot. // Most items will go through this. // Finds the next loot owner on the team the is in range of the kill and gives them this reward. -void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg) { +void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg, const bool noTeamLootOnDeath = false) { auto earningPlayer = LWOOBJID_EMPTY; lootMsg.lootID = ObjectIDManager::GenerateObjectID(); CalcFinalDropPos(lootMsg); GameMessages::GetPosition memberPosMsg{}; + GameMessages::IsDead isDeadMsg{}; // Find the next loot owner. Eventually this will run into the `player` passed into this function, since those will // have the same ID, this loop will only ever run at most 4 times. do { earningPlayer = team.GetNextLootOwner(); memberPosMsg.target = earningPlayer; memberPosMsg.Send(); - } while (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS); - + if (noTeamLootOnDeath) { + isDeadMsg.target = earningPlayer; + // Skip dead players + isDeadMsg.Send(); + } + } while (isDeadMsg.bDead || (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS)); if (team.lootOption == 0 /* Shared loot */) { lootMsg.target = earningPlayer; lootMsg.ownerID = earningPlayer; @@ -304,7 +323,7 @@ void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg) { DistrbuteMsgToTeam(lootMsg, team); } -void DropLoot(Entity* player, const LWOOBJID source, const std::map& rolledItems, uint32_t minCoins, uint32_t maxCoins) { +void DropLoot(Entity* player, const LWOOBJID source, const std::map& rolledItems, uint32_t minCoins, uint32_t maxCoins, const bool noTeamLootOnDeath) { player = player->GetOwner(); // if the owner is overwritten, we collect that here const auto playerID = player->GetObjectID(); if (!player || !player->IsPlayer()) { @@ -342,45 +361,53 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::mapGetByID(lootLot); if (lootLot == TOKEN_PROXY) { - team ? DropFactionLoot(*team, lootMsg) : DropFactionLoot(*player, lootMsg); + team ? DropFactionLoot(*team, lootMsg, noTeamLootOnDeath) : DropFactionLoot(*player, lootMsg); } else if (info.table.MissionDrop) { - team ? DropMissionLoot(*team, lootMsg) : DropMissionLoot(*player, lootMsg); + team ? DropMissionLoot(*team, lootMsg, noTeamLootOnDeath) : DropMissionLoot(*player, lootMsg); } else if (object.type == "Powerup") { - team ? DropPowerupLoot(*team, lootMsg) : DropPowerupLoot(*player, lootMsg); + team ? DropPowerupLoot(*team, lootMsg, noTeamLootOnDeath) : DropPowerupLoot(*player, lootMsg); } else { - team ? DropRegularLoot(*team, lootMsg) : DropRegularLoot(*player, lootMsg); + team ? DropRegularLoot(*team, lootMsg, noTeamLootOnDeath) : DropRegularLoot(*player, lootMsg); } } } - // Coin roll is divided up between the members, rounded up, then dropped for each player - const uint32_t coinRoll = static_cast(minCoins + GeneralUtils::GenerateRandomNumber(0, 1) * (maxCoins - minCoins)); - const auto droppedCoins = team ? std::ceil(static_cast(coinRoll) / team->members.size()) : coinRoll; + // Filter out dead player if we need to + std::vector lootEarners; if (team) { - for (auto member : team->members) { - GameMessages::DropClientLoot lootMsg{}; - lootMsg.target = member; - lootMsg.ownerID = member; - lootMsg.currency = droppedCoins; - lootMsg.spawnPos = spawnPosition; - lootMsg.sourceID = source; - lootMsg.item = LOT_NULL; - CalcFinalDropPos(lootMsg); - lootMsg.Send(); - const auto* const memberEntity = Game::entityManager->GetEntity(member); - if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + if (noTeamLootOnDeath) { + for (const auto member : team->members) { + GameMessages::IsDead isDeadMsg{}; + isDeadMsg.target = member; + if (isDeadMsg.Send() && !isDeadMsg.bDead) { + lootEarners.push_back(member); + } + } + } else { + lootEarners = team->members; } } else { + lootEarners.push_back(playerID); + } + + // Coin roll is divided up between the members, rounded up, then dropped for each player + const uint32_t coinRoll = static_cast(minCoins + GeneralUtils::GenerateRandomNumber(0, 1) * (maxCoins - minCoins)); + // Just in case its empty don't allow divide by 0 + const auto droppedCoins = lootEarners.empty() ? coinRoll : static_cast(std::ceil(static_cast(coinRoll) / lootEarners.size())); + + // Drops coins for each alive member of a team (or just a player) + for (auto member : lootEarners) { GameMessages::DropClientLoot lootMsg{}; - lootMsg.target = playerID; - lootMsg.ownerID = playerID; + lootMsg.target = member; + lootMsg.ownerID = member; lootMsg.currency = droppedCoins; lootMsg.spawnPos = spawnPosition; lootMsg.sourceID = source; lootMsg.item = LOT_NULL; CalcFinalDropPos(lootMsg); lootMsg.Send(); - lootMsg.Send(player->GetSystemAddress()); + const auto* const memberEntity = Game::entityManager->GetEntity(member); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); } } @@ -533,6 +560,9 @@ void Loot::GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t acti void Loot::DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins) { player = player->GetOwner(); // if the owner is overwritten, we collect that here + auto* scriptedActivityComponent = Game::entityManager->GetZoneControlEntity()->GetComponent(); + const bool noTeamLootOnDeath = scriptedActivityComponent ? scriptedActivityComponent->GetNoTeamLootOnDeath() : false; + auto* inventoryComponent = player->GetComponent(); if (!inventoryComponent) @@ -540,7 +570,7 @@ void Loot::DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, const auto result = ::RollLootMatrix(matrixIndex); - ::DropLoot(player, source, result, minCoins, maxCoins); + ::DropLoot(player, source, result, minCoins, maxCoins, noTeamLootOnDeath); } void Loot::DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating) {