mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-15 08:17:13 +00:00
fix(inventory): nothing is used while stunned from using items, as the client
LWOInventoryComponent_Common::msgUseNonEquipmentItem (0x00cdeba0) asks GetStunned first and uses nothing when canUseItem is false (LWOControllablePhysComponent::msgGetStunned 0x00be0df0: stunned from using items, or dead). The server now counts the use-item stun of every SetStunned it sends for an entity as msgSetStunned (0x00bf1d10) does (push/pop, never below 0, characters only, unless immune and not bIgnoreImmunity), and checks it with IsDead before using an item. The server never checked either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
#include "ModuleAssemblyComponent.h"
|
||||
#include "HavokVehiclePhysicsComponent.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "ControllablePhysicsComponent.h"
|
||||
#include "dZoneManager.h"
|
||||
#include "PropertyManagementComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
@@ -2407,8 +2408,17 @@ void InventoryComponent::OnClientItemConsumed(const GameMessages::ClientItemCons
|
||||
|
||||
void InventoryComponent::OnUseNonEquipmentItem(const GameMessages::UseNonEquipmentItem& msg) {
|
||||
auto* item = FindItemById(msg.itemToUse);
|
||||
if (!item) return;
|
||||
|
||||
if (item) item->UseNonEquip(item);
|
||||
// As LWOInventoryComponent_Common::msgUseNonEquipmentItem (0x00cdeba0): nothing is used while GetStunned says
|
||||
// canUseItem is false (LWOControllablePhysComponent::msgGetStunned 0x00be0df0: stunned from using items, or dead)
|
||||
auto* const physics = m_Parent->GetComponent<ControllablePhysicsComponent>();
|
||||
GameMessages::IsDead isDead{};
|
||||
isDead.target = m_Parent->GetObjectID();
|
||||
const bool dead = m_Parent->HandleMsg(isDead) && isDead.bDead;
|
||||
if (physics && (physics->GetStunnedFromUsingItems() || dead)) return;
|
||||
|
||||
item->UseNonEquip(item);
|
||||
}
|
||||
|
||||
void InventoryComponent::OnUpdateInventoryGroup(const GameMessages::UpdateInventoryGroup& msg) {
|
||||
|
||||
@@ -370,6 +370,13 @@ void ControllablePhysicsComponent::DeactivateBubbleBuff() {
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
};
|
||||
|
||||
void ControllablePhysicsComponent::ApplyStun(const GameMessages::SetStunned& stun) {
|
||||
// Only characters are stunned from using items
|
||||
if (!stun.bCantUseItem || !m_Parent->IsPlayer()) return;
|
||||
if (!stun.bIgnoreImmunity && m_ImmuneToStunUseItemCount > 0) return;
|
||||
m_StunnedUseItemCount = std::max(0, m_StunnedUseItemCount + (stun.StateChangeType == eStateChangeType::PUSH ? 1 : -1));
|
||||
}
|
||||
|
||||
void ControllablePhysicsComponent::SetStunImmunity(
|
||||
const eStateChangeType state,
|
||||
const LWOOBJID originator,
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace tinyxml2 {
|
||||
class Entity;
|
||||
class dpEntity;
|
||||
enum class eStateChangeType : uint32_t;
|
||||
namespace GameMessages {
|
||||
struct SetStunned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the movement of controllable Entities, e.g. enemies and players
|
||||
@@ -289,6 +292,11 @@ public:
|
||||
const bool GetImmuneToStunTurn() { return m_ImmuneToStunTurnCount > 0;};
|
||||
const bool GetImmuneToStunUseItem() { return m_ImmuneToStunUseItemCount > 0;};
|
||||
|
||||
// A SetStunned sent for this entity, counted as LWOControllablePhysComponent::msgSetStunned (0x00bf1d10) counts it.
|
||||
// Only the use-item stun is kept: the server checks it before using an item (msgGetStunned 0x00be0df0).
|
||||
void ApplyStun(const GameMessages::SetStunned& stun);
|
||||
bool GetStunnedFromUsingItems() const { return m_StunnedUseItemCount > 0; }
|
||||
|
||||
private:
|
||||
// The save's physics state (LoadFromSaveData), then where the character comes into the world
|
||||
void ReadSaveData(const tinyxml2::XMLDocument& doc);
|
||||
@@ -421,6 +429,9 @@ private:
|
||||
int32_t m_ImmuneToStunMoveCount;
|
||||
int32_t m_ImmuneToStunTurnCount;
|
||||
int32_t m_ImmuneToStunUseItemCount;
|
||||
|
||||
// Stuns from using items (ApplyStun)
|
||||
int32_t m_StunnedUseItemCount = 0;
|
||||
};
|
||||
|
||||
#endif // CONTROLLABLEPHYSICSCOMPONENT_H
|
||||
|
||||
@@ -208,6 +208,13 @@ namespace GameMessages {
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetStunned::Send(const SystemAddress& sysAddr) const {
|
||||
auto* const entity = Game::entityManager->GetEntity(target);
|
||||
auto* const physics = entity ? entity->GetComponent<ControllablePhysicsComponent>() : nullptr;
|
||||
if (physics) physics->ApplyStun(*this);
|
||||
NetGameMsg::Send(sysAddr);
|
||||
}
|
||||
|
||||
void SetStunned::Serialize(RakNet::BitStream& bitStream) const {
|
||||
BitStreamUtils::WriteOptional(bitStream, Originator, LWOOBJID_EMPTY);
|
||||
bitStream.Write(StateChangeType);
|
||||
|
||||
@@ -132,6 +132,8 @@ namespace GameMessages {
|
||||
// Server -> client.
|
||||
struct SetStunned : public NetGameMsg {
|
||||
SetStunned() : NetGameMsg(MessageType::Game::SET_STUNNED) {}
|
||||
// Also counts the stun on the target, as the client does (ControllablePhysicsComponent::ApplyStun), then sends it
|
||||
void Send(const SystemAddress& sysAddr) const;
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
|
||||
@@ -5,11 +5,17 @@
|
||||
#include "CDItemComponentTable.h"
|
||||
#include "CDLootMatrixTable.h"
|
||||
#include "CDPackageComponentTable.h"
|
||||
#include "Character.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "CombatMessages.h"
|
||||
#include "ControllablePhysicsComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "Entity.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "InventoryMessages.h"
|
||||
#include "Item.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eStateChangeType.h"
|
||||
|
||||
#include "dGameMessagesTests/GameMessageTestUtils.h"
|
||||
|
||||
@@ -65,3 +71,90 @@ TEST_F(UseItemResultTests, AnOpenedPackageReportsSuccess) {
|
||||
EXPECT_TRUE(results[0].useItemResult);
|
||||
EXPECT_EQ(package->GetCount(), 1u);
|
||||
}
|
||||
|
||||
// LWOInventoryComponent_Common::msgUseNonEquipmentItem (0x00cdeba0) uses nothing while GetStunned says the player can't
|
||||
// use items (LWOControllablePhysComponent::msgGetStunned 0x00be0df0)
|
||||
class UseItemStunTests : public UseItemResultTests {
|
||||
protected:
|
||||
std::unique_ptr<Character> character;
|
||||
ControllablePhysicsComponent* physics{};
|
||||
Item* package{};
|
||||
|
||||
void SetUp() override {
|
||||
UseItemResultTests::SetUp();
|
||||
// Again as a player
|
||||
entity.reset();
|
||||
info.lot = 1;
|
||||
CDClientManager::GetEntriesMutable<CDComponentsRegistryTable>().insert_or_assign(static_cast<uint64_t>(info.lot), 0);
|
||||
entity = std::make_unique<Entity>(1, info);
|
||||
inventoryComponent = entity->AddComponent<InventoryComponent>(-1);
|
||||
character = std::make_unique<Character>(1, nullptr);
|
||||
entity->SetCharacter(character.get());
|
||||
entity->AddComponent<CharacterComponent>(-1, character.get(), ClientAddress())->InitializeStatisticsFromString("");
|
||||
physics = entity->AddComponent<ControllablePhysicsComponent>(-1);
|
||||
auto* const items = inventoryComponent->GetInventory(eInventoryType::ITEMS);
|
||||
package = new Item(0x7000, PACKAGE, items, 0, 5, false, {}, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eLootSourceType::NONE);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
entity->SetCharacter(nullptr);
|
||||
entity.reset();
|
||||
character.reset();
|
||||
UseItemResultTests::TearDown();
|
||||
}
|
||||
|
||||
void Use() {
|
||||
GameMessages::UseNonEquipmentItem msg;
|
||||
msg.itemToUse = package->GetId();
|
||||
inventoryComponent->OnUseNonEquipmentItem(msg);
|
||||
}
|
||||
|
||||
void Stun(const eStateChangeType type, const bool cantUseItem = true) {
|
||||
GameMessages::SetStunned stun;
|
||||
stun.target = entity->GetObjectID();
|
||||
stun.StateChangeType = type;
|
||||
stun.bCantUseItem = cantUseItem;
|
||||
physics->ApplyStun(stun);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(UseItemStunTests, NothingIsUsedWhileStunnedFromUsingItems) {
|
||||
ASSERT_TRUE(entity->IsPlayer());
|
||||
Stun(eStateChangeType::PUSH);
|
||||
Stun(eStateChangeType::PUSH);
|
||||
Use();
|
||||
EXPECT_EQ(package->GetCount(), 5u);
|
||||
|
||||
Stun(eStateChangeType::POP);
|
||||
Use();
|
||||
EXPECT_EQ(package->GetCount(), 5u);
|
||||
|
||||
Stun(eStateChangeType::POP);
|
||||
Use();
|
||||
EXPECT_EQ(package->GetCount(), 4u);
|
||||
|
||||
// Other stuns don't stop it, and a pop with nothing pushed stays at 0
|
||||
Stun(eStateChangeType::PUSH, false);
|
||||
Stun(eStateChangeType::POP);
|
||||
Use();
|
||||
EXPECT_EQ(package->GetCount(), 3u);
|
||||
}
|
||||
|
||||
TEST_F(UseItemStunTests, ImmunityKeepsTheStunOff) {
|
||||
physics->SetStunImmunity(eStateChangeType::PUSH, LWOOBJID_EMPTY, false, false, false, false, false, false, true);
|
||||
GameMessages::SetStunned stun;
|
||||
stun.StateChangeType = eStateChangeType::PUSH;
|
||||
stun.bCantUseItem = true;
|
||||
stun.bIgnoreImmunity = false;
|
||||
physics->ApplyStun(stun);
|
||||
EXPECT_FALSE(physics->GetStunnedFromUsingItems());
|
||||
stun.bIgnoreImmunity = true;
|
||||
physics->ApplyStun(stun);
|
||||
EXPECT_TRUE(physics->GetStunnedFromUsingItems());
|
||||
}
|
||||
|
||||
TEST_F(UseItemStunTests, NothingIsUsedWhileDead) {
|
||||
entity->AddComponent<DestroyableComponent>(-1)->SetIsDead(true);
|
||||
Use();
|
||||
EXPECT_EQ(package->GetCount(), 5u);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user