diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 45136774..fee53aef 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -84,6 +84,8 @@ #include "GhostComponent.h" #include "AchievementVendorComponent.h" #include "VanityUtilities.h" +#include "ObjectIDManager.h" +#include "ePlayerFlag.h" // Table includes #include "CDComponentsRegistryTable.h" @@ -192,7 +194,10 @@ Entity::~Entity() { } void Entity::Initialize() { - RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &Entity::MsgRequestServerObjectInfo); + RegisterMsg(this, &Entity::MsgRequestServerObjectInfo); + RegisterMsg(this, &Entity::MsgDropClientLoot); + RegisterMsg(this, &Entity::MsgGetFactionTokenType); + RegisterMsg(this, &Entity::MsgPickupItem); /** * Setup trigger */ @@ -287,7 +292,7 @@ void Entity::Initialize() { AddComponent(lupExhibitID); } - const auto racingControlID =compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL); + const auto racingControlID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL); if (racingControlID > 0) { AddComponent(racingControlID); } @@ -1663,7 +1668,7 @@ void Entity::AddLootItem(const Loot::Info& info) const { auto* const characterComponent = GetComponent(); if (!characterComponent) return; - + LOG("Player %llu has been allowed to pickup %i with id %llu", m_ObjectID, info.lot, info.id); auto& droppedLoot = characterComponent->GetDroppedLoot(); droppedLoot[info.id] = info; } @@ -2275,3 +2280,73 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) { if (client) GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, client->GetSystemAddress()); return true; } + +bool Entity::MsgDropClientLoot(GameMessages::GameMsg& msg) { + auto& dropLootMsg = static_cast(msg); + + if (dropLootMsg.item != LOT_NULL && dropLootMsg.item != 0) { + Loot::Info info{ + .id = dropLootMsg.lootID, + .lot = dropLootMsg.item, + .count = dropLootMsg.count, + }; + AddLootItem(info); + } + + if (dropLootMsg.item == LOT_NULL && dropLootMsg.currency != 0) { + RegisterCoinDrop(dropLootMsg.currency); + } + + return true; +} + +bool Entity::MsgGetFlag(GameMessages::GameMsg& msg) { + auto& flagMsg = static_cast(msg); + if (m_Character) flagMsg.flag = m_Character->GetPlayerFlag(flagMsg.flagID); + return true; +} +bool Entity::MsgGetFactionTokenType(GameMessages::GameMsg& msg) { + auto& tokenMsg = static_cast(msg); + GameMessages::GetFlag getFlagMsg{}; + + getFlagMsg.flagID = ePlayerFlag::ASSEMBLY_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8318; + + getFlagMsg.flagID = ePlayerFlag::SENTINEL_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8319; + + getFlagMsg.flagID = ePlayerFlag::PARADOX_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8320; + + getFlagMsg.flagID = ePlayerFlag::VENTURE_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8321; + + LOG("Returning token type %i", tokenMsg.tokenType); + return tokenMsg.tokenType != LOT_NULL; +} + +bool Entity::MsgPickupItem(GameMessages::GameMsg& msg) { + auto& pickupItemMsg = static_cast(msg); + if (GetObjectID() == pickupItemMsg.lootOwnerID) { + PickupItem(pickupItemMsg.lootID); + } else { + auto* const characterComponent = GetComponent(); + if (!characterComponent) return false; + auto& droppedLoot = characterComponent->GetDroppedLoot(); + const auto it = droppedLoot.find(pickupItemMsg.lootID); + if (it != droppedLoot.end()) { + CDObjectsTable* objectsTable = CDClientManager::GetTable(); + const CDObjects& object = objectsTable->GetByID(it->second.lot); + if (object.id != 0 && object.type == "Powerup") { + return false; // Let powerups be duplicated + } + } + droppedLoot.erase(pickupItemMsg.lootID); + } + + return true; +} diff --git a/dGame/Entity.h b/dGame/Entity.h index f9498854..6d50efa7 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -176,6 +176,10 @@ public: void AddComponent(eReplicaComponentType componentId, Component* component); bool MsgRequestServerObjectInfo(GameMessages::GameMsg& msg); + bool MsgDropClientLoot(GameMessages::GameMsg& msg); + bool MsgGetFlag(GameMessages::GameMsg& msg); + bool MsgGetFactionTokenType(GameMessages::GameMsg& msg); + bool MsgPickupItem(GameMessages::GameMsg& msg); // This is expceted to never return nullptr, an assert checks this. CppScripts::Script* const GetScript() const; @@ -342,6 +346,12 @@ public: RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1)); } + template + inline void RegisterMsg(auto* self, const auto handler) { + T msg; + RegisterMsg(msg.msgId, self, handler); + } + /** * @brief The observable for player entity position updates. */ @@ -600,5 +610,5 @@ auto Entity::GetComponents() const { template auto Entity::GetComponentsMut() const { - return std::tuple{GetComponent()...}; + return std::tuple{ GetComponent()... }; } diff --git a/dGame/TeamManager.cpp b/dGame/TeamManager.cpp index abb20c48..89eff408 100644 --- a/dGame/TeamManager.cpp +++ b/dGame/TeamManager.cpp @@ -9,6 +9,16 @@ Team::Team() { lootOption = Game::config->GetValue("default_team_loot") == "0" ? 0 : 1; } +LWOOBJID Team::GetNextLootOwner() { + lootRound++; + + if (lootRound >= members.size()) { + lootRound = 0; + } + + return members[lootRound]; +} + TeamManager::TeamManager() { } diff --git a/dGame/TeamManager.h b/dGame/TeamManager.h index 5d4716f8..1c99a9d2 100644 --- a/dGame/TeamManager.h +++ b/dGame/TeamManager.h @@ -4,6 +4,8 @@ struct Team { Team(); + + LWOOBJID GetNextLootOwner(); LWOOBJID teamID = LWOOBJID_EMPTY; char lootOption = 0; std::vector members{}; diff --git a/dGame/dComponents/ActivityComponent.cpp b/dGame/dComponents/ActivityComponent.cpp index 6de50bed..7567e20b 100644 --- a/dGame/dComponents/ActivityComponent.cpp +++ b/dGame/dComponents/ActivityComponent.cpp @@ -45,33 +45,6 @@ ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Comp m_ActivityID = parent->GetVar(u"activityID"); LoadActivityData(m_ActivityID); } - - auto* destroyableComponent = m_Parent->GetComponent(); - - if (destroyableComponent) { - // First lookup the loot matrix id for this component id. - CDActivityRewardsTable* activityRewardsTable = CDClientManager::GetTable(); - std::vector activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) {return (entry.LootMatrixIndex == destroyableComponent->GetLootMatrixID()); }); - - uint32_t startingLMI = 0; - - // If we have one, set the starting loot matrix id to that. - if (activityRewards.size() > 0) { - startingLMI = activityRewards[0].LootMatrixIndex; - } - - if (startingLMI > 0) { - // We may have more than 1 loot matrix index to use depending ont the size of the team that is looting the activity. - // So this logic will get the rest of the loot matrix indices for this activity. - - std::vector objectTemplateActivities = activityRewardsTable->Query([=](CDActivityRewards entry) {return (activityRewards[0].objectTemplate == entry.objectTemplate); }); - for (const auto& item : objectTemplateActivities) { - if (item.activityRating > 0 && item.activityRating < 5) { - m_ActivityLootMatrices.insert({ item.activityRating, item.LootMatrixIndex }); - } - } - } - } } void ActivityComponent::LoadActivityData(const int32_t activityId) { CDActivitiesTable* activitiesTable = CDClientManager::GetTable(); @@ -698,10 +671,6 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { } } - auto& lootMatrices = activityInfo.PushDebug("Loot Matrices"); - for (const auto& [activityRating, lootMatrixID] : m_ActivityLootMatrices) { - lootMatrices.PushDebug("Loot Matrix " + std::to_string(activityRating)) = lootMatrixID; - } activityInfo.PushDebug("ActivityID") = m_ActivityID; return true; } diff --git a/dGame/dComponents/ActivityComponent.h b/dGame/dComponents/ActivityComponent.h index 08ceb6da..dc249068 100644 --- a/dGame/dComponents/ActivityComponent.h +++ b/dGame/dComponents/ActivityComponent.h @@ -341,12 +341,6 @@ public: */ void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; }; - /** - * Returns the LMI that this activity points to for a team size - * @param teamSize the team size to get the LMI for - * @return the LMI that this activity points to for a team size - */ - uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; } private: bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); @@ -370,11 +364,6 @@ private: */ std::vector m_ActivityPlayers; - /** - * LMIs for team sizes - */ - std::unordered_map m_ActivityLootMatrices; - /** * The activity id */ diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 49b88cf4..8b654f9f 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -756,36 +756,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType //NANI?! if (!isPlayer) { if (owner != nullptr) { - auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID()); - - if (team != nullptr && m_Parent->GetComponent() != nullptr) { - LWOOBJID specificOwner = LWOOBJID_EMPTY; - auto* scriptedActivityComponent = m_Parent->GetComponent(); - uint32_t teamSize = team->members.size(); - uint32_t lootMatrixId = GetLootMatrixID(); - - if (scriptedActivityComponent) { - lootMatrixId = scriptedActivityComponent->GetLootMatrixForTeamSize(teamSize); - } - - if (team->lootOption == 0) { // Round robin - specificOwner = TeamManager::Instance()->GetNextLootOwner(team); - - auto* member = Game::entityManager->GetEntity(specificOwner); - - if (member) Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins()); - } else { - for (const auto memberId : team->members) { // Free for all - auto* member = Game::entityManager->GetEntity(memberId); - - if (member == nullptr) continue; - - Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins()); - } - } - } else { // drop loot for non team user - Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins()); - } + Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins()); } } else { //Check if this zone allows coin drops @@ -1046,8 +1017,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { auto maxHealth = GetMaxHealth(); const auto uscoreMultiplier = Game::entityManager->GetHardcoreUscoreEnemiesMultiplier(); const bool isUscoreReducedLot = - Game::entityManager->GetHardcoreUscoreReducedLots().contains(lot) || - Game::entityManager->GetHardcoreUscoreReduced(); + Game::entityManager->GetHardcoreUscoreReducedLots().contains(lot) || + Game::entityManager->GetHardcoreUscoreReduced(); const auto uscoreReduction = isUscoreReducedLot ? Game::entityManager->GetHardcoreUscoreReduction() : 1.0f; int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier() * uscoreReduction; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 5361eb6b..6bd690f8 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -443,7 +443,7 @@ Item* InventoryComponent::FindItemBySubKey(LWOOBJID id, eInventoryType inventory } } -bool InventoryComponent::HasSpaceForLoot(const std::unordered_map& loot) { +bool InventoryComponent::HasSpaceForLoot(const Loot::Return& loot) { std::unordered_map spaceOffset{}; uint32_t slotsNeeded = 0; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 6e0fdba7..3728c428 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -22,6 +22,7 @@ #include "eInventoryType.h" #include "eReplicaComponentType.h" #include "eLootSourceType.h" +#include "Loot.h" class Entity; class ItemSet; @@ -200,7 +201,7 @@ public: * @param loot a map of items to add and how many to add * @return whether the entity has enough space for all the items */ - bool HasSpaceForLoot(const std::unordered_map& loot); + bool HasSpaceForLoot(const Loot::Return& loot); /** * Equips an item in the specified slot diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 6c85f908..b3a503d5 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -31,6 +31,8 @@ MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue(); RegisterMsg(this, &MissionComponent::OnGetObjectReportInfo); + RegisterMsg(this, &MissionComponent::OnGetMissionState); + RegisterMsg(this, &MissionComponent::OnMissionNeedsLot); } //! Destructor @@ -733,3 +735,15 @@ bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { return true; } + +bool MissionComponent::OnGetMissionState(GameMessages::GameMsg& msg) { + auto misState = static_cast(msg); + misState.missionState = GetMissionState(misState.missionID); + + return true; +} + +bool MissionComponent::OnMissionNeedsLot(GameMessages::GameMsg& msg) { + const auto& needMsg = static_cast(msg); + return RequiresItem(needMsg.item); +} diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 53f09d41..a5cd058a 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -172,6 +172,8 @@ public: void ResetMission(const int32_t missionId); private: bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); + bool OnGetMissionState(GameMessages::GameMsg& msg); + bool OnMissionNeedsLot(GameMessages::GameMsg& msg); /** * All the missions owned by this entity, mapped by mission ID */ diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index eae01e56..1e89e2a9 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -48,6 +48,7 @@ namespace { { REQUEST_USE, []() { return std::make_unique(); }}, { REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique(); } }, { SHOOTING_GALLERY_FIRE, []() { return std::make_unique(); } }, + { PICKUP_ITEM, []() { return std::make_unique(); } }, }; }; @@ -281,11 +282,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System break; } - case MessageType::Game::PICKUP_ITEM: { - GameMessages::HandlePickupItem(inStream, entity); - break; - } - case MessageType::Game::RESURRECT: { GameMessages::HandleResurrect(inStream, entity); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c23b4988..6855778d 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1103,52 +1103,6 @@ void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, finalPosition = NiPoint3(static_cast(spawnPos.GetX() + sin_v), spawnPos.GetY(), static_cast(spawnPos.GetZ() + cos_v)); } - - //Write data to packet & send: - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::DROP_CLIENT_LOOT); - - bitStream.Write(bUsePosition); - - bitStream.Write(finalPosition != NiPoint3Constant::ZERO); - if (finalPosition != NiPoint3Constant::ZERO) bitStream.Write(finalPosition); - - bitStream.Write(currency); - bitStream.Write(item); - bitStream.Write(lootID); - bitStream.Write(owner); - bitStream.Write(sourceID); - - bitStream.Write(spawnPos != NiPoint3Constant::ZERO); - if (spawnPos != NiPoint3Constant::ZERO) bitStream.Write(spawnPos); - - auto* team = TeamManager::Instance()->GetTeam(owner); - - // Currency and powerups should not sync - if (team != nullptr && currency == 0) { - CDObjectsTable* objectsTable = CDClientManager::GetTable(); - - const CDObjects& object = objectsTable->GetByID(item); - - if (object.type != "Powerup") { - for (const auto memberId : team->members) { - auto* member = Game::entityManager->GetEntity(memberId); - - if (member == nullptr) continue; - - SystemAddress sysAddr = member->GetSystemAddress(); - SEND_PACKET; - } - - return; - } - } - - SystemAddress sysAddr = entity->GetSystemAddress(); - SEND_PACKET; } void GameMessages::SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme) { @@ -5725,27 +5679,6 @@ void GameMessages::HandleModularBuildMoveAndEquip(RakNet::BitStream& inStream, E inv->MoveItemToInventory(item, eInventoryType::MODELS, 1, false, true); } -void GameMessages::HandlePickupItem(RakNet::BitStream& inStream, Entity* entity) { - LWOOBJID lootObjectID; - LWOOBJID playerID; - inStream.Read(lootObjectID); - inStream.Read(playerID); - - entity->PickupItem(lootObjectID); - - auto* team = TeamManager::Instance()->GetTeam(entity->GetObjectID()); - - if (team != nullptr) { - for (const auto memberId : team->members) { - auto* member = Game::entityManager->GetEntity(memberId); - - if (member == nullptr || memberId == playerID) continue; - - SendTeamPickupItem(lootObjectID, lootObjectID, playerID, member->GetSystemAddress()); - } - } -} - void GameMessages::HandleResurrect(RakNet::BitStream& inStream, Entity* entity) { bool immediate = inStream.ReadBit(); @@ -6329,6 +6262,11 @@ namespace GameMessages { return Game::entityManager->SendMessage(*this); } + bool GameMsg::Send(const LWOOBJID _target) { + target = _target; + return Send(); + } + void GameMsg::Send(const SystemAddress& sysAddr) const { CBITSTREAM; CMSGHEADER; @@ -6496,4 +6434,49 @@ namespace GameMessages { stream.Write(emoteID); stream.Write(targetID); } + + void DropClientLoot::Serialize(RakNet::BitStream& stream) const { + stream.Write(bUsePosition); + + stream.Write(finalPosition != NiPoint3Constant::ZERO); + if (finalPosition != NiPoint3Constant::ZERO) stream.Write(finalPosition); + + stream.Write(currency); + stream.Write(item); + stream.Write(lootID); + stream.Write(ownerID); + stream.Write(sourceID); + + stream.Write(spawnPos != NiPoint3Constant::ZERO); + if (spawnPos != NiPoint3Constant::ZERO) stream.Write(spawnPos); + } + + bool PickupItem::Deserialize(RakNet::BitStream& stream) { + if (!stream.Read(lootID)) return false; + if (!stream.Read(lootOwnerID)) return false; + return true; + } + + void PickupItem::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* team = TeamManager::Instance()->GetTeam(entity.GetObjectID()); + LOG("Has team %i picking up %llu:%llu", team != nullptr, lootID, lootOwnerID); + if (team) { + for (const auto memberId : team->members) { + this->Send(memberId); + TeamPickupItem teamPickupMsg{}; + teamPickupMsg.target = lootID; + teamPickupMsg.lootID = lootID; + teamPickupMsg.lootOwnerID = lootOwnerID; + const auto* const memberEntity = Game::entityManager->GetEntity(memberId); + if (memberEntity) teamPickupMsg.Send(memberEntity->GetSystemAddress()); + } + } else { + entity.PickupItem(lootID); + } + } + + void TeamPickupItem::Serialize(RakNet::BitStream& stream) const { + stream.Write(lootID); + stream.Write(lootOwnerID); + } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 08ca677f..4f9ce78b 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -43,6 +43,7 @@ enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; +enum class eMissionState : int; enum class eCameraTargetCyclingMode : int32_t { ALLOW_CYCLE_TEAMMATES, @@ -57,6 +58,7 @@ namespace GameMessages { // Sends a message to the entity manager to route to the target bool Send(); + bool Send(const LWOOBJID _target); // Sends the message to the specified client or // all clients if UNASSIGNED_SYSTEM_ADDRESS is specified @@ -850,9 +852,9 @@ namespace GameMessages { struct EmotePlayed : public GameMsg { EmotePlayed() : GameMsg(MessageType::Game::EMOTE_PLAYED), emoteID(0), targetID(0) {} - + void Serialize(RakNet::BitStream& stream) const override; - + int32_t emoteID; LWOOBJID targetID; }; @@ -870,5 +872,65 @@ namespace GameMessages { bool bIgnoreChecks{ false }; }; + + struct DropClientLoot : public GameMsg { + DropClientLoot() : GameMsg(MessageType::Game::DROP_CLIENT_LOOT) {} + + void Serialize(RakNet::BitStream& stream) const override; + LWOOBJID sourceID{ LWOOBJID_EMPTY }; + LOT item{ LOT_NULL }; + int32_t currency{}; + NiPoint3 spawnPos{}; + NiPoint3 finalPosition{}; + int32_t count{}; + bool bUsePosition{}; + LWOOBJID lootID{ LWOOBJID_EMPTY }; + LWOOBJID ownerID{ LWOOBJID_EMPTY }; + }; + + struct GetMissionState : public GameMsg { + GetMissionState() : GameMsg(MessageType::Game::GET_MISSION_STATE) {} + + int32_t missionID{}; + eMissionState missionState{}; + bool cooldownInfoRequested{}; + bool cooldownFinished{}; + }; + + struct GetFlag : public GameMsg { + GetFlag() : GameMsg(MessageType::Game::GET_FLAG) {} + + uint32_t flagID{}; + bool flag{}; + }; + + struct GetFactionTokenType : public GameMsg { + GetFactionTokenType() : GameMsg(MessageType::Game::GET_FACTION_TOKEN_TYPE) {} + + LOT tokenType{ LOT_NULL }; + }; + + struct MissionNeedsLot : public GameMsg { + MissionNeedsLot() : GameMsg(MessageType::Game::MISSION_NEEDS_LOT) {} + + LOT item{}; + }; + + struct PickupItem : public GameMsg { + PickupItem() : GameMsg(MessageType::Game::PICKUP_ITEM) {} + + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + bool Deserialize(RakNet::BitStream& stream) override; + LWOOBJID lootID{}; + LWOOBJID lootOwnerID{}; + }; + + struct TeamPickupItem : public GameMsg { + TeamPickupItem() : GameMsg(MessageType::Game::TEAM_PICKUP_ITEM) {} + + void Serialize(RakNet::BitStream& stream) const override; + LWOOBJID lootID{}; + LWOOBJID lootOwnerID{}; + }; }; #endif // GAMEMESSAGES_H diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index b39a1f73..7ee27f01 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -343,9 +343,9 @@ void Item::UseNonEquip(Item* item) { if (this->GetPreconditionExpression()->Check(playerInventoryComponent->GetParent())) { auto* entityParent = playerInventoryComponent->GetParent(); // Roll the loot for all the packages then see if it all fits. If it fits, give it to the player, otherwise don't. - std::unordered_map rolledLoot{}; + Loot::Return rolledLoot{}; for (auto& pack : packages) { - auto thisPackage = Loot::RollLootMatrix(entityParent, pack.LootMatrixIndex); + const auto thisPackage = Loot::RollLootMatrix(entityParent, pack.LootMatrixIndex); for (auto& loot : thisPackage) { // If we already rolled this lot, add it to the existing one, otherwise create a new entry. auto existingLoot = rolledLoot.find(loot.first); @@ -356,6 +356,7 @@ void Item::UseNonEquip(Item* item) { } } } + if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) { Loot::GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::CONSUMPTION); item->SetCount(item->GetCount() - 1); diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index 5cbdf2d4..656ff61f 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -18,131 +18,27 @@ #include "MissionComponent.h" #include "eMissionState.h" #include "eReplicaComponentType.h" +#include "TeamManager.h" +#include "CDObjectsTable.h" +#include "ObjectIDManager.h" namespace { std::unordered_set CachedMatrices; + constexpr float g_MAX_DROP_RADIUS = 700.0f; } -void Loot::CacheMatrix(uint32_t matrixIndex) { - if (CachedMatrices.contains(matrixIndex)) return; +struct LootDropInfo { + CDLootTable table{}; + uint32_t count{ 0 }; +}; - CachedMatrices.insert(matrixIndex); +std::map RollLootMatrix(uint32_t matrixIndex) { CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); CDLootTableTable* lootTableTable = CDClientManager::GetTable(); CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); - - const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); - - for (const auto& entry : matrix) { - const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); - const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); - for (const auto& loot : lootTable) { - uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); - uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; - } - } -} - -std::unordered_map Loot::RollLootMatrix(Entity* player, uint32_t matrixIndex) { - CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); - CDLootTableTable* lootTableTable = CDClientManager::GetTable(); - CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); - auto* missionComponent = player->GetComponent(); - - std::unordered_map drops; - - if (missionComponent == nullptr) return drops; - - const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); - - for (const auto& entry : matrix) { - if (GeneralUtils::GenerateRandomNumber(0, 1) < entry.percent) { // GetTable - const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); - const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); - - uint32_t dropCount = GeneralUtils::GenerateRandomNumber(entry.minToDrop, entry.maxToDrop); - for (uint32_t i = 0; i < dropCount; ++i) { - uint32_t maxRarity = 1; - - float rarityRoll = GeneralUtils::GenerateRandomNumber(0, 1); - - for (const auto& rarity : rarityTable) { - if (rarity.randmax >= rarityRoll) { - maxRarity = rarity.rarity; - } else { - break; - } - } - - bool rarityFound = false; - std::vector possibleDrops; - - for (const auto& loot : lootTable) { - uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); - uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; - - if (rarity == maxRarity) { - possibleDrops.push_back(loot); - rarityFound = true; - } else if (rarity < maxRarity && !rarityFound) { - possibleDrops.push_back(loot); - maxRarity = rarity; - } - } - - if (possibleDrops.size() > 0) { - const auto& drop = possibleDrops[GeneralUtils::GenerateRandomNumber(0, possibleDrops.size() - 1)]; - - // filter out uneeded mission items - if (drop.MissionDrop && !missionComponent->RequiresItem(drop.itemid)) - continue; - - LOT itemID = drop.itemid; - // convert faction token proxy - if (itemID == 13763) { - if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) - itemID = 8318; // "Assembly Token" - else if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) - itemID = 8321; // "Venture League Token" - else if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) - itemID = 8319; // "Sentinels Token" - else if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) - itemID = 8320; // "Paradox Token" - } - - if (itemID == 13763) { - continue; - } // check if we aren't in faction - - // drops[itemID]++; this should work? - if (drops.find(itemID) == drops.end()) { - drops.insert({ itemID, 1 }); - } else { - ++drops[itemID]; - } - } - } - } - } - - for (const auto& drop : drops) { - LOG("Player %llu has rolled %i of item %i from loot matrix %i", player->GetObjectID(), drop.second, drop.first, matrixIndex); - } - - return drops; -} - -std::unordered_map Loot::RollLootMatrix(uint32_t matrixIndex) { - CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); - CDLootTableTable* lootTableTable = CDClientManager::GetTable(); - CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); - std::unordered_map drops; + std::map drops; const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); @@ -181,14 +77,12 @@ std::unordered_map Loot::RollLootMatrix(uint32_t matrixIndex) { } } - if (possibleDrops.size() > 0) { + if (!possibleDrops.empty()) { const auto& drop = possibleDrops[GeneralUtils::GenerateRandomNumber(0, possibleDrops.size() - 1)]; - if (drops.find(drop.itemid) == drops.end()) { - drops.insert({ drop.itemid, 1 }); - } else { - ++drops[drop.itemid]; - } + auto& info = drops[drop.itemid]; + if (info.count == 0) info.table = drop; + info.count++; } } } @@ -197,15 +91,395 @@ std::unordered_map Loot::RollLootMatrix(uint32_t matrixIndex) { return drops; } +// Generates a 'random' final position for the loot drop based on its input spawn position. +void CalcFinalDropPos(GameMessages::DropClientLoot& lootMsg) { + lootMsg.bUsePosition = true; + + //Calculate where the loot will go: + uint16_t degree = GeneralUtils::GenerateRandomNumber(0, 360); + + double rad = degree * 3.14 / 180; + double sin_v = sin(rad) * 4.2; + double cos_v = cos(rad) * 4.2; + + const auto [x, y, z] = lootMsg.spawnPos; + lootMsg.finalPosition = NiPoint3(static_cast(x + sin_v), y, static_cast(z + cos_v)); +} + +// Visually drop the loot to all team members, though only the lootMsg.ownerID can pick it up +void DistrbuteMsgToTeam(const GameMessages::DropClientLoot& lootMsg, const Team& team) { + for (const auto memberClient : team.members) { + const auto* const memberEntity = Game::entityManager->GetEntity(memberClient); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + } +} + +// The following 8 functions are all ever so slightly different such that combining them +// would make the logic harder to follow. Please read the comments! + +// Given a faction token proxy LOT to drop, drop 1 token for each player on a team, or the provided player. +// token drops are always given to every player on the team. +void DropFactionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + const auto playerID = player.GetObjectID(); + + GameMessages::GetFactionTokenType factionTokenType{}; + factionTokenType.target = playerID; + // If we're not in a faction, this message will return false + if (factionTokenType.Send()) { + lootMsg.item = factionTokenType.tokenType; + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop on the player + lootMsg.Send(); + // Visually drop it for the player + lootMsg.Send(player.GetSystemAddress()); + } +} + +// 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) { + 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::GetFactionTokenType factionTokenType{}; + factionTokenType.target = member; + // If we're not in a faction, this message will return false + if (factionTokenType.Send()) { + lootMsg.item = factionTokenType.tokenType; + lootMsg.target = member; + lootMsg.ownerID = member; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop on this team member + lootMsg.Send(); + // Show the rewards on all connected members of the team. Only the loot owner will be able to pick the tokens up. + DistrbuteMsgToTeam(lootMsg, team); + } + } +} + +// 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(Entity& player, GameMessages::DropClientLoot& lootMsg) { + const auto playerID = player.GetObjectID(); + CalcFinalDropPos(lootMsg); + + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + lootMsg.ownerID = playerID; + lootMsg.target = playerID; + + // Register the drop on the player + lootMsg.Send(); + // Visually drop it for the player + lootMsg.Send(player.GetSystemAddress()); +} + +// 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) { + 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); + + // We want to drop the powerups as the same ID and the same position to all members of the team + 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; + + 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(); + // No need to send to all members in a loop since that will happen by using the outer loop above and also since there is no owner + // sending to all will do nothing. + const auto* const memberEntity = Game::entityManager->GetEntity(member); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + } +} + +// Drops a mission item for a player +// If the player does not need this item, it will not be dropped. +void DropMissionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + GameMessages::MissionNeedsLot needMsg{}; + needMsg.item = lootMsg.item; + const auto playerID = player.GetObjectID(); + needMsg.target = playerID; + // Will return false if the item is not required + if (needMsg.Send()) { + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop with the player + lootMsg.Send(); + // Visually drop the loot to be picked up + lootMsg.Send(player.GetSystemAddress()); + } +} + +// 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) { + GameMessages::MissionNeedsLot needMsg{}; + needMsg.item = lootMsg.item; + 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; + + needMsg.target = member; + // Will return false if the item is not required + if (needMsg.Send()) { + lootMsg.target = member; + lootMsg.ownerID = member; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop with the player + lootMsg.Send(); + DistrbuteMsgToTeam(lootMsg, team); + } + } +} + +// Drop a regular piece of loot. +// Most items will go through this. +// A player will always get a drop that goes through this function +void DropRegularLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + const auto playerID = player.GetObjectID(); + + CalcFinalDropPos(lootMsg); + + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + // Register the drop with the player + lootMsg.Send(); + // Visually drop the loot to be picked up + lootMsg.Send(player.GetSystemAddress()); + +} + +// 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) { + auto earningPlayer = LWOOBJID_EMPTY; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + GameMessages::GetPosition memberPosMsg{}; + // 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 (team.lootOption == 0 /* Shared loot */) { + lootMsg.target = earningPlayer; + lootMsg.ownerID = earningPlayer; + lootMsg.Send(); + } else /* Free for all loot */ { + lootMsg.ownerID = LWOOBJID_EMPTY; + // By sending the loot with NO owner and to ALL members of the team, + // its a first come, first serve with who picks the item up. + for (const auto ffaMember : team.members) { + lootMsg.target = ffaMember; + lootMsg.Send(); + } + } + + DistrbuteMsgToTeam(lootMsg, team); +} + +void DropLoot(Entity* player, const LWOOBJID source, const std::map& rolledItems, uint32_t minCoins, uint32_t maxCoins) { + player = player->GetOwner(); // if the owner is overwritten, we collect that here + const auto playerID = player->GetObjectID(); + if (!player || !player->IsPlayer()) { + LOG("Trying to drop loot for non-player %llu:%i", playerID, player->GetLOT()); + return; + } + + // TODO should be scene based instead of radius based + // drop loot to either single player or team + // powerups never have an owner when dropped + // for every player on the team in a radius of 700 (arbitrary value, not lore) + // if shared loot, drop everything but tokens to the next team member that gets loot, + // then tokens to everyone (1 token drop in a 3 person team means everyone gets a token) + // if Free for all, drop everything with NO owner, except tokens which follow the same logic as above + auto* team = TeamManager::Instance()->GetTeam(playerID); + + GameMessages::GetPosition posMsg; + posMsg.target = source; + posMsg.Send(); + + const auto spawnPosition = posMsg.pos; + auto* const objectsTable = CDClientManager::GetTable(); + + constexpr LOT TOKEN_PROXY = 13763; + // Go through the drops 1 at a time to drop them + for (auto it = rolledItems.begin(); it != rolledItems.end(); it++) { + auto& [lootLot, info] = *it; + for (int i = 0; i < info.count; i++) { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.spawnPos = spawnPosition; + lootMsg.sourceID = source; + lootMsg.item = lootLot; + lootMsg.count = 1; + lootMsg.currency = 0; + const CDObjects& object = objectsTable->GetByID(lootLot); + + if (lootLot == TOKEN_PROXY) { + team ? DropFactionLoot(*team, lootMsg) : DropFactionLoot(*player, lootMsg); + } else if (info.table.MissionDrop) { + team ? DropMissionLoot(*team, lootMsg) : DropMissionLoot(*player, lootMsg); + } else if (object.type == "Powerup") { + team ? DropPowerupLoot(*team, lootMsg) : DropPowerupLoot(*player, lootMsg); + } else { + team ? DropRegularLoot(*team, lootMsg) : 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(coinRoll / team->members.size()) : coinRoll; + 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; + lootMsg.Send(); + const auto* const memberEntity = Game::entityManager->GetEntity(member); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + } + } else { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + lootMsg.currency = droppedCoins; + lootMsg.spawnPos = spawnPosition; + lootMsg.sourceID = source; + lootMsg.item = LOT_NULL; + lootMsg.Send(); + lootMsg.Send(player->GetSystemAddress()); + } +} + +void Loot::DropItem(Entity& player, GameMessages::DropClientLoot& lootMsg, bool useTeam, bool forceFfa) { + auto* const team = useTeam ? TeamManager::Instance()->GetTeam(player.GetObjectID()) : nullptr; + char oldTeamLoot{}; + if (team && forceFfa) { + oldTeamLoot = team->lootOption; + team->lootOption = 1; + } + + auto* const objectsTable = CDClientManager::GetTable(); + const CDObjects& object = objectsTable->GetByID(lootMsg.item); + + constexpr LOT TOKEN_PROXY = 13763; + if (lootMsg.item == TOKEN_PROXY) { + team ? DropFactionLoot(*team, lootMsg) : DropFactionLoot(player, lootMsg); + } else if (object.type == "Powerup") { + team ? DropPowerupLoot(*team, lootMsg) : DropPowerupLoot(player, lootMsg); + } else { + team ? DropRegularLoot(*team, lootMsg) : DropRegularLoot(player, lootMsg); + } + + if (team) team->lootOption = oldTeamLoot; +} + +void Loot::CacheMatrix(uint32_t matrixIndex) { + if (CachedMatrices.contains(matrixIndex)) return; + + CachedMatrices.insert(matrixIndex); + CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); + CDLootTableTable* lootTableTable = CDClientManager::GetTable(); + CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); + + const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); + + for (const auto& entry : matrix) { + const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); + const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); + for (const auto& loot : lootTable) { + uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); + uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; + } + } +} + +Loot::Return Loot::RollLootMatrix(Entity* player, uint32_t matrixIndex) { + auto* const missionComponent = player ? player->GetComponent() : nullptr; + + Loot::Return toReturn; + const auto drops = ::RollLootMatrix(matrixIndex); + // if no mission component, just convert the map and skip checking if its a mission drop + if (!missionComponent) { + for (const auto& [lot, info] : drops) toReturn[lot] = info.count; + } else { + for (const auto& [lot, info] : drops) { + const auto& itemInfo = info.table; + + // filter out uneeded mission items + if (itemInfo.MissionDrop && !missionComponent->RequiresItem(itemInfo.itemid)) + continue; + + LOT itemLot = lot; + // convert faction token proxy + if (itemLot == 13763) { + if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) + itemLot = 8318; // "Assembly Token" + else if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) + itemLot = 8321; // "Venture League Token" + else if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) + itemLot = 8319; // "Sentinels Token" + else if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) + itemLot = 8320; // "Paradox Token" + } + + if (itemLot == 13763) { + continue; + } // check if we aren't in faction + + toReturn[itemLot] = info.count; + } + } + + if (player) { + for (const auto& [lot, count] : toReturn) { + LOG("Player %llu has rolled %i of item %i from loot matrix %i", player->GetObjectID(), count, lot, matrixIndex); + } + } + + return toReturn; +} + void Loot::GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType) { player = player->GetOwner(); // If the owner is overwritten, we collect that here - std::unordered_map result = RollLootMatrix(player, matrixIndex); + const auto result = RollLootMatrix(player, matrixIndex); GiveLoot(player, result, lootSourceType); } -void Loot::GiveLoot(Entity* player, std::unordered_map& result, eLootSourceType lootSourceType) { +void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType) { player = player->GetOwner(); // if the owner is overwritten, we collect that here auto* inventoryComponent = player->GetComponent(); @@ -260,34 +534,9 @@ void Loot::DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, if (!inventoryComponent) return; - std::unordered_map result = RollLootMatrix(player, matrixIndex); + const auto result = ::RollLootMatrix(matrixIndex); - DropLoot(player, source, result, minCoins, maxCoins); -} - -void Loot::DropLoot(Entity* player, const LWOOBJID source, std::unordered_map& result, uint32_t minCoins, uint32_t maxCoins) { - player = player->GetOwner(); // if the owner is overwritten, we collect that here - - auto* inventoryComponent = player->GetComponent(); - - if (!inventoryComponent) - return; - - GameMessages::GetPosition posMsg; - posMsg.target = source; - posMsg.Send(); - - const auto spawnPosition = posMsg.pos; - - for (const auto& pair : result) { - for (int i = 0; i < pair.second; ++i) { - GameMessages::SendDropClientLoot(player, source, pair.first, 0, spawnPosition, 1); - } - } - - uint32_t coins = static_cast(minCoins + GeneralUtils::GenerateRandomNumber(0, 1) * (maxCoins - minCoins)); - - GameMessages::SendDropClientLoot(player, source, LOT_NULL, coins, spawnPosition); + ::DropLoot(player, source, result, minCoins, maxCoins); } void Loot::DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating) { diff --git a/dGame/dUtilities/Loot.h b/dGame/dUtilities/Loot.h index c8247b5b..266e35c6 100644 --- a/dGame/dUtilities/Loot.h +++ b/dGame/dUtilities/Loot.h @@ -6,20 +6,25 @@ class Entity; +namespace GameMessages { + struct DropClientLoot; +}; + namespace Loot { struct Info { LWOOBJID id = 0; LOT lot = 0; - uint32_t count = 0; + int32_t count = 0; }; - std::unordered_map RollLootMatrix(Entity* player, uint32_t matrixIndex); - std::unordered_map RollLootMatrix(uint32_t matrixIndex); + using Return = std::map; + + Loot::Return RollLootMatrix(Entity* player, uint32_t matrixIndex); void CacheMatrix(const uint32_t matrixIndex); void GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType = eLootSourceType::NONE); - void GiveLoot(Entity* player, std::unordered_map& result, eLootSourceType lootSourceType = eLootSourceType::NONE); + void GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType = eLootSourceType::NONE); void GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0); void DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins); - void DropLoot(Entity* player, const LWOOBJID source, std::unordered_map& result, uint32_t minCoins, uint32_t maxCoins); + void DropItem(Entity& player, GameMessages::DropClientLoot& lootMsg, bool useTeam = false, bool forceFfa = false); void DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0); }; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 0c5b7585..0fd86512 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1314,7 +1314,7 @@ namespace DEVGMCommands { for (uint32_t i = 0; i < loops; i++) { while (true) { - auto lootRoll = Loot::RollLootMatrix(lootMatrixIndex.value()); + const auto lootRoll = Loot::RollLootMatrix(nullptr, lootMatrixIndex.value()); totalRuns += 1; bool doBreak = false; for (const auto& kv : lootRoll) { @@ -1479,15 +1479,20 @@ namespace DEVGMCommands { void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; + const auto idParsed = GeneralUtils::TryParse(splitArgs[0]); + // First try to get the object by its ID if provided. + // Second try to get the object by player name. + // Lastly assume we were passed a component or LDF and try to find the closest entity with that component or LDF. Entity* closest = nullptr; + if (idParsed) closest = Game::entityManager->GetEntity(idParsed.value()); float closestDistance = 0.0f; std::u16string ldf; bool isLDF = false; - closest = PlayerManager::GetPlayer(splitArgs[0]); + if (!closest) closest = PlayerManager::GetPlayer(splitArgs[0]); if (!closest) { auto component = GeneralUtils::TryParse(splitArgs[0]); if (!component) { diff --git a/dScripts/ScriptedPowerupSpawner.cpp b/dScripts/ScriptedPowerupSpawner.cpp index 9abb8fd6..8539b363 100644 --- a/dScripts/ScriptedPowerupSpawner.cpp +++ b/dScripts/ScriptedPowerupSpawner.cpp @@ -15,11 +15,6 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity* self, std::string message) { const auto itemLOT = self->GetVar(u"lootLOT"); - // Build drop table - std::unordered_map drops; - - drops.emplace(itemLOT, 1); - // Spawn the required number of powerups auto* owner = Game::entityManager->GetEntity(self->GetSpawnerID()); if (owner != nullptr) { @@ -28,8 +23,19 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity* self, std::string message) { if (renderComponent != nullptr) { renderComponent->PlayEffect(0, u"cast", "N_cast"); } + GameMessages::GetPosition posMsg{}; + posMsg.target = self->GetObjectID(); + posMsg.Send(); - Loot::DropLoot(owner, self->GetObjectID(), drops, 0, 0); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = owner->GetObjectID(); + lootMsg.ownerID = owner->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.spawnPos = posMsg.pos; + lootMsg.item = itemLOT; + lootMsg.count = 1; + lootMsg.currency = 0; + Loot::DropItem(*owner, lootMsg, true, true); } // Increment the current cycle diff --git a/dScripts/ai/AG/AgPicnicBlanket.cpp b/dScripts/ai/AG/AgPicnicBlanket.cpp index 4e0bb44a..fd3ced79 100644 --- a/dScripts/ai/AG/AgPicnicBlanket.cpp +++ b/dScripts/ai/AG/AgPicnicBlanket.cpp @@ -10,8 +10,21 @@ void AgPicnicBlanket::OnUse(Entity* self, Entity* user) { return; self->SetVar(u"active", true); - auto lootTable = std::unordered_map{ {935, 3} }; - Loot::DropLoot(user, self->GetObjectID(), lootTable, 0, 0); + GameMessages::GetPosition posMsg{}; + posMsg.target = self->GetObjectID(); + posMsg.Send(); + + for (int32_t i = 0; i < 3; i++) { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = user->GetObjectID(); + lootMsg.ownerID = user->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.item = 935; + lootMsg.count = 1; + lootMsg.spawnPos = posMsg.pos; + lootMsg.currency = 0; + Loot::DropItem(*user, lootMsg, true); + } self->AddCallbackTimer(5.0f, [self]() { self->SetVar(u"active", false); diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index ecdacc42..e97316e5 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -415,9 +415,7 @@ void SGCannon::SpawnNewModel(Entity* self) { } if (lootMatrix != 0) { - std::unordered_map toDrop = {}; - toDrop = Loot::RollLootMatrix(player, lootMatrix); - + const auto toDrop = Loot::RollLootMatrix(player, lootMatrix); for (const auto [lot, count] : toDrop) { GameMessages::SetModelToBuild modelToBuild{}; modelToBuild.modelLot = lot; diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index ace0ef55..333f2249 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -47,7 +47,7 @@ client_net_version=171022 # Turn to 0 to default teams to use the live accurate Shared Loot (0) by default as opposed to Free for All (1) # This is used in both Chat and World servers. -default_team_loot=1 +default_team_loot=0 # event gating for login response and luz gating event_1=Talk_Like_A_Pirate