Compare commits

...

3 Commits

Author SHA1 Message Date
dbc02b12b1 WIP 2023-11-13 22:39:19 -06:00
jadebenn
68e5737b74 Fix: Pets can no longer dig treasure without completing Bella Pepper's "Lost Tags" mission (#1287)
* Added mission check to pet digs

* Little formatting change

* More formatting clean-up
2023-11-13 02:41:27 -08:00
jadebenn
411dce7457 Adding damage cooldown/"invincibility frames" as in Live (#1276)
* Added cooldown handling

* Made most of the logs hidden outside of debug mode

* removed weird submodule

* kill this phantom submodule

* updated to reflect reviewed feedback

* Added IsCooldownImmune() method to DestroyableComponent

* friggin typo

* Implemented non-pending changes and added cooldown immunity functions to DestroyableComponentTests

* add trailing linebreak

* another typo :(

* flipped cooldown test order (not leaving immune)

* Clean up comment and add DestroyableComponent test
2023-11-12 05:53:03 -06:00
12 changed files with 161 additions and 47 deletions

View File

@@ -15,7 +15,9 @@ enum class eCharacterVersion : uint32_t {
// Fixes vault size value
VAULT_SIZE,
// Fixes speed base value in level component
UP_TO_DATE, // will become SPEED_BASE
SPEED_BASE,
// Fixes explore missions that were corruped
UP_TO_DATE // Will become EXPLORE
};
#endif //!__ECHARACTERVERSION__H__

View File

@@ -3,6 +3,8 @@
#include "Game.h"
#include "Logger.h"
#include "EntityManager.h"
#include "dZoneManager.h"
#include "WorldConfig.h"
#include "DestroyableComponent.h"
#include "BehaviorContext.h"
#include "eBasicAttackSuccessTypes.h"
@@ -13,8 +15,15 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) {
PlayFx(u"onhit", entity->GetObjectID());
PlayFx(u"onhit", entity->GetObjectID()); //This damage animation doesn't seem to play consistently
destroyableComponent->Damage(this->m_MaxDamage, context->originator, context->skillID);
//Handle player damage cooldown
if (entity->IsPlayer() && !this->m_DontApplyImmune) {
const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
destroyableComponent->SetDamageCooldownTimer(immunityTime);
LOG_DEBUG("Target targetEntity %llu took damage, setting damage cooldown timer to %f s", branch.target, immunityTime);
}
}
this->m_OnSuccess->Handle(context, bitStream, branch);
@@ -72,6 +81,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
}
if (isImmune) {
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
this->m_OnFailImmune->Handle(context, bitStream, branch);
return;
}
@@ -178,11 +188,15 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
return;
}
const bool isImmune = destroyableComponent->IsImmune();
const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
LOG_DEBUG("Damage cooldown timer currently %f s", destroyableComponent->GetDamageCooldownTimer());
const bool isImmune = (destroyableComponent->IsImmune()) || (destroyableComponent->IsCooldownImmune());
bitStream->Write(isImmune);
if (isImmune) {
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
this->m_OnFailImmune->Calculate(context, bitStream, branch);
return;
}
@@ -203,6 +217,12 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
bitStream->Write(isSuccess);
//Handle player damage cooldown
if (isSuccess && targetEntity->IsPlayer() && !this->m_DontApplyImmune) {
destroyableComponent->SetDamageCooldownTimer(immunityTime);
LOG_DEBUG("Target targetEntity %llu took damage, setting damage cooldown timer to %f s", branch.target, immunityTime);
}
eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;
if (isSuccess) {
if (healthDamageDealt >= 1) {
@@ -236,6 +256,8 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
}
void BasicAttackBehavior::Load() {
this->m_DontApplyImmune = GetBoolean("dont_apply_immune");
this->m_MinDamage = GetInt("min damage");
if (this->m_MinDamage == 0) this->m_MinDamage = 1;

View File

@@ -48,6 +48,8 @@ public:
*/
void Load() override;
private:
bool m_DontApplyImmune;
uint32_t m_MinDamage;
uint32_t m_MaxDamage;

View File

@@ -73,6 +73,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_ImmuneToQuickbuildInterruptCount = 0;
m_ImmuneToPullToPointCount = 0;
m_DeathBehavior = -1;
m_DamageCooldownTimer = 0.0f;
}
DestroyableComponent::~DestroyableComponent() {
@@ -179,6 +181,10 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
}
}
void DestroyableComponent::Update(float deltaTime) {
m_DamageCooldownTimer -= deltaTime;
}
void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) {
@@ -464,6 +470,10 @@ bool DestroyableComponent::IsImmune() const {
return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0;
}
bool DestroyableComponent::IsCooldownImmune() const {
return m_DamageCooldownTimer > 0.0f;
}
bool DestroyableComponent::IsKnockbackImmune() const {
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
@@ -546,7 +556,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
return;
}
if (IsImmune()) {
if (IsImmune() || IsCooldownImmune()) {
LOG_DEBUG("Target targetEntity %llu is immune!", m_Parent->GetObjectID()); //Immune is succesfully proc'd
return;
}

View File

@@ -24,6 +24,7 @@ public:
DestroyableComponent(Entity* parentEntity);
~DestroyableComponent() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override;
void LoadFromXml(tinyxml2::XMLDocument* doc) override;
void UpdateXml(tinyxml2::XMLDocument* doc) override;
@@ -166,6 +167,11 @@ public:
*/
bool IsImmune() const;
/**
* @return whether this entity is currently immune to attacks due to a damage cooldown period
*/
bool IsCooldownImmune() const;
/**
* Sets if this entity has GM immunity, making it not killable
* @param value the GM immunity of this entity
@@ -416,8 +422,13 @@ public:
const bool GetImmuneToQuickbuildInterrupt() { return m_ImmuneToQuickbuildInterruptCount > 0; };
const bool GetImmuneToPullToPoint() { return m_ImmuneToPullToPointCount > 0; };
int32_t GetDeathBehavior() const { return m_DeathBehavior; }
// Damage cooldown setters/getters
void SetDamageCooldownTimer(float value) { m_DamageCooldownTimer = value; }
float GetDamageCooldownTimer() { return m_DamageCooldownTimer; }
// Death behavior setters/getters
void SetDeathBehavior(int32_t value) { m_DeathBehavior = value; }
int32_t GetDeathBehavior() const { return m_DeathBehavior; }
/**
* Utility to reset all stats to the default stats based on items and completed missions
@@ -605,6 +616,11 @@ private:
* Death behavior type. If 0, the client plays a death animation as opposed to a smash animation.
*/
int32_t m_DeathBehavior;
/**
* Damage immunity cooldown timer. Set to a value that then counts down to create a damage cooldown for players
*/
float m_DamageCooldownTimer;
};
#endif // DESTROYABLECOMPONENT_H

View File

@@ -620,3 +620,7 @@ bool MissionComponent::HasCollectible(int32_t collectibleID) {
bool MissionComponent::HasMission(uint32_t missionId) {
return GetMission(missionId) != nullptr;
}
void FixExplorerMissions() {
}

View File

@@ -170,6 +170,9 @@ public:
*/
bool HasMission(uint32_t missionId);
void FixExplorerMissions();
private:
/**
* All the missions owned by this entity, mapped by mission ID

View File

@@ -29,6 +29,7 @@
#include "RenderComponent.h"
#include "eObjectBits.h"
#include "eGameMasterLevel.h"
#include "eMissionState.h"
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{};
@@ -439,9 +440,15 @@ void PetComponent::Update(float deltaTime) {
}
}
auto* missionComponent = owner->GetComponent<MissionComponent>();
if (!missionComponent) return;
// Determine if the "Lost Tags" mission has been completed and digging has been unlocked
const bool digUnlocked = missionComponent->GetMissionState(842) == eMissionState::COMPLETE;
Entity* closestTresure = PetDigServer::GetClosestTresure(position);
if (closestTresure != nullptr) {
if (closestTresure != nullptr && digUnlocked) {
// Skeleton Dragon Pat special case for bone digging
if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) {
goto skipTresure;

View File

@@ -1490,6 +1490,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
return;
}
//Testing basic attack immunity
if (chatCommand == "attackimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
int32_t state = false;
if (!GeneralUtils::TryParse(args[0], state)) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid state.");
return;
}
if (destroyableComponent != nullptr) {
destroyableComponent->SetIsImmune(state);
}
return;
}
if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* buffComponent = entity->GetComponent<BuffComponent>();

View File

@@ -74,6 +74,7 @@
#include "ZCompression.h"
#include "EntityManager.h"
#include "CheatDetection.h"
#include "MissionComponent.h"
namespace Game {
Logger* logger = nullptr;
@@ -1057,6 +1058,9 @@ void HandlePacket(Packet* packet) {
auto* levelComponent = player->GetComponent<LevelProgressionComponent>();
if (!levelComponent) return;
auto* missionComponent = player->GetComponent<MissionComponent>();
if (!missionComponent) return;
auto version = levelComponent->GetCharacterVersion();
switch(version) {
case eCharacterVersion::RELEASE:
@@ -1070,9 +1074,14 @@ void HandlePacket(Packet* packet) {
player->RetroactiveVaultSize();
levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE);
case eCharacterVersion::VAULT_SIZE:
LOG("Updaing Speedbase");
LOG("Updating Speedbase");
levelComponent->SetRetroactiveBaseSpeed();
levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE);
levelComponent->SetCharacterVersion(eCharacterVersion::SPEED_BASE);
case eCharacterVersion::SPEED_BASE:
LOG("Fixing Explorer missions");
missionComponent->FixExplorerMissions();
// Commented out so it runs every time for testing
// levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE);
case eCharacterVersion::UP_TO_DATE:
break;
}

View File

@@ -25,6 +25,7 @@
|ban|`/ban <username>`|Bans a user from the server.|4|
|approveproperty|`/approveproperty`|Approves the property the player is currently visiting.|5|
|mute|`/mute <username> (days) (hours)`|Mute player for the given amount of time. If no time is given, the mute is indefinite.|6|
|attackimmune|`/attackimmune <value>`|Sets the character's immunity to basic attacks state, where value can be one of "1", to make yourself immune to basic attack damage, or "0" to undo.|8|
|gmimmune|`/gmimmunve <value>`|Sets the character's GMImmune state, where value can be one of "1", to make yourself immune to damage, or "0" to undo.|8|
|gminvis|`/gminvis`|Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8.|8|
|setname|`/setname <name>`|Sets a temporary name for your player. The name resets when you log out.|8|

View File

@@ -536,3 +536,22 @@ TEST_F(DestroyableTest, DestroyableComponentImmunityTest) {
}
/**
* Test the Damage cooldown timer of DestroyableComponent
*/
TEST_F(DestroyableTest, DestroyableComponentDamageCooldownTest) {
// Test the damage immune timer state (anything above 0.0f)
destroyableComponent->SetDamageCooldownTimer(1.0f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 1.0f);
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
// Test that the Update() function correctly decrements the damage cooldown timer
destroyableComponent->Update(0.5f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.5f);
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
// Test the non damage immune timer state (anything below or equal to 0.0f)
destroyableComponent->SetDamageCooldownTimer(0.0f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.0f);
ASSERT_FALSE(destroyableComponent->IsCooldownImmune());
}