Merge branch 'main' into rotation-behaviors

This commit is contained in:
David Markowitz
2025-10-19 22:28:30 -07:00
255 changed files with 5051 additions and 1825 deletions

View File

@@ -48,6 +48,7 @@ namespace {
{ REQUEST_USE, []() { return std::make_unique<RequestUse>(); }},
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
{ SHOOTING_GALLERY_FIRE, []() { return std::make_unique<ShootingGalleryFire>(); } },
{ PICKUP_ITEM, []() { return std::make_unique<PickupItem>(); } },
};
};
@@ -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;

View File

@@ -978,6 +978,7 @@ void GameMessages::SendResurrect(Entity* entity) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr && entity->GetLOT() == 1) {
destroyableComponent->SetIsDead(false);
auto* levelComponent = entity->GetComponent<LevelProgressionComponent>();
if (levelComponent) {
int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4;
@@ -1066,90 +1067,6 @@ void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress&
SEND_PACKET;
}
void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos, int count) {
if (Game::config->GetValue("disable_drops") == "1" || !entity) {
return;
}
bool bUsePosition = false;
NiPoint3 finalPosition;
LWOOBJID lootID = LWOOBJID_EMPTY;
LWOOBJID owner = entity->GetObjectID();
if (item != LOT_NULL && item != 0) {
lootID = ObjectIDManager::GenerateObjectID();
Loot::Info info;
info.id = lootID;
info.count = count;
info.lot = item;
entity->AddLootItem(info);
}
if (item == LOT_NULL && currency != 0) {
entity->RegisterCoinDrop(currency);
}
if (spawnPos != NiPoint3Constant::ZERO) {
bUsePosition = true;
//Calculate where the loot will go:
uint16_t degree = GeneralUtils::GenerateRandomNumber<uint16_t>(0, 360);
double rad = degree * 3.14 / 180;
double sin_v = sin(rad) * 4.2;
double cos_v = cos(rad) * 4.2;
finalPosition = NiPoint3(static_cast<float>(spawnPos.GetX() + sin_v), spawnPos.GetY(), static_cast<float>(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<CDObjectsTable>();
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) {
CBITSTREAM;
CMSGHEADER;
@@ -2555,7 +2472,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
uint32_t sd0Size;
inStream.Read(sd0Size);
std::shared_ptr<char[]> sd0Data(new char[sd0Size]);
std::unique_ptr<char[]> sd0Data(new char[sd0Size]);
if (sd0Data == nullptr) return;
@@ -2565,9 +2482,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
inStream.Read(timeTaken);
/*
Disabled this, as it's kinda silly to do this roundabout way of storing plaintext lxfml, then recompressing
it to send it back to the client.
On DLU we had agreed that bricks wouldn't be taken anyway, but if your server decides otherwise, feel free to
comment this back out and add the needed code to get the bricks used from lxfml and take them from the inventory.
@@ -2579,51 +2493,79 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
//Now, the cave of dragons:
//We runs this in async because the http library here is blocking, meaning it'll halt the thread.
//But we don't want the server to go unresponsive, because then the client would disconnect.
//We need to get a new ID for our model first:
ObjectIDManager::RequestPersistentID([=](uint32_t newID) {
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
LWOOBJID newIDL = newID;
GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER);
GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT);
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
uint32_t blueprintIDSmall = ObjectIDManager::GenerateRandomObjectID();
LWOOBJID blueprintID = blueprintIDSmall;
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID();
const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID();
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
LWOOBJID propertyId = LWOOBJID_EMPTY;
if (propertyInfo) propertyId = propertyInfo->id;
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
LWOOBJID propertyId = LWOOBJID_EMPTY;
if (propertyInfo) propertyId = propertyInfo->id;
// Save the binary data to the Sd0 buffer
std::string str(sd0Data.get(), sd0Size);
std::istringstream sd0DataStream(str);
Sd0 sd0(sd0DataStream);
// Save the binary data to the Sd0 buffer
std::string str(sd0Data.get(), sd0Size);
std::istringstream sd0DataStream(str);
Sd0 sd0(sd0DataStream);
// Uncompress the data, split, and nornmalize the model
const auto asStr = sd0.GetAsStringUncompressed();
// Uncompress the data and normalize the position
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
if (Game::config->GetValue("save_lxfmls") == "1") {
// save using localId to avoid conflicts
std::ofstream outFile("debug_lxfml_uncompressed_" + std::to_string(localId) + ".lxfml");
outFile << asStr;
outFile.close();
}
// Recompress the data and save to the database
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto splitLxfmls = Lxfml::Split(asStr);
LOG_DEBUG("Split into %zu models", splitLxfmls.size());
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
bitStream.Write<uint32_t>(splitLxfmls.size());
std::vector<LWOOBJID> blueprintIDs;
std::vector<LWOOBJID> modelIDs;
for (size_t i = 0; i < splitLxfmls.size(); ++i) {
// Legacy logic to check for old random IDs (regenerating these is not really feasible)
// Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow
const uint32_t maxRetries = 100;
uint32_t retries = 0;
bool blueprintIDExists = true;
bool modelExists = true;
LWOOBJID newID = LWOOBJID_EMPTY;
LWOOBJID blueprintID = LWOOBJID_EMPTY;
do {
if (newID != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID);
newID = ObjectIDManager::GetPersistentID();
blueprintID = ObjectIDManager::GetPersistentID();
++retries;
blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value();
modelExists = Database::Get()->GetModel(newID).has_value();
} while ((blueprintIDExists || modelExists) && retries < maxRetries);
blueprintIDs.push_back(blueprintID);
modelIDs.push_back(newID);
// Save each model to the database
sd0.FromData(reinterpret_cast<const uint8_t*>(splitLxfmls[i].lxfml.data()), splitLxfmls[i].lxfml.size());
auto sd0AsStream = sd0.GetAsStream();
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintIDSmall, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
//Insert into the db as a BBB model:
// Insert the new property model
IPropertyContents::Model model;
model.id = newIDL;
model.ugcId = blueprintIDSmall;
model.position = newCenter;
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
model.id = newID;
model.ugcId = blueprintID;
model.position = splitLxfmls[i].center;
model.rotation = QuatUtils::IDENTITY;
model.lot = 14;
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
@@ -2632,54 +2574,48 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
(or you uncomment the lxfml decomp stuff above)
*/
////Send off to UGC for processing, if enabled:
//if (Game::config->GetValue("ugc_remote") == "1") {
// std::string ugcIP = Game::config->GetValue("ugc_ip");
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
// Send off to UGC for processing, if enabled:
// if (Game::config->GetValue("ugc_remote") == "1") {
// std::string ugcIP = Game::config->GetValue("ugc_ip");
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
// //Send out a request:
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
// //Send out a request:
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
// //the nif, hkx and checksum files are ready to be downloaded from cache.
//}
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
// //the nif, hkx and checksum files are ready to be downloaded from cache.
// }
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
const auto& newSd0 = sd0.GetAsVector();
uint32_t sd0Size{};
for (const auto& chunk : newSd0) sd0Size += chunk.size();
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
bitStream.Write<uint32_t>(1);
// Write the ID and data to the response packet
bitStream.Write(blueprintID);
bitStream.Write(sd0Size);
const auto& newSd0 = sd0.GetAsVector();
uint32_t newSd0Size{};
for (const auto& chunk : newSd0) newSd0Size += chunk.size();
bitStream.Write(newSd0Size);
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
}
SEND_PACKET;
//Now we have to construct this object:
SEND_PACKET;
// Create entities for each model
for (size_t i = 0; i < splitLxfmls.size(); ++i) {
EntityInfo info;
info.lot = 14;
info.pos = newCenter;
info.rot = {};
info.pos = splitLxfmls[i].center;
info.rot = QuatUtils::IDENTITY;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintIDs[i]));
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
info.settings.push_back(new LDFData<int>(u"modelType", 2));
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", newIDL));
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", modelIDs[i]));
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
if (newEntity) {
Game::entityManager->ConstructEntity(newEntity);
@@ -2687,10 +2623,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
//Make sure the propMgmt doesn't delete our model after the server dies
//Trying to do this after the entity is constructed. Shouldn't really change anything but
//there was an issue with builds not appearing since it was placed above ConstructEntity.
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), modelIDs[i]);
}
});
}
}
void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
@@ -3240,12 +3175,13 @@ void GameMessages::SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, cons
void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
// Check if the player has restricted trade access
auto* character = entity->GetCharacter();
const bool restrictTradeOnMute = UserManager::Instance()->GetMuteRestrictTrade();
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess)) {
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) {
// Send a message to the player
ChatPackets::SendSystemMessage(
sysAddr,
u"This character has restricted trade access."
u"Your character has restricted trade access."
);
return;
@@ -3261,7 +3197,7 @@ void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity*
if (invitee != nullptr && invitee->IsPlayer()) {
character = invitee->GetCharacter();
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess)) {
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) {
// Send a message to the player
ChatPackets::SendSystemMessage(
sysAddr,
@@ -5529,55 +5465,52 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
}
}
ObjectIDManager::RequestPersistentID([=](uint32_t newId) {
LOG("Build finished");
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
LOG("Build finished");
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
//Game::entityManager->SerializeEntity(entity);
//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
//Game::entityManager->SerializeEntity(entity);
const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);
const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);
std::vector<LDFBaseData*> config;
config.push_back(moduleAssembly);
std::vector<LDFBaseData*> config;
config.push_back(moduleAssembly);
LWOOBJID newIdBig = newId;
GeneralUtils::SetBit(newIdBig, eObjectBits::CHARACTER);
LWOOBJID newID = ObjectIDManager::GetPersistentID();
if (count == 3) {
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
} else if (count == 7) {
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
if (count == 3) {
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
} else if (count == 7) {
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
}
auto* pCharacter = character->GetCharacter();
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newID, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
auto* missionComponent = character->GetComponent<MissionComponent>();
if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID());
if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast<LWOOBJID>(eRacingTaskParam::MODULAR_BUILDING));
}
}
auto* pCharacter = character->GetCharacter();
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(eReplicaComponentType::SCRIPT));
auto* missionComponent = character->GetComponent<MissionComponent>();
entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList);
if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID());
if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast<LWOOBJID>(eRacingTaskParam::MODULAR_BUILDING));
}
}
// Move remaining temp models back to models
std::vector<Item*> items;
ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(eReplicaComponentType::SCRIPT));
for (const auto& pair : temp->GetItems()) {
items.push_back(pair.second);
}
entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList);
// Move remaining temp models back to models
std::vector<Item*> items;
for (const auto& pair : temp->GetItems()) {
items.push_back(pair.second);
}
for (auto* item : items) {
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
}
});
for (auto* item : items) {
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
}
}
}
@@ -5716,27 +5649,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();
@@ -6320,6 +6232,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;
@@ -6414,6 +6331,7 @@ namespace GameMessages {
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
if (handlingEntity) handlingEntity->HandleMsg(*this);
else LOG("Failed to find target %llu", targetForReport);
}
bool RequestUse::Deserialize(RakNet::BitStream& stream) {
@@ -6486,4 +6404,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);
}
}

View File

@@ -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
@@ -151,7 +153,6 @@ namespace GameMessages {
void SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result = false);
void SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result = false);
void SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data);
void SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos = NiPoint3Constant::ZERO, int count = 1);
void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme);
void SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation);
@@ -792,6 +793,7 @@ namespace GameMessages {
AMFArrayValue* info{};
AMFArrayValue* subCategory{};
bool bVerbose{};
LWOOBJID clientID{};
GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, eGameMasterLevel::DEVELOPER) {}
};
@@ -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;
};
@@ -886,5 +888,65 @@ namespace GameMessages {
bool bForceFlagDirty{};
};
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