Compare commits

...

7 Commits

Author SHA1 Message Date
David Markowitz
2235f62680 fixes 2026-06-24 02:58:03 -07:00
David Markowitz
be6b94ae40 remove amf3 header
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 02:53:57 -07:00
David Markowitz
7338319fac feat: debugger additions
Add type field for links in flash
Add warning level for dangerous buttons
fix uninitialzied memory with jetpack variable
remove a bunch of duplicated position push code

tested that the ui is still functional and components with multiple physics components have all their details visible.
tested that jetpack is initialized now
2026-06-24 02:43:45 -07:00
Aaron Kimbrell
5d523a1e7b fix: handling of the same skill on multiple items (#1990)
* refactor: update behavior slot determination to use equipLocation instead of itemType

* fix: improve skill management in InventoryComponent to ensure correct client updates
2026-06-22 17:16:57 -07:00
David Markowitz
5745742c91 feat: dragon instance script (#2012)
* feat: dragon instance script

* Update FvDragonInstanceServer.h

* feat: implement ronin script

* Update CountdownDestroyAI.cpp

* default initialize

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* remove unused handlers

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* use float

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fixes

* Update ScriptComponent.h

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-22 17:16:21 -07:00
Aaron Kimbrell
62f58f5307 chore: cleanup possession handling (#1984)
* feat: enhance possession mechanics with skill set integration and improved message handling

* fix: restore SetPossessor in Mount() and scope IsRacing to vehicles

SetPossessor was missing from Mount(), breaking direct possessions via
PossessableComponent::OnUse which bypasses HandlePossession. IsRacing
now only set/cleared when the mount has HavokVehiclePhysicsComponent,
preventing non-vehicle possessions from incorrectly affecting the
distance-driven statistic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 21:39:51 -07:00
David Markowitz
83707e2210 feat: add the lightning orb script* (#2011)
* feat: add the lightning orb script*

Doesn't do anything because MPC don't work...

* Update LightningOrbServer.cpp
2026-06-21 00:30:44 -05:00
40 changed files with 394 additions and 194 deletions

View File

@@ -4,7 +4,6 @@
#include "dCommonVars.h"
#include "Logger.h"
#include "Game.h"
#include <type_traits>
#include <unordered_map>
#include <vector>
@@ -368,9 +367,37 @@ public:
}
template<typename AmfType = AMFArrayValue>
AmfType& PushDebug(const std::string_view name) {
AmfType& PushDebug(const std::string_view name, const std::string& objectType = "", const uint32_t warningLevel = 0) {
size_t i = 0;
for (; i < m_Dense.size(); i++) {
const auto& cast = dynamic_cast<AMFArrayValue*>(m_Dense[i].get());
if (!cast) continue;
const auto& nameValue = cast->Get<std::string>("name");
if (!nameValue || nameValue->GetValue() != name) continue;
if (!objectType.empty()) {
cast->Insert<std::string>("type", objectType);
}
if (warningLevel != 0) {
cast->Insert<double>("warningLevel", warningLevel);
}
// found a duplicate, return this instead
auto valueCast = dynamic_cast<AmfType*>(cast->Get("value"));
if (valueCast) return *valueCast;
}
auto* value = PushArray();
value->Insert("name", name.data());
value->Insert<std::string>("name", name.data());
if (!objectType.empty()) {
value->Insert<std::string>("type", objectType);
}
if (warningLevel != 0) {
value->Insert<double>("warningLevel", warningLevel);
}
return value->Insert<AmfType>("value", std::make_unique<AmfType>());
}

View File

@@ -1019,6 +1019,7 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacke
const bool hasParent = m_ParentEntity != nullptr || m_SpawnerID != 0;
outBitStream.Write(hasParent);
if (hasParent) {
// 触るな!
if (m_ParentEntity != nullptr) outBitStream.Write(GeneralUtils::SetBit(m_ParentEntity->GetObjectID(), static_cast<uint32_t>(eObjectBits::CLIENT)));
else if (m_Spawner != nullptr && m_Spawner->m_Info.isNetwork) outBitStream.Write(m_SpawnerID);
else outBitStream.Write(GeneralUtils::SetBit(m_SpawnerID, static_cast<uint32_t>(eObjectBits::CLIENT)));
@@ -2235,11 +2236,16 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& r
const auto& objTableInfo = table->GetByID(GetLOT());
objectInfo.PushDebug<AMFStringValue>("Name") = objTableInfo.name;
objectInfo.PushDebug<AMFIntValue>("Template ID(LOT)") = GetLOT();
objectInfo.PushDebug<AMFStringValue>("Object ID") = std::to_string(GetObjectID());
objectInfo.PushDebug<AMFStringValue>("Spawner's Object ID") = std::to_string(GetSpawnerID());
objectInfo.PushDebug<AMFStringValue>("Owner override") = std::to_string(m_OwnerOverride);
objectInfo.PushDebug<AMFStringValue>("Name", "name") = objTableInfo.name;
objectInfo.PushDebug<AMFIntValue>("Template ID(LOT)", "LOT") = GetLOT();
objectInfo.PushDebug<AMFStringValue>("Object ID", "LWOOBJID") = std::to_string(GetObjectID());
objectInfo.PushDebug<AMFStringValue>("Spawner's Object ID", "LWOOBJID") = std::to_string(GetSpawnerID());
objectInfo.PushDebug<AMFStringValue>("Owner override", "LWOOBJID") = std::to_string(m_OwnerOverride);
auto& children = objectInfo.PushDebug("Child Objects");
int i = 1;
for (const auto* child : m_ChildEntities) {
if (child) children.PushDebug<AMFStringValue>("Child " + std::to_string(i++), "LWOOBJID") = std::to_string(child->GetObjectID());
}
auto& componentDetails = objectInfo.PushDebug("Component Information");
for (const auto [id, component] : m_Components) {

View File

@@ -521,6 +521,10 @@ void BaseCombatAIComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsI
void BaseCombatAIComponent::SetAiState(AiState newState) {
if (newState == this->m_State) return;
GameMessages::NotifyCombatAIStateChange stateMsg;
stateMsg.prevState = this->m_State;
stateMsg.newState = newState;
m_Parent->HandleMsg(stateMsg);
this->m_State = newState;
m_DirtyStateOrTarget = true;
Game::entityManager->SerializeEntity(m_Parent);
@@ -860,7 +864,7 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
auto& cmptType = reportInfo.info->PushDebug("Base Combat AI");
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
auto& targetInfo = cmptType.PushDebug("Current Target Info");
targetInfo.PushDebug<AMFStringValue>("Current Target ID") = std::to_string(m_Target);
targetInfo.PushDebug<AMFStringValue>("Current Target ID", "LWOOBJID") = std::to_string(m_Target);
// if (m_Target != LWOOBJID_EMPTY) {
// LWOGameMessages::ObjGetName nameMsg(m_CurrentTarget);
// SEND_GAMEOBJ_MSG(nameMsg);
@@ -901,10 +905,7 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
//}
//cmptType.PushDebug("Current Combat Role") = curState;
auto& tetherPoint = cmptType.PushDebug("Tether Point");
tetherPoint.PushDebug<AMFDoubleValue>("X") = m_StartPosition.x;
tetherPoint.PushDebug<AMFDoubleValue>("Y") = m_StartPosition.y;
tetherPoint.PushDebug<AMFDoubleValue>("Z") = m_StartPosition.z;
cmptType.PushDebug("Tether Point").PushDebug(m_StartPosition);
cmptType.PushDebug<AMFDoubleValue>("Hard Tether Radius") = m_HardTetherRadius;
cmptType.PushDebug<AMFDoubleValue>("Soft Tether Radius") = m_SoftTetherRadius;
cmptType.PushDebug<AMFDoubleValue>("Aggro Radius") = m_AggroRadius;

View File

@@ -361,17 +361,11 @@ void ControllablePhysicsComponent::SetStunImmunity(
bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
auto& info = reportInfo.subCategory->PushDebug("Controllable Info");
auto& info = reportInfo.subCategory->PushDebug("Controllable Physics");
auto& vel = info.PushDebug("Velocity");
vel.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
vel.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
vel.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
info.PushDebug("Velocity").PushDebug(m_Velocity);
auto& angularVelocity = info.PushDebug("Angular Velocity");
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
info.PushDebug("Angular Velocity").PushDebug(m_AngularVelocity);
info.PushDebug<AMFBoolValue>("Is On Ground") = m_IsOnGround;
info.PushDebug<AMFBoolValue>("Is On Rail") = m_IsOnRail;
@@ -403,12 +397,13 @@ bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObject
info.PushDebug<AMFBoolValue>("Is In Bubble") = m_IsInBubble;
info.PushDebug<AMFStringValue>("Bubble Type") = StringifiedEnum::ToString(m_BubbleType).data();
info.PushDebug<AMFBoolValue>("Special Anims") = m_SpecialAnims;
info.PushDebug<AMFIntValue>("Immune To Stun Attack Count") = m_ImmuneToStunAttackCount;
info.PushDebug<AMFIntValue>("Immune To Stun Equip Count") = m_ImmuneToStunEquipCount;
info.PushDebug<AMFIntValue>("Immune To Stun Interact Count") = m_ImmuneToStunInteractCount;
info.PushDebug<AMFIntValue>("Immune To Stun Jump Count") = m_ImmuneToStunJumpCount;
info.PushDebug<AMFIntValue>("Immune To Stun Move Count") = m_ImmuneToStunMoveCount;
info.PushDebug<AMFIntValue>("Immune To Stun Turn Count") = m_ImmuneToStunTurnCount;
info.PushDebug<AMFIntValue>("Immune To Stun UseItem Count") = m_ImmuneToStunUseItemCount;
auto& immunity = info.PushDebug("Immunity Effects");
immunity.PushDebug<AMFBoolValue>("Immune to Stun Move") = m_ImmuneToStunMoveCount != 0;
immunity.PushDebug<AMFBoolValue>("Immune to Stun Turn") = m_ImmuneToStunTurnCount != 0;
immunity.PushDebug<AMFBoolValue>("Immune to Stun Attack") = m_ImmuneToStunAttackCount != 0;
immunity.PushDebug<AMFBoolValue>("Immune to Stun Use Item") = m_ImmuneToStunUseItemCount != 0;
immunity.PushDebug<AMFBoolValue>("Immune to Stun Equip") = m_ImmuneToStunEquipCount != 0;
immunity.PushDebug<AMFBoolValue>("Immune to Stun Interact") = m_ImmuneToStunInteractCount != 0;
immunity.PushDebug<AMFBoolValue>("Immune to Stun Jump") = m_ImmuneToStunJumpCount != 0;
return true;
}

View File

@@ -328,7 +328,7 @@ private:
/**
* The effect that plays while using the jetpack
*/
int32_t m_JetpackEffectID;
int32_t m_JetpackEffectID{};
/**
* The current speed multiplier, allowing an entity to run faster

View File

@@ -1146,7 +1146,7 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportIn
destroyableInfo.PushDebug<AMFDoubleValue>("Explode Factor") = m_ExplodeFactor;
destroyableInfo.PushDebug<AMFBoolValue>("Has Threats") = m_HasThreats;
destroyableInfo.PushDebug<AMFStringValue>("Killer ID") = std::to_string(m_KillerID);
destroyableInfo.PushDebug<AMFStringValue>("Killer ID", "LWOOBJID") = std::to_string(m_KillerID);
// "Scripts"; idk what to do about scripts yet
auto& immuneCounts = destroyableInfo.PushDebug("Immune Counts");

View File

@@ -108,7 +108,7 @@ bool GhostComponent::OnGetGMInvis(GameMessages::GetGMInvis& gmInvisMsg) {
bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportMsg) {
auto& cmptType = reportMsg.info->PushDebug("Ghost");
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = false;
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = m_IsGMInvisible;
return true;
}

View File

@@ -110,15 +110,9 @@ bool HavokVehiclePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObject
auto& info = reportInfo.subCategory->PushDebug("Havok Vehicle Physics Info");
auto& velocity = info.PushDebug("Velocity");
velocity.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
velocity.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
velocity.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
auto& velocity = info.PushDebug("Velocity").PushDebug(m_Velocity);
auto& angularVelocity = info.PushDebug("Angular Velocity");
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
auto& angularVelocity = info.PushDebug("Angular Velocity").PushDebug(m_AngularVelocity);
info.PushDebug<AMFBoolValue>("Is On Ground") = m_IsOnGround;
info.PushDebug<AMFBoolValue>("Is On Rail") = m_IsOnRail;

View File

@@ -723,10 +723,6 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
for (const auto& pair : m_Equipped) {
const auto item = pair.second;
if (bIsInitialUpdate) {
AddItemSkills(item.lot);
}
outBitStream.Write(item.id);
outBitStream.Write(item.lot);
@@ -984,9 +980,6 @@ void InventoryComponent::UnequipScripts(Item* unequippedItem) {
}
void InventoryComponent::HandlePossession(Item* item) {
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (!characterComponent) return;
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
if (!possessorComponent) return;
@@ -1002,52 +995,31 @@ void InventoryComponent::HandlePossession(Item* item) {
return;
}
GameMessages::SendSetStunned(m_Parent->GetObjectID(), eStateChangeType::PUSH, m_Parent->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
// Set the mount Item ID so that we know what were handling
// Set the mount item ID so that we know what we're handling
possessorComponent->SetMountItemID(item->GetId());
GameMessages::SendSetMountInventoryID(m_Parent, item->GetId(), UNASSIGNED_SYSTEM_ADDRESS);
// Create entity to mount
auto startRotation = m_Parent->GetRotation();
// Create the mount entity
EntityInfo info{};
info.lot = item->GetLot();
info.pos = m_Parent->GetPosition();
info.rot = startRotation;
info.rot = m_Parent->GetRotation();
info.spawnerID = m_Parent->GetObjectID();
auto* mount = Game::entityManager->CreateEntity(info, nullptr, m_Parent);
// Check to see if the mount is a vehicle, if so, flip it
auto* vehicleComponent = mount->GetComponent<HavokVehiclePhysicsComponent>();
if (vehicleComponent) characterComponent->SetIsRacing(true);
// Setup the destroyable stats
auto* destroyableComponent = mount->GetComponent<DestroyableComponent>();
if (destroyableComponent) {
destroyableComponent->SetIsImmune(true);
}
// Mount it
auto* possessableComponent = mount->GetComponent<PossessableComponent>();
if (possessableComponent) {
possessableComponent->SetIsItemSpawned(true);
possessableComponent->SetPossessor(m_Parent->GetObjectID());
// Possess it
possessorComponent->SetPossessable(mount->GetObjectID());
possessorComponent->SetPossessableType(possessableComponent->GetPossessionType());
}
GameMessages::SendSetJetPackMode(m_Parent, false);
auto* destroyableComponent = mount->GetComponent<DestroyableComponent>();
if (destroyableComponent) destroyableComponent->SetIsImmune(true);
// Make it go to the client
Game::entityManager->ConstructEntity(mount);
// Update the possessor
Game::entityManager->SerializeEntity(m_Parent);
possessorComponent->Mount(mount);
// have to unlock the input so it vehicle can be driven
if (vehicleComponent) GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress());
GameMessages::SendMarkInventoryItemAsActive(m_Parent->GetObjectID(), true, eUnequippableActiveType::MOUNT, item->GetId(), m_Parent->GetSystemAddress());
}
@@ -1175,14 +1147,12 @@ LOT InventoryComponent::GetConsumable() const {
void InventoryComponent::AddItemSkills(const LOT lot) {
const auto info = Inventory::FindItemComponent(lot);
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
const auto slot = FindBehaviorSlot(info.equipLocation);
if (slot == BehaviorSlot::Invalid) {
return;
}
const auto index = m_Skills.find(slot);
const auto skill = FindSkill(lot);
SetSkill(slot, skill);
@@ -1210,7 +1180,7 @@ void InventoryComponent::FixInvisibleItems() {
void InventoryComponent::RemoveItemSkills(const LOT lot) {
const auto info = Inventory::FindItemComponent(lot);
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
const auto slot = FindBehaviorSlot(info.equipLocation);
if (slot == BehaviorSlot::Invalid) {
return;
@@ -1222,15 +1192,31 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
return;
}
const auto old = index->second;
const auto skillId = FindSkill(lot);
GameMessages::SendRemoveSkill(m_Parent, old);
// Only act on this slot if it still holds the skill from this item.
// Another item may have overwritten the slot since this one was equipped.
if (index->second != skillId) {
return;
}
m_Skills.erase(slot);
// Find another slot that still holds this skillID (if any).
const auto surviving = std::ranges::find_if(m_Skills, [skillId](const auto& pair) {
return pair.second == skillId;
});
// The client stores one acquiredSkillsInfo entry per skillID, tagged with the slotID
// it was originally added with. Always send RemoveSkill to clear that entry, then
// re-add with the surviving slot so the client shows it in the correct place.
GameMessages::SendRemoveSkill(m_Parent, skillId);
if (surviving != m_Skills.end()) {
GameMessages::SendAddSkill(m_Parent, skillId, surviving->first);
}
if (slot == BehaviorSlot::Primary) {
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
}
}
@@ -1322,23 +1308,17 @@ void InventoryComponent::RemoveDatabasePet(LWOOBJID id) {
m_Pets.erase(id);
}
BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) {
switch (type) {
case eItemType::HAT:
return BehaviorSlot::Head;
case eItemType::NECK:
return BehaviorSlot::Neck;
case eItemType::LEFT_HAND:
return BehaviorSlot::Offhand;
case eItemType::RIGHT_HAND:
return BehaviorSlot::Primary;
case eItemType::CONSUMABLE:
return BehaviorSlot::Consumable;
default:
return BehaviorSlot::Invalid;
}
BehaviorSlot InventoryComponent::FindBehaviorSlot(const std::string& equipLocation) {
// Skill slot is determined by equipLocation, not itemType.
// Mapping confirmed against live captures and client data (issue #1339).
if (equipLocation == "special_r") return BehaviorSlot::Primary;
if (equipLocation == "hair") return BehaviorSlot::Head;
if (equipLocation == "special_l") return BehaviorSlot::Offhand;
if (equipLocation == "clavicle") return BehaviorSlot::Neck;
return BehaviorSlot::Invalid;
}
bool InventoryComponent::IsTransferInventory(eInventoryType type, bool includeVault) {
return type == VENDOR_BUYBACK || (includeVault && (type == VAULT_ITEMS || type == VAULT_MODELS)) || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
}
@@ -1679,10 +1659,28 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
const auto index = m_Skills.find(slot);
if (index != m_Skills.end()) {
const auto old = index->second;
GameMessages::SendRemoveSkill(m_Parent, old);
// Only remove the old skill from the client if no other slot still holds it.
// The client's acquiredSkillsInfo is keyed by skillID (one entry per skill),
// so RemoveSkill clears it globally — sending it while another slot still uses
// the same skillID would break that slot on the client.
const auto usedElsewhere = std::ranges::any_of(m_Skills, [&](const auto& pair) {
return pair.first != slot && pair.second == old;
});
if (!usedElsewhere) {
GameMessages::SendRemoveSkill(m_Parent, old);
}
}
// Only send AddSkill if the client doesn't already know about this skillID.
// The client early-exits on duplicate AddSkill (same skillID already in
// acquiredSkillsInfo) without updating the slot — so only send when it's new.
const auto alreadyKnown = std::ranges::any_of(m_Skills, [&](const auto& pair) {
return pair.first != slot && pair.second == skillId;
});
if (!alreadyKnown) {
GameMessages::SendAddSkill(m_Parent, skillId, slot);
}
GameMessages::SendAddSkill(m_Parent, skillId, slot);
m_Skills.insert_or_assign(slot, skillId);
return true;
}
@@ -1862,9 +1860,9 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo
std::stringstream ss;
ss << "%[Objects_" << item->GetLot() << "_name] Slot " << item->GetSlot();
auto& slot = curInv.PushDebug(ss.str());
slot.PushDebug<AMFStringValue>("Object ID") = std::to_string(item->GetId());
slot.PushDebug<AMFIntValue>("LOT") = item->GetLot();
if (item->GetSubKey() != LWOOBJID_EMPTY) slot.PushDebug<AMFStringValue>("Subkey") = std::to_string(item->GetSubKey());
slot.PushDebug<AMFStringValue>("Object ID", "LWOOBJID") = std::to_string(item->GetId());
slot.PushDebug<AMFIntValue>("LOT", "LOT") = item->GetLot();
if (item->GetSubKey() != LWOOBJID_EMPTY) slot.PushDebug<AMFStringValue>("Subkey", "LWOOBJID") = std::to_string(item->GetSubKey());
slot.PushDebug<AMFIntValue>("Count") = item->GetCount();
slot.PushDebug<AMFIntValue>("Slot") = item->GetSlot();
slot.PushDebug<AMFBoolValue>("Bind on pickup") = item->GetInfo().isBOP;
@@ -1883,7 +1881,8 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo
ss << "%[Objects_" << info.lot << "_name]";
auto& equipSlot = equipped.PushDebug(ss.str());
equipSlot.PushDebug<AMFStringValue>("Location") = location;
equipSlot.PushDebug<AMFStringValue>("Object ID") = std::to_string(info.id);
equipSlot.PushDebug<AMFStringValue>("Object ID", "LWOOBJID") = std::to_string(info.id);
equipSlot.PushDebug<AMFIntValue>("LOT", "LOT") = info.lot;
equipSlot.PushDebug<AMFIntValue>("Slot") = info.slot;
equipSlot.PushDebug<AMFIntValue>("Count") = info.count;
auto& extra = equipSlot.PushDebug("Extra Info");

View File

@@ -367,11 +367,10 @@ public:
void RemoveDatabasePet(LWOOBJID id);
/**
* Returns the current behavior slot active for the passed item type
* @param type the item type to find the behavior slot for
* @return the current behavior slot active for the passed item type
* Returns the behavior slot for the given equipLocation string.
* This is the authoritative mapping used for skill slot assignment.
*/
static BehaviorSlot FindBehaviorSlot(eItemType type);
static BehaviorSlot FindBehaviorSlot(const std::string& equipLocation);
/**
* Checks if the inventory type is a temp inventory
@@ -403,6 +402,8 @@ public:
std::map<BehaviorSlot, uint32_t> GetSkills() { return m_Skills; };
void ClearSkills() { m_Skills.clear(); };
bool SetSkill(int slot, uint32_t skillId);
bool SetSkill(BehaviorSlot slot, uint32_t skillId);

View File

@@ -652,7 +652,7 @@ void PushMissions(const std::map<uint32_t, Mission*>& missions, AMFArrayValue& V
}
bool MissionComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
auto& missionInfo = reportInfo.info->PushDebug("Mission (Laggy)");
auto& missionInfo = reportInfo.info->PushDebug("Mission (Laggy)", "", 1);
missionInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
// Sort the missions so they are easier to parse and present to the end user
std::map<uint32_t, Mission*> achievements;

View File

@@ -355,8 +355,8 @@ bool ModelComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& re
cmptInfo.PushDebug<AMFStringValue>("Name") = "Objects_" + std::to_string(m_Parent->GetLOT()) + "_name";
cmptInfo.PushDebug<AMFBoolValue>("Has Unique Name") = false;
cmptInfo.PushDebug<AMFStringValue>("UGID (from item)") = std::to_string(m_userModelID);
cmptInfo.PushDebug<AMFStringValue>("UGID") = std::to_string(m_userModelID);
cmptInfo.PushDebug<AMFStringValue>("UGID (from item)", "LWOOBJID") = std::to_string(m_userModelID);
cmptInfo.PushDebug<AMFStringValue>("UGID", "LWOOBJID") = std::to_string(m_userModelID);
cmptInfo.PushDebug<AMFStringValue>("Description") = "";
cmptInfo.PushDebug<AMFIntValue>("Behavior Count") = m_Behaviors.size();

View File

@@ -535,20 +535,14 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
movementInfo.PushDebug<AMFDoubleValue>("Pulling To Point") = m_PullingToPoint;
movementInfo.PushDebug<AMFBoolValue>("At Final Waypoint") = m_AtFinalWaypoint;
auto& pullPointInfo = movementInfo.PushDebug("Pull Point");
pullPointInfo.PushDebug<AMFDoubleValue>("X") = m_PullPoint.x;
pullPointInfo.PushDebug<AMFDoubleValue>("Y") = m_PullPoint.y;
pullPointInfo.PushDebug<AMFDoubleValue>("Z") = m_PullPoint.z;
auto& pullPointInfo = movementInfo.PushDebug("Pull Point").PushDebug(m_PullPoint);
// movementInfo.PushDebug<AMFDoubleValue>("Delay") = m_Delay;
auto& waypoints = movementInfo.PushDebug("Interpolated Waypoints");
int i = 0;
for (const auto& point : m_InterpolatedWaypoints) {
auto& waypoint = waypoints.PushDebug("Waypoint " + std::to_string(++i));
waypoint.PushDebug<AMFDoubleValue>("X") = point.x;
waypoint.PushDebug<AMFDoubleValue>("Y") = point.y;
waypoint.PushDebug<AMFDoubleValue>("Z") = point.z;
waypoints.PushDebug("Waypoint " + std::to_string(++i)).PushDebug(point);
}
i = 0;
@@ -556,10 +550,7 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
auto pathCopy = m_CurrentPath; // Copy to avoid modifying the original stack
while (!pathCopy.empty()) {
const auto& waypoint = pathCopy.top();
auto& pathWaypoint = currentPath.PushDebug("Waypoint " + std::to_string(++i));
pathWaypoint.PushDebug<AMFDoubleValue>("X") = waypoint.position.x;
pathWaypoint.PushDebug<AMFDoubleValue>("Y") = waypoint.position.y;
pathWaypoint.PushDebug<AMFDoubleValue>("Z") = waypoint.position.z;
currentPath.PushDebug("Waypoint " + std::to_string(++i)).PushDebug(waypoint.position);
pathCopy.pop();
}

View File

@@ -238,10 +238,7 @@ bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectRepor
info.PushDebug<AMFIntValue>("Effect Type") = static_cast<int>(m_EffectType);
info.PushDebug<AMFDoubleValue>("Directional Multiplier") = m_DirectionalMultiplier;
info.PushDebug<AMFBoolValue>("Is Directional") = m_IsDirectional;
auto& direction = info.PushDebug("Direction");
direction.PushDebug<AMFDoubleValue>("x") = m_Direction.x;
direction.PushDebug<AMFDoubleValue>("y") = m_Direction.y;
direction.PushDebug<AMFDoubleValue>("z") = m_Direction.z;
auto& direction = info.PushDebug("Direction").PushDebug(m_Direction);
if (m_MinMax) {
auto& minMaxInfo = info.PushDebug("Min Max Info");
@@ -252,15 +249,8 @@ bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectRepor
if (m_IsRespawnVolume) {
auto& respawnInfo = info.PushDebug("Respawn Info");
respawnInfo.PushDebug<AMFBoolValue>("Is Respawn Volume") = m_IsRespawnVolume;
auto& respawnPos = respawnInfo.PushDebug("Respawn Position");
respawnPos.PushDebug<AMFDoubleValue>("x") = m_RespawnPos.x;
respawnPos.PushDebug<AMFDoubleValue>("y") = m_RespawnPos.y;
respawnPos.PushDebug<AMFDoubleValue>("z") = m_RespawnPos.z;
auto& respawnRot = respawnInfo.PushDebug("Respawn Rotation");
respawnRot.PushDebug<AMFDoubleValue>("w") = m_RespawnRot.w;
respawnRot.PushDebug<AMFDoubleValue>("x") = m_RespawnRot.x;
respawnRot.PushDebug<AMFDoubleValue>("y") = m_RespawnRot.y;
respawnRot.PushDebug<AMFDoubleValue>("z") = m_RespawnRot.z;
respawnInfo.PushDebug("Respawn Position").PushDebug(m_RespawnPos);
respawnInfo.PushDebug("Respawn Rotation").PushDebug(m_RespawnRot);
}
return true;

View File

@@ -249,18 +249,11 @@ bool PhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo&
auto& info = reportInfo.info->PushDebug("Physics");
reportInfo.subCategory = &info;
auto& pos = info.PushDebug("Position");
pos.PushDebug<AMFDoubleValue>("x") = m_Position.x;
pos.PushDebug<AMFDoubleValue>("y") = m_Position.y;
pos.PushDebug<AMFDoubleValue>("z") = m_Position.z;
auto& pos = info.PushDebug("Position").PushDebug(m_Position);
auto& rot = info.PushDebug("Rotation");
rot.PushDebug<AMFDoubleValue>("w") = m_Rotation.w;
rot.PushDebug<AMFDoubleValue>("x") = m_Rotation.x;
rot.PushDebug<AMFDoubleValue>("y") = m_Rotation.y;
rot.PushDebug<AMFDoubleValue>("z") = m_Rotation.z;
auto& rot = info.PushDebug("Rotation").PushDebug(m_Rotation);
info.PushDebug<AMFIntValue>("CollisionGroup") = m_CollisionGroup;
info.PushDebug<AMFIntValue>("Collision Group") = m_CollisionGroup;
return true;
}

View File

@@ -10,7 +10,7 @@ PossessableComponent::PossessableComponent(Entity* parent, const int32_t compone
m_AnimationFlag = static_cast<eAnimationFlags>(item.animationFlag);
// Get the possession Type from the CDClient
auto query = CDClientDatabase::CreatePreppedStmt("SELECT possessionType, depossessOnHit FROM PossessableComponent WHERE id = ?;");
auto query = CDClientDatabase::CreatePreppedStmt("SELECT possessionType, depossessOnHit, skillSet FROM PossessableComponent WHERE id = ?;");
query.bind(1, static_cast<int>(componentID));
@@ -20,6 +20,7 @@ PossessableComponent::PossessableComponent(Entity* parent, const int32_t compone
if (!result.eof()) {
m_PossessionType = static_cast<ePossessionType>(result.getIntField("possessionType", 1)); // Default to Attached Visible
m_DepossessOnHit = static_cast<bool>(result.getIntField("depossessOnHit", 0));
m_SkillSet = result.getIntField("skillSet", 0);
} else {
m_PossessionType = ePossessionType::ATTACHED_VISIBLE;
m_DepossessOnHit = false;

View File

@@ -55,6 +55,12 @@ public:
*/
bool GetDepossessOnHit() const { return m_DepossessOnHit; };
/**
* Returns the skill set ID for this possessable (0 = no skill set)
* @return the skill set ID
*/
int32_t GetSkillSet() const { return m_SkillSet; };
/**
* Forcibly depossess the Entity
*/
@@ -118,4 +124,9 @@ private:
*
*/
bool m_ItemSpawned = false;
/**
* @brief Skill set ID from PossessableComponent CDClient table (0 = none)
*/
int32_t m_SkillSet = 0;
};

View File

@@ -1,11 +1,10 @@
#include "PossessorComponent.h"
#include "PossessableComponent.h"
#include "CharacterComponent.h"
#include "HavokVehiclePhysicsComponent.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "eUnequippableActiveType.h"
#include "eControlScheme.h"
#include "eStateChangeType.h"
PossessorComponent::PossessorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Possessable = LWOOBJID_EMPTY;
@@ -42,21 +41,27 @@ void PossessorComponent::Mount(Entity* mount) {
// Don't do anything if we are busy dismounting
if (GetIsDismounting() || !mount) return;
GameMessages::SendSetMountInventoryID(m_Parent, mount->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
auto* possessableComponent = mount->GetComponent<PossessableComponent>();
if (possessableComponent) {
possessableComponent->SetPossessor(m_Parent->GetObjectID());
SetPossessable(mount->GetObjectID());
SetPossessableType(possessableComponent->GetPossessionType());
if (possessableComponent->GetSkillSet() != 0) {
GameMessages::UseSkillSet useSkillSet;
useSkillSet.target = m_Parent->GetObjectID();
useSkillSet.possessedId = mount->GetObjectID();
useSkillSet.setId = possessableComponent->GetSkillSet();
useSkillSet.Send(m_Parent->GetSystemAddress());
}
}
auto characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SetIsRacing(true);
// GM's to send
GameMessages::SendSetJetPackMode(m_Parent, false);
GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress());
GameMessages::SendSetStunned(m_Parent->GetObjectID(), eStateChangeType::PUSH, m_Parent->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
if (mount->GetComponent<HavokVehiclePhysicsComponent>()) {
auto characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SetIsRacing(true);
GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress());
}
Game::entityManager->SerializeEntity(m_Parent);
Game::entityManager->SerializeEntity(mount);
@@ -72,13 +77,21 @@ void PossessorComponent::Dismount(Entity* mount, bool forceDismount) {
if (possessableComponent) {
possessableComponent->SetPossessor(LWOOBJID_EMPTY);
if (forceDismount) possessableComponent->ForceDepossess();
if (possessableComponent->GetSkillSet() != 0) {
GameMessages::UseSkillSet useSkillSet;
useSkillSet.target = m_Parent->GetObjectID();
useSkillSet.possessedId = mount->GetObjectID();
useSkillSet.setId = possessableComponent->GetSkillSet();
useSkillSet.bRemove = true;
useSkillSet.Send(m_Parent->GetSystemAddress());
}
}
Game::entityManager->SerializeEntity(m_Parent);
Game::entityManager->SerializeEntity(mount);
auto characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SetIsRacing(false);
if (mount->GetComponent<HavokVehiclePhysicsComponent>()) {
auto characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SetIsRacing(false);
}
}
// Make sure we don't have wacky controls
GameMessages::SendSetPlayerControlScheme(m_Parent, eControlScheme::SCHEME_A);
}

View File

@@ -80,8 +80,9 @@ bool ProximityMonitorComponent::OnGetObjectReportInfo(GameMessages::GetObjectRep
proxAmf.PushDebug("Position").PushDebug(entity->GetPosition());
proxAmf.PushDebug("Rotation").PushDebug(entity->GetRotation());
auto& collidingAmf = proxAmf.PushDebug("Colliding Objects");
int i = 1;
for (const auto& colliding : entity->GetCurrentlyCollidingObjects()) {
collidingAmf.PushDebug(std::to_string(colliding));
collidingAmf.PushDebug<AMFStringValue>(std::to_string(i++), "LWOOBJID") = std::to_string(colliding);
}
}

View File

@@ -576,25 +576,25 @@ bool QuickBuildComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
auto& quickbuild = reportInfo.info->PushDebug("Quick Build");
quickbuild.PushDebug<AMFStringValue>("State") = StringifiedEnum::ToString(m_State).data();
quickbuild.PushDebug<AMFDoubleValue>("Timer") = m_Timer;
quickbuild.PushDebug<AMFDoubleValue>("TimerIncomplete") = m_TimerIncomplete;
quickbuild.PushDebug("ActivatorPosition").PushDebug(m_ActivatorPosition);
quickbuild.PushDebug<AMFStringValue>("ActivatorId") = std::to_string(m_ActivatorId);
quickbuild.PushDebug<AMFBoolValue>("ShowResetEffect") = m_ShowResetEffect;
quickbuild.PushDebug<AMFDoubleValue>("Timer Incomplete") = m_TimerIncomplete;
quickbuild.PushDebug("Activator Position").PushDebug(m_ActivatorPosition);
quickbuild.PushDebug<AMFStringValue>("Activator ID", "LWOOBJID") = std::to_string(m_ActivatorId);
quickbuild.PushDebug<AMFBoolValue>("Show Reset Effect") = m_ShowResetEffect;
quickbuild.PushDebug<AMFDoubleValue>("Taken") = m_Taken;
quickbuild.PushDebug<AMFDoubleValue>("ResetTime") = m_ResetTime;
quickbuild.PushDebug<AMFDoubleValue>("CompleteTime") = m_CompleteTime;
quickbuild.PushDebug<AMFIntValue>("TakeImagination") = m_TakeImagination;
quickbuild.PushDebug<AMFDoubleValue>("Reset Time") = m_ResetTime;
quickbuild.PushDebug<AMFDoubleValue>("Complete Time") = m_CompleteTime;
quickbuild.PushDebug<AMFIntValue>("Take Imagination") = m_TakeImagination;
quickbuild.PushDebug<AMFBoolValue>("Interruptible") = m_Interruptible;
quickbuild.PushDebug<AMFBoolValue>("SelfActivator") = m_SelfActivator;
auto& modules = quickbuild.PushDebug("CustomModules");
quickbuild.PushDebug<AMFBoolValue>("Self Activator") = m_SelfActivator;
auto& modules = quickbuild.PushDebug("Custom Modules");
for (const auto cmodule : m_CustomModules) modules.PushDebug<AMFIntValue>("Module") = cmodule;
quickbuild.PushDebug<AMFIntValue>("ActivityId") = m_ActivityId;
quickbuild.PushDebug<AMFIntValue>("PostImaginationCost") = m_PostImaginationCost;
quickbuild.PushDebug<AMFDoubleValue>("TimeBeforeSmash") = m_TimeBeforeSmash;
quickbuild.PushDebug<AMFDoubleValue>("TimeBeforeDrain") = m_TimeBeforeDrain;
quickbuild.PushDebug<AMFIntValue>("DrainedImagination") = m_DrainedImagination;
quickbuild.PushDebug<AMFBoolValue>("RepositionPlayer") = m_RepositionPlayer;
quickbuild.PushDebug<AMFDoubleValue>("SoftTimer") = m_SoftTimer;
quickbuild.PushDebug<AMFStringValue>("Builder") = std::to_string(m_Builder);
quickbuild.PushDebug<AMFIntValue>("Activity Id") = m_ActivityId;
quickbuild.PushDebug<AMFIntValue>("Post Imagination Cost") = m_PostImaginationCost;
quickbuild.PushDebug<AMFDoubleValue>("Time Before Smash") = m_TimeBeforeSmash;
quickbuild.PushDebug<AMFDoubleValue>("Time Before Drain") = m_TimeBeforeDrain;
quickbuild.PushDebug<AMFIntValue>("Drained Imagination") = m_DrainedImagination;
quickbuild.PushDebug<AMFBoolValue>("Reposition Player") = m_RepositionPlayer;
quickbuild.PushDebug<AMFDoubleValue>("Soft Timer") = m_SoftTimer;
quickbuild.PushDebug<AMFStringValue>("Builder", "LWOOBJID") = std::to_string(m_Builder);
return true;
}

View File

@@ -16,7 +16,7 @@ ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, cons
m_ScriptName = scriptName;
SetScript(scriptName);
RegisterMsg(&ScriptComponent::OnGetObjectReportInfo);
Component::RegisterMsg(&ScriptComponent::OnGetObjectReportInfo);
}
void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {

View File

@@ -8,6 +8,9 @@
#include "CppScripts.h"
#include "Component.h"
#include "GameMessages.h"
#include <functional>
#include <map>
#include <string>
#include "eReplicaComponentType.h"
@@ -42,9 +45,22 @@ public:
* @param scriptName the name of the script to find
*/
void SetScript(const std::string& scriptName);
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
// Registers a message from a script to be listened for on the parent object
template<typename ScriptClass, typename DerivedMsgType>
void RegisterMsg(ScriptClass* scriptThis, bool (ScriptClass::*scriptHandler)(Entity&, DerivedMsgType&)) {
static_assert(std::is_base_of_v<GameMessages::GameMsg, DerivedMsgType>, "DerivedMsgType must derive from GameMessages::GameMsg base class.");
const auto boundMsg = std::bind(scriptHandler, scriptThis, std::placeholders::_1, std::placeholders::_2);
auto* const parent = m_Parent;
const auto castWrapper = [parent, boundMsg](GameMessages::GameMsg& msg) {
return boundMsg(*parent, static_cast<DerivedMsgType&>(msg));
};
DerivedMsgType msg;
m_Parent->RegisterMsg(msg.msgId, castWrapper);
}
private:
/**

View File

@@ -79,14 +79,8 @@ void SimplePhysicsComponent::SetPhysicsMotionState(uint32_t value) {
bool SimplePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
auto& info = reportInfo.subCategory->PushDebug("Simple Physics Info");
auto& velocity = info.PushDebug("Velocity");
velocity.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
velocity.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
velocity.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
auto& angularVelocity = info.PushDebug("Angular Velocity");
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
info.PushDebug("Velocity").PushDebug(m_Velocity);
info.PushDebug("Angular Velocity").PushDebug(m_AngularVelocity);
info.PushDebug<AMFIntValue>("Physics Motion State") = m_PhysicsMotionState;
info.PushDebug<AMFStringValue>("Climbable Type") = StringifiedEnum::ToString(m_ClimbableType).data();
return true;

View File

@@ -159,6 +159,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
InventoryComponent* inv = entity->GetComponent<InventoryComponent>();
if (inv) {
// Clear server-side skill state so AddItemSkills sends fresh AddSkill
// packets to the now-ready client. Skills sent during entity construction
// (Serialize) arrive before LWOSkillComponent is initialized and are dropped.
inv->ClearSkills();
auto items = inv->GetEquippedItems();
for (auto pair : items) {
const auto item = pair.second;

View File

@@ -3944,11 +3944,19 @@ void GameMessages::SendSetMountInventoryID(Entity* entity, const LWOOBJID& objec
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::SET_MOUNT_INVENTORY_ID);
bitStream.Write(objectID);
bitStream.Write(objectID != LWOOBJID_EMPTY);
if (objectID != LWOOBJID_EMPTY) bitStream.Write(objectID);
SEND_PACKET_BROADCAST;
}
void GameMessages::UseSkillSet::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bRemove);
bitStream.Write(possessedId != LWOOBJID_EMPTY);
if (possessedId != LWOOBJID_EMPTY) bitStream.Write(possessedId);
bitStream.Write(setId != -1);
if (setId != -1) bitStream.Write(setId);
}
void GameMessages::HandleDismountComplete(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
// Get the objectID from the bitstream
@@ -3984,9 +3992,6 @@ void GameMessages::HandleDismountComplete(RakNet::BitStream& inStream, Entity* e
// Update the entity that was possessing
Game::entityManager->SerializeEntity(entity);
// We aren't mounted so remove the stun
GameMessages::SendSetStunned(entity->GetObjectID(), eStateChangeType::POP, UNASSIGNED_SYSTEM_ADDRESS, LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
}
}
}
@@ -3994,10 +3999,14 @@ void GameMessages::HandleDismountComplete(RakNet::BitStream& inStream, Entity* e
void GameMessages::HandleAcknowledgePossession(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
Game::entityManager->SerializeEntity(entity);
LWOOBJID objectId{};
inStream.Read(objectId);
auto* mount = Game::entityManager->GetEntity(objectId);
if (mount) Game::entityManager->SerializeEntity(mount);
bool hasObjectId{};
inStream.Read(hasObjectId);
if (hasObjectId) {
LWOOBJID objectId{};
inStream.Read(objectId);
auto* mount = Game::entityManager->GetEntity(objectId);
if (mount) Game::entityManager->SerializeEntity(mount);
}
}
//Racing

View File

@@ -45,6 +45,7 @@ enum class BehaviorSlot : int32_t;
enum class eVendorTransactionResult : uint32_t;
enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t;
enum class eMissionState : int;
enum class AiState : uint32_t;
enum class eCameraTargetCyclingMode : int32_t {
ALLOW_CYCLE_TEAMMATES,
@@ -965,11 +966,27 @@ namespace GameMessages {
LWOOBJID childID{};
};
struct UseSkillSet : public GameMsg {
UseSkillSet() : GameMsg(MessageType::Game::USE_SKILL_SET) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool bRemove{};
LWOOBJID possessedId{ LWOOBJID_EMPTY };
int32_t setId{ -1 };
};
struct ObjectLoaded : public GameMsg {
ObjectLoaded() : GameMsg(MessageType::Game::OBJECT_LOADED) {}
LWOOBJID objectID{};
LOT lot{};
};
struct NotifyCombatAIStateChange : public GameMsg {
NotifyCombatAIStateChange() : GameMsg(MessageType::Game::NOTIFY_COMBAT_AI_STATE_CHANGE) {}
AiState newState{};
AiState prevState{};
};
};
#endif // GAMEMESSAGES_H

View File

@@ -1,4 +1,5 @@
set(DSCRIPTS_SOURCES_02_SERVER_ENEMY_FV
"DragonRonin.cpp"
"FvMaelstromCavalry.cpp"
"FvMaelstromDragon.cpp"
PARENT_SCOPE)

View File

@@ -0,0 +1,6 @@
#include "DragonRonin.h"
void DragonRonin::OnStartup(Entity* self) {
self->SetVar<float>(u"suicideTimer", 40.0f);
CountdownDestroyAI::OnStartup(self);
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "CountdownDestroyAI.h"
class DragonRonin : public CountdownDestroyAI {
public:
void OnStartup(Entity* self) override;
};

View File

@@ -1,6 +1,7 @@
set(DSCRIPTS_SOURCES_02_SERVER_ENEMY_GENERAL
"BaseEnemyMech.cpp"
"BaseEnemyApe.cpp"
"CountdownDestroyAI.cpp"
"GfApeSmashingQB.cpp"
"TreasureChestDragonServer.cpp"
"EnemyNjBuff.cpp"

View File

@@ -0,0 +1,50 @@
#include "CountdownDestroyAI.h"
#include "BaseCombatAIComponent.h"
#include "ScriptComponent.h"
void CountdownDestroyAI::OnStartup(Entity* self) {
CountdownStartup(*self);
auto* scriptComp = self->GetComponent<ScriptComponent>();
if (scriptComp) scriptComp->RegisterMsg(this, &CountdownDestroyAI::OnNotifyCombatAIStateChange);
}
void CountdownDestroyAI::CountdownStartup(Entity& self) {
auto suicideTimer = self.GetVar<float>(u"suicideTimer");
if (suicideTimer == 0.0f) suicideTimer = 60;
self.AddTimer("Dead", suicideTimer);
}
void CountdownDestroyAI::OnHit(Entity* self, Entity* attacker) {
if (!self->GetVar<bool>(u"ShouldBeDead")) return;
self->CancelTimer("IsBeingAttacked");
self->AddTimer("Dead", 5.0f);
}
void CountdownDestroyAI::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "Dead") {
self->SetVar<bool>(u"ShouldBeDead", true);
if (self->GetVar<bool>(u"Busy")) {
self->AddTimer("IsBeingAttacked", 5.0f);
} else {
self->Smash();
}
} else if (timerName == "IsBeingAttacked") {
self->Smash();
}
}
bool CountdownDestroyAI::OnNotifyCombatAIStateChange(Entity& self, GameMessages::NotifyCombatAIStateChange& notifyMsg) {
const auto curState = notifyMsg.newState;
if (curState == AiState::dead) return true;
if (curState == AiState::aggro || curState == AiState::tether) {
self.SetVar(u"Busy", true);
} else {
self.SetVar(u"Busy", false);
if (self.GetVar<bool>(u"ShouldBeDead")) {
self.Smash();
}
}
return true;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "CppScripts.h"
#include "GameMessages.h"
class CountdownDestroyAI : public CppScripts::Script {
public:
void OnStartup(Entity* self) override;
void CountdownStartup(Entity& self);
void OnHit(Entity* self, Entity* attacker) override;
void OnTimerDone(Entity* self, std::string timerName) override;
bool OnNotifyCombatAIStateChange(Entity& self, GameMessages::NotifyCombatAIStateChange& msg);
};

View File

@@ -6,6 +6,7 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB
"EnemySkeletonSpawner.cpp"
"FallingTile.cpp"
"FlameJetServer.cpp"
"LightningOrbServer.cpp"
"ImaginationShrineServer.cpp"
"Lieutenant.cpp"
"MonCoreNookDoors.cpp"

View File

@@ -0,0 +1,12 @@
#include "LightningOrbServer.h"
void LightningOrbServer::OnCollisionPhantom(Entity* self, Entity* target) {
GameMessages::GetPosition playerPos;
playerPos.Send(target->GetObjectID());
GameMessages::GetPosition selfPos;
selfPos.Send(self->GetObjectID());
const NiPoint3 newVec((playerPos.pos.x - selfPos.pos.x) * 2.5, 15, (playerPos.pos.z - selfPos.pos.z) * 2.5);
// ahhhh aron said to put a TODO here moving platforms don't work lol. disable this so people can actually do the puzzle
// GameMessages::SendKnockback(target->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, newVec);
// GameMessages::SendPlayFXEffect(target->GetObjectID(), -1, u"knockback", "knockback");
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "CppScripts.h"
class LightningOrbServer : public CppScripts::Script
{
public:
void OnCollisionPhantom(Entity* self, Entity* target) override;
};

View File

@@ -135,8 +135,11 @@
#include "FvMaelstromCavalry.h"
#include "FvHorsemenTrigger.h"
#include "FvFlyingCreviceDragon.h"
#include "FvDragonInstanceServer.h"
#include "FvMaelstromDragon.h"
#include "DragonRonin.h"
#include "FvDragonSmashingGolemQb.h"
#include "CountdownDestroyAI.h"
#include "TreasureChestDragonServer.h"
#include "InstanceExitTransferPlayerToLastNonInstance.h"
#include "FvFreeGfNinjas.h"
@@ -274,6 +277,7 @@
#include "MonCoreNookDoors.h"
#include "MonCoreSmashableDoors.h"
#include "FlameJetServer.h"
#include "LightningOrbServer.h"
#include "BurningTile.h"
#include "NjEarthDragonPetServer.h"
#include "NjEarthPetServer.h"
@@ -490,7 +494,10 @@ namespace {
{"scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua", []() {return new ActNinjaTurret();}},
{"scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua", []() {return new FvHorsemenTrigger();}},
{"scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua", []() {return new FvFlyingCreviceDragon();}},
{"scripts\\ai\\FV\\Dragon_Instance\\L_FV_DRAGON_INSTANCE_SERVER.lua", []() {return new FvDragonInstanceServer();}},
{"scripts\\02_server\\Enemy\\FV\\L_FV_DRAGON_RONIN.lua", []() {return new DragonRonin();}},
{"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua", []() {return new FvMaelstromDragon();}},
{"scripts\\02_server\\Enemy\\General\\L_COUNTDOWN_DESTROY_AI.lua", []() {return new CountdownDestroyAI();}},
{"scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua", []() {return new FvDragonSmashingGolemQb();}},
{"scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua", []() {return new TreasureChestDragonServer();}},
{"scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua", []() {return new InstanceExitTransferPlayerToLastNonInstance();}},
@@ -628,6 +635,7 @@ namespace {
{"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}},
{"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}},
{"scripts\\02_server\\Map\\njhub\\L_FLAME_JET_SERVER.lua", []() {return new FlameJetServer();}},
{"scripts\\02_server\\Map\\njhub\\L_LIGHTNING_ORB_SERVER.lua", []() {return new LightningOrbServer();}},
{"scripts\\02_server\\Map\\njhub\\L_BURNING_TILE.lua", []() {return new BurningTile();}},
{"scripts\\02_server\\Map\\njhub\\L_SPAWN_EARTH_PET_SERVER.lua", []() {return new NjEarthDragonPetServer();}},
{"scripts\\02_server\\Map\\njhub\\L_EARTH_PET_SERVER.lua", []() {return new NjEarthPetServer();}},

View File

@@ -18,7 +18,13 @@ set(DSCRIPTS_SOURCES_AI_FV
"FvMaelstromGeyser.cpp"
"TriggerGas.cpp")
add_subdirectory(Dragon_Instance)
foreach(file ${DSCRIPTS_SOURCES_AI_FV_DRAGON_INSTANCE})
set(DSCRIPTS_SOURCES_AI_FV ${DSCRIPTS_SOURCES_AI_FV} "Dragon_Instance/${file}")
endforeach()
add_library(dScriptsAiFV OBJECT ${DSCRIPTS_SOURCES_AI_FV})
target_include_directories(dScriptsAiFV PUBLIC ".")
target_include_directories(dScriptsAiFV PUBLIC "." "Dragon_Instance")
target_precompile_headers(dScriptsAiFV REUSE_FROM dScriptsBase)

View File

@@ -0,0 +1,3 @@
set(DSCRIPTS_SOURCES_AI_FV_DRAGON_INSTANCE
"FvDragonInstanceServer.cpp"
PARENT_SCOPE)

View File

@@ -0,0 +1,14 @@
#include "FvDragonInstanceServer.h"
#include "Entity.h"
#include "DestroyableComponent.h"
void FvDragonInstanceServer::OnPlayerLoaded(Entity* self, Entity* player) {
auto* const destComp = player->GetComponent<DestroyableComponent>();
if (destComp) {
destComp->SetHealth(destComp->GetMaxHealth());
destComp->SetArmor(destComp->GetMaxArmor());
destComp->SetImagination(destComp->GetMaxImagination());
Game::entityManager->SerializeEntity(player);
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "CppScripts.h"
class FvDragonInstanceServer : public CppScripts::Script {
public:
void OnPlayerLoaded(Entity* self, Entity* player) override;
};