Merge remote-tracking branch 'origin/main' into dCinema

This commit is contained in:
wincent 2023-11-14 14:06:20 +01:00
commit 25e1482a25
20 changed files with 342 additions and 171 deletions

View File

@ -31,7 +31,7 @@ void CDZoneTableTable::LoadValuesFromDatabase() {
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f); entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", "")); UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", "")); UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
UNUSED(entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "")); entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1); entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
entry.widthInChunks = tableData.getIntField("widthInChunks", -1); entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
entry.heightInChunks = tableData.getIntField("heightInChunks", -1); entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
@ -40,10 +40,10 @@ void CDZoneTableTable::LoadValuesFromDatabase() {
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f); entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", "")); UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false; entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
UNUSED(entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false); entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f); entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", "")); UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
UNUSED(entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false); entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
this->m_Entries.insert(std::make_pair(entry.zoneID, entry)); this->m_Entries.insert(std::make_pair(entry.zoneID, entry));
tableData.nextRow(); tableData.nextRow();

View File

@ -18,7 +18,7 @@ struct CDZoneTable {
float smashableMaxDistance; //!< The maximum smashable distance? float smashableMaxDistance; //!< The maximum smashable distance?
UNUSED(std::string mixerProgram); //!< ??? UNUSED(std::string mixerProgram); //!< ???
UNUSED(std::string clientPhysicsFramerate); //!< The client physics framerate UNUSED(std::string clientPhysicsFramerate); //!< The client physics framerate
UNUSED(std::string serverPhysicsFramerate); //!< The server physics framerate std::string serverPhysicsFramerate; //!< The server physics framerate
unsigned int zoneControlTemplate; //!< The Zone Control template unsigned int zoneControlTemplate; //!< The Zone Control template
unsigned int widthInChunks; //!< The width of the world in chunks unsigned int widthInChunks; //!< The width of the world in chunks
unsigned int heightInChunks; //!< The height of the world in chunks unsigned int heightInChunks; //!< The height of the world in chunks
@ -27,10 +27,10 @@ struct CDZoneTable {
float fZoneWeight; //!< ??? float fZoneWeight; //!< ???
UNUSED(std::string thumbnail); //!< The thumbnail of the world UNUSED(std::string thumbnail); //!< The thumbnail of the world
bool PlayerLoseCoinsOnDeath; //!< Whether or not the user loses coins on death bool PlayerLoseCoinsOnDeath; //!< Whether or not the user loses coins on death
UNUSED(bool disableSaveLoc); //!< Disables the saving location? bool disableSaveLoc; //!< Disables the saving location?
float teamRadius; //!< ??? float teamRadius; //!< ???
UNUSED(std::string gate_version); //!< The gate version UNUSED(std::string gate_version); //!< The gate version
UNUSED(bool mountsAllowed); //!< Whether or not mounts are allowed bool mountsAllowed; //!< Whether or not mounts are allowed
}; };
class CDZoneTableTable : public CDTable<CDZoneTableTable> { class CDZoneTableTable : public CDTable<CDZoneTableTable> {

View File

@ -314,7 +314,7 @@ void Character::SaveXMLToDatabase() {
auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID();
// lzid garbage, binary concat of zoneID, zoneInstance and zoneClone // lzid garbage, binary concat of zoneID, zoneInstance and zoneClone
if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0) { if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) {
uint64_t lzidConcat = zoneInfo.GetCloneID(); uint64_t lzidConcat = zoneInfo.GetCloneID();
lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID()); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID());
lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID()); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID());

View File

@ -3,6 +3,8 @@
#include "Game.h" #include "Game.h"
#include "Logger.h" #include "Logger.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "dZoneManager.h"
#include "WorldConfig.h"
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
#include "BehaviorContext.h" #include "BehaviorContext.h"
#include "eBasicAttackSuccessTypes.h" #include "eBasicAttackSuccessTypes.h"
@ -13,8 +15,15 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) { 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); 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); this->m_OnSuccess->Handle(context, bitStream, branch);
@ -72,6 +81,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
} }
if (isImmune) { if (isImmune) {
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
this->m_OnFailImmune->Handle(context, bitStream, branch); this->m_OnFailImmune->Handle(context, bitStream, branch);
return; return;
} }
@ -178,11 +188,15 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
return; 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); bitStream->Write(isImmune);
if (isImmune) { if (isImmune) {
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
this->m_OnFailImmune->Calculate(context, bitStream, branch); this->m_OnFailImmune->Calculate(context, bitStream, branch);
return; return;
} }
@ -203,6 +217,12 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
bitStream->Write(isSuccess); 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; eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;
if (isSuccess) { if (isSuccess) {
if (healthDamageDealt >= 1) { if (healthDamageDealt >= 1) {
@ -236,6 +256,8 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
} }
void BasicAttackBehavior::Load() { void BasicAttackBehavior::Load() {
this->m_DontApplyImmune = GetBoolean("dont_apply_immune");
this->m_MinDamage = GetInt("min damage"); this->m_MinDamage = GetInt("min damage");
if (this->m_MinDamage == 0) this->m_MinDamage = 1; if (this->m_MinDamage == 0) this->m_MinDamage = 1;

View File

@ -10,14 +10,14 @@ public:
/** /**
* @brief Reads a 16bit short from the bitStream and when the actual behavior handling finishes with all of its branches, the bitStream * @brief Reads a 16bit short from the bitStream and when the actual behavior handling finishes with all of its branches, the bitStream
* is then offset to after the allocated bits for this stream. * is then offset to after the allocated bits for this stream.
* *
*/ */
void DoHandleBehavior(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch); void DoHandleBehavior(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch);
/** /**
* @brief Handles a client initialized Basic Attack Behavior cast to be deserialized and verified on the server. * @brief Handles a client initialized Basic Attack Behavior cast to be deserialized and verified on the server.
* *
* @param context The Skill's Behavior context. All behaviors in the same tree share the same context * @param context The Skill's Behavior context. All behaviors in the same tree share the same context
* @param bitStream The bitStream to deserialize. BitStreams will always check their bounds before reading in a behavior * @param bitStream The bitStream to deserialize. BitStreams will always check their bounds before reading in a behavior
* and will fail gracefully if an overread is detected. * and will fail gracefully if an overread is detected.
* @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down. * @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down.
@ -27,13 +27,13 @@ public:
/** /**
* @brief Writes a 16bit short to the bitStream and when the actual behavior calculation finishes with all of its branches, the number * @brief Writes a 16bit short to the bitStream and when the actual behavior calculation finishes with all of its branches, the number
* of bits used is then written to where the 16bit short initially was. * of bits used is then written to where the 16bit short initially was.
* *
*/ */
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
/** /**
* @brief Calculates a server initialized Basic Attack Behavior cast to be serialized to the client * @brief Calculates a server initialized Basic Attack Behavior cast to be serialized to the client
* *
* @param context The Skill's Behavior context. All behaviors in the same tree share the same context * @param context The Skill's Behavior context. All behaviors in the same tree share the same context
* @param bitStream The bitStream to serialize to. * @param bitStream The bitStream to serialize to.
* @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down. * @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down.
@ -44,10 +44,12 @@ public:
* @brief Loads this Behaviors parameters from the database. For this behavior specifically: * @brief Loads this Behaviors parameters from the database. For this behavior specifically:
* max and min damage will always be the same. If min is less than max, they are both set to max. * max and min damage will always be the same. If min is less than max, they are both set to max.
* If an action is not in the database, then no action is taken for that result. * If an action is not in the database, then no action is taken for that result.
* *
*/ */
void Load() override; void Load() override;
private: private:
bool m_DontApplyImmune;
uint32_t m_MinDamage; uint32_t m_MinDamage;
uint32_t m_MaxDamage; uint32_t m_MaxDamage;

View File

@ -187,7 +187,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID();
if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0) { if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) {
character->SetAttribute("lzx", m_Position.x); character->SetAttribute("lzx", m_Position.x);
character->SetAttribute("lzy", m_Position.y); character->SetAttribute("lzy", m_Position.y);
character->SetAttribute("lzz", m_Position.z); character->SetAttribute("lzz", m_Position.z);

View File

@ -73,6 +73,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_ImmuneToQuickbuildInterruptCount = 0; m_ImmuneToQuickbuildInterruptCount = 0;
m_ImmuneToPullToPointCount = 0; m_ImmuneToPullToPointCount = 0;
m_DeathBehavior = -1; m_DeathBehavior = -1;
m_DamageCooldownTimer = 0.0f;
} }
DestroyableComponent::~DestroyableComponent() { 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) { void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) { if (!dest) {
@ -409,7 +415,7 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
} }
bool DestroyableComponent::IsEnemy(const Entity* other) const { bool DestroyableComponent::IsEnemy(const Entity* other) const {
if (m_Parent->IsPlayer() && other->IsPlayer()){ if (m_Parent->IsPlayer() && other->IsPlayer()) {
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>(); auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
if (!thisCharacterComponent) return false; if (!thisCharacterComponent) return false;
auto* otherCharacterComponent = other->GetComponent<CharacterComponent>(); auto* otherCharacterComponent = other->GetComponent<CharacterComponent>();
@ -464,6 +470,10 @@ bool DestroyableComponent::IsImmune() const {
return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0; return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0;
} }
bool DestroyableComponent::IsCooldownImmune() const {
return m_DamageCooldownTimer > 0.0f;
}
bool DestroyableComponent::IsKnockbackImmune() const { bool DestroyableComponent::IsKnockbackImmune() const {
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>(); auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>(); auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
@ -546,7 +556,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
return; return;
} }
if (IsImmune()) { if (IsImmune() || IsCooldownImmune()) {
LOG_DEBUG("Target targetEntity %llu is immune!", m_Parent->GetObjectID()); //Immune is succesfully proc'd
return; return;
} }
@ -634,9 +645,9 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
} }
//check if hardcore mode is enabled //check if hardcore mode is enabled
if (Game::entityManager->GetHardcoreMode()) { if (Game::entityManager->GetHardcoreMode()) {
DoHardcoreModeDrops(source); DoHardcoreModeDrops(source);
} }
Smash(source, eKillType::VIOLENT, u"", skillID); Smash(source, eKillType::VIOLENT, u"", skillID);
} }
@ -796,16 +807,16 @@ void DestroyableComponent::SetFaction(int32_t factionID, bool ignoreChecks) {
} }
void DestroyableComponent::SetStatusImmunity( void DestroyableComponent::SetStatusImmunity(
const eStateChangeType state, const eStateChangeType state,
const bool bImmuneToBasicAttack, const bool bImmuneToBasicAttack,
const bool bImmuneToDamageOverTime, const bool bImmuneToDamageOverTime,
const bool bImmuneToKnockback, const bool bImmuneToKnockback,
const bool bImmuneToInterrupt, const bool bImmuneToInterrupt,
const bool bImmuneToSpeed, const bool bImmuneToSpeed,
const bool bImmuneToImaginationGain, const bool bImmuneToImaginationGain,
const bool bImmuneToImaginationLoss, const bool bImmuneToImaginationLoss,
const bool bImmuneToQuickbuildInterrupt, const bool bImmuneToQuickbuildInterrupt,
const bool bImmuneToPullToPoint) { const bool bImmuneToPullToPoint) {
if (state == eStateChangeType::POP) { if (state == eStateChangeType::POP) {
if (bImmuneToBasicAttack && m_ImmuneToBasicAttackCount > 0) m_ImmuneToBasicAttackCount -= 1; if (bImmuneToBasicAttack && m_ImmuneToBasicAttackCount > 0) m_ImmuneToBasicAttackCount -= 1;
@ -818,7 +829,7 @@ void DestroyableComponent::SetStatusImmunity(
if (bImmuneToQuickbuildInterrupt && m_ImmuneToQuickbuildInterruptCount > 0) m_ImmuneToQuickbuildInterruptCount -= 1; if (bImmuneToQuickbuildInterrupt && m_ImmuneToQuickbuildInterruptCount > 0) m_ImmuneToQuickbuildInterruptCount -= 1;
if (bImmuneToPullToPoint && m_ImmuneToPullToPointCount > 0) m_ImmuneToPullToPointCount -= 1; if (bImmuneToPullToPoint && m_ImmuneToPullToPointCount > 0) m_ImmuneToPullToPointCount -= 1;
} else if (state == eStateChangeType::PUSH){ } else if (state == eStateChangeType::PUSH) {
if (bImmuneToBasicAttack) m_ImmuneToBasicAttackCount += 1; if (bImmuneToBasicAttack) m_ImmuneToBasicAttackCount += 1;
if (bImmuneToDamageOverTime) m_ImmuneToDamageOverTimeCount += 1; if (bImmuneToDamageOverTime) m_ImmuneToDamageOverTimeCount += 1;
if (bImmuneToKnockback) m_ImmuneToKnockbackCount += 1; if (bImmuneToKnockback) m_ImmuneToKnockbackCount += 1;
@ -945,7 +956,7 @@ void DestroyableComponent::AddOnHitCallback(const std::function<void(Entity*)>&
m_OnHitCallbacks.push_back(callback); m_OnHitCallbacks.push_back(callback);
} }
void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
//check if this is a player: //check if this is a player:
if (m_Parent->IsPlayer()) { if (m_Parent->IsPlayer()) {
//remove hardcore_lose_uscore_on_death_percent from the player's uscore: //remove hardcore_lose_uscore_on_death_percent from the player's uscore:
@ -963,9 +974,9 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){
if (inventory) { if (inventory) {
//get the items inventory: //get the items inventory:
auto items = inventory->GetInventory(eInventoryType::ITEMS); auto items = inventory->GetInventory(eInventoryType::ITEMS);
if (items){ if (items) {
auto itemMap = items->GetItems(); auto itemMap = items->GetItems();
if (!itemMap.empty()){ if (!itemMap.empty()) {
for (const auto& item : itemMap) { for (const auto& item : itemMap) {
//drop the item: //drop the item:
if (!item.second) continue; if (!item.second) continue;

View File

@ -24,6 +24,7 @@ public:
DestroyableComponent(Entity* parentEntity); DestroyableComponent(Entity* parentEntity);
~DestroyableComponent() override; ~DestroyableComponent() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override;
void LoadFromXml(tinyxml2::XMLDocument* doc) override; void LoadFromXml(tinyxml2::XMLDocument* doc) override;
void UpdateXml(tinyxml2::XMLDocument* doc) override; void UpdateXml(tinyxml2::XMLDocument* doc) override;
@ -166,6 +167,11 @@ public:
*/ */
bool IsImmune() const; 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 * Sets if this entity has GM immunity, making it not killable
* @param value the GM immunity of this entity * @param value the GM immunity of this entity
@ -406,18 +412,23 @@ public:
); );
// Getters for status immunities // Getters for status immunities
const bool GetImmuneToBasicAttack() {return m_ImmuneToBasicAttackCount > 0;}; const bool GetImmuneToBasicAttack() { return m_ImmuneToBasicAttackCount > 0; };
const bool GetImmuneToDamageOverTime() {return m_ImmuneToDamageOverTimeCount > 0;}; const bool GetImmuneToDamageOverTime() { return m_ImmuneToDamageOverTimeCount > 0; };
const bool GetImmuneToKnockback() {return m_ImmuneToKnockbackCount > 0;}; const bool GetImmuneToKnockback() { return m_ImmuneToKnockbackCount > 0; };
const bool GetImmuneToInterrupt() {return m_ImmuneToInterruptCount > 0;}; const bool GetImmuneToInterrupt() { return m_ImmuneToInterruptCount > 0; };
const bool GetImmuneToSpeed() {return m_ImmuneToSpeedCount > 0;}; const bool GetImmuneToSpeed() { return m_ImmuneToSpeedCount > 0; };
const bool GetImmuneToImaginationGain() {return m_ImmuneToImaginationGainCount > 0;}; const bool GetImmuneToImaginationGain() { return m_ImmuneToImaginationGainCount > 0; };
const bool GetImmuneToImaginationLoss() {return m_ImmuneToImaginationLossCount > 0;}; const bool GetImmuneToImaginationLoss() { return m_ImmuneToImaginationLossCount > 0; };
const bool GetImmuneToQuickbuildInterrupt() {return m_ImmuneToQuickbuildInterruptCount > 0;}; const bool GetImmuneToQuickbuildInterrupt() { return m_ImmuneToQuickbuildInterruptCount > 0; };
const bool GetImmuneToPullToPoint() {return m_ImmuneToPullToPointCount > 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; } 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 * 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. * Death behavior type. If 0, the client plays a death animation as opposed to a smash animation.
*/ */
int32_t m_DeathBehavior; 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 #endif // DESTROYABLECOMPONENT_H

View File

@ -29,6 +29,7 @@
#include "RenderComponent.h" #include "RenderComponent.h"
#include "eObjectBits.h" #include "eObjectBits.h"
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "eMissionState.h"
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{}; std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{}; 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); Entity* closestTresure = PetDigServer::GetClosestTresure(position);
if (closestTresure != nullptr) { if (closestTresure != nullptr && digUnlocked) {
// Skeleton Dragon Pat special case for bone digging // Skeleton Dragon Pat special case for bone digging
if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) { if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) {
goto skipTresure; goto skipTresure;

View File

@ -19,6 +19,8 @@
#include "eObjectBits.h" #include "eObjectBits.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
#include "eUseItemResponse.h" #include "eUseItemResponse.h"
#include "dZoneManager.h"
#include "ChatPackets.h"
#include "CDBrickIDTableTable.h" #include "CDBrickIDTableTable.h"
#include "CDObjectSkillsTable.h" #include "CDObjectSkillsTable.h"
@ -292,12 +294,19 @@ void Item::UseNonEquip(Item* item) {
const auto type = static_cast<eItemType>(info->itemType); const auto type = static_cast<eItemType>(info->itemType);
if (type == eItemType::MOUNT) { if (type == eItemType::MOUNT) {
playerInventoryComponent->HandlePossession(this); if (Game::zoneManager->GetMountsAllowed()){
// TODO Check if mounts are allowed to be spawned playerInventoryComponent->HandlePossession(this);
} else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) { } else {
const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Mounts are not allowed in this zone");
if (databasePet.lot != LOT_NULL) { }
playerInventoryComponent->SpawnPet(this); } else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY ) {
if (Game::zoneManager->GetPetsAllowed()){
const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey);
if (databasePet.lot != LOT_NULL) {
playerInventoryComponent->SpawnPet(this);
}
} else {
ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Pets are not allowed in this zone");
} }
// This precondition response is taken care of in SpawnPet(). // This precondition response is taken care of in SpawnPet().
} else { } else {
@ -433,7 +442,7 @@ void Item::DisassembleModel() {
return; return;
} }
auto* doc = new tinyxml2::XMLDocument(); std::unique_ptr<tinyxml2::XMLDocument> doc(new tinyxml2::XMLDocument());
if (!doc) { if (!doc) {
return; return;

View File

@ -252,7 +252,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
} }
if (chatCommand == "credits" || chatCommand == "info") { if (chatCommand == "credits" || chatCommand == "info") {
const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string());
{ {
AMFArrayValue args; AMFArrayValue args;
@ -1666,6 +1666,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
return; 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) { if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* buffComponent = entity->GetComponent<BuffComponent>(); auto* buffComponent = entity->GetComponent<BuffComponent>();
@ -2019,7 +2037,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
auto* skillComponent = entity->GetComponent<SkillComponent>(); auto* skillComponent = entity->GetComponent<SkillComponent>();
if (skillComponent){ if (skillComponent) {
uint32_t skillId; uint32_t skillId;
if (!GeneralUtils::TryParse(args[0], skillId)) { if (!GeneralUtils::TryParse(args[0], skillId)) {
@ -2036,7 +2054,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
uint32_t skillId; uint32_t skillId;
int slot; int slot;
auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent){ if (inventoryComponent) {
if (!GeneralUtils::TryParse(args[0], slot)) { if (!GeneralUtils::TryParse(args[0], slot)) {
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
return; return;
@ -2045,7 +2063,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
return; return;
} else { } else {
if(inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); if (inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
} }
} }
@ -2054,7 +2072,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent){ if (destroyableComponent) {
int32_t faction; int32_t faction;
if (!GeneralUtils::TryParse(args[0], faction)) { if (!GeneralUtils::TryParse(args[0], faction)) {
@ -2069,7 +2087,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent){ if (destroyableComponent) {
int32_t faction; int32_t faction;
if (!GeneralUtils::TryParse(args[0], faction)) { if (!GeneralUtils::TryParse(args[0], faction)) {
@ -2084,7 +2102,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent){ if (destroyableComponent) {
ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:");
for (const auto entry : destroyableComponent->GetFactionIDs()) { for (const auto entry : destroyableComponent->GetFactionIDs()) {
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));

View File

@ -15,6 +15,10 @@
#include "Logger.h" #include "Logger.h"
#include "BinaryPathFinder.h" #include "BinaryPathFinder.h"
#include "EntityInfo.h" #include "EntityInfo.h"
#include "Spawner.h"
#include "dZoneManager.h"
#include "../dWorldServer/ObjectIDManager.h"
#include "Level.h"
#include <fstream> #include <fstream>
@ -29,6 +33,21 @@ void VanityUtilities::SpawnVanity() {
const uint32_t zoneID = Game::server->GetZoneID(); const uint32_t zoneID = Game::server->GetZoneID();
for (const auto& npc : m_NPCs) {
if (npc.m_ID == LWOOBJID_EMPTY) continue;
if (npc.m_LOT == 176){
Game::zoneManager->RemoveSpawner(npc.m_ID);
} else{
auto* entity = Game::entityManager->GetEntity(npc.m_ID);
if (!entity) continue;
entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT);
}
}
m_NPCs.clear();
m_Parties.clear();
m_PartyPhrases.clear();
ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string());
// Loop through all parties // Loop through all parties
@ -53,7 +72,7 @@ void VanityUtilities::SpawnVanity() {
// Loop through all locations // Loop through all locations
for (const auto& location : party.m_Locations) { for (const auto& location : party.m_Locations) {
rate = GeneralUtils::GenerateRandomNumber<float>(0, 1); rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (0.75f < rate) { if (0.75f < rate) {
continue; continue;
} }
@ -66,10 +85,11 @@ void VanityUtilities::SpawnVanity() {
} }
auto& npc = npcList[npcIndex]; auto& npc = npcList[npcIndex];
// Skip spawners
if (npc.m_LOT == 176) continue;
taken.push_back(npcIndex); taken.push_back(npcIndex);
// Spawn the NPC
LOG("ldf size is %i", npc.ldf.size()); LOG("ldf size is %i", npc.ldf.size());
if (npc.ldf.empty()) { if (npc.ldf.empty()) {
npc.ldf = { npc.ldf = {
@ -79,13 +99,16 @@ void VanityUtilities::SpawnVanity() {
} }
// Spawn the NPC // Spawn the NPC
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); if (npc.m_LOT == 176){
if (!npc.m_Phrases.empty()) { npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf);
npcEntity->SetVar<std::vector<std::string>>(u"chats", m_PartyPhrases); } else {
SetupNPCTalk(npcEntity); auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf);
if (!npc.m_Phrases.empty()) {
npcEntity->SetVar<std::vector<std::string>>(u"chats", m_PartyPhrases);
SetupNPCTalk(npcEntity);
}
} }
} }
return; return;
} }
@ -111,27 +134,27 @@ void VanityUtilities::SpawnVanity() {
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
}; };
} }
if (npc.m_LOT == 176){
npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf);
} else {
// Spawn the NPC
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf);
if (!npcEntity) continue;
npc.m_ID = npcEntity->GetObjectID();
if (!npc.m_Phrases.empty()){
npcEntity->SetVar<std::vector<std::string>>(u"chats", npc.m_Phrases);
// Spawn the NPC auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>();
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf);
if (!npc.m_Phrases.empty()){
npcEntity->SetVar<std::vector<std::string>>(u"chats", npc.m_Phrases);
SetupNPCTalk(npcEntity); if (scriptComponent && !npc.m_Script.empty()) {
} scriptComponent->SetScript(npc.m_Script);
scriptComponent->SetSerialized(false);
auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>(); for (const auto& npc : npc.m_Flags) {
npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second);
LOG_DEBUG("Script: %s", npc.m_Script.c_str()); }
}
if (scriptComponent && !npc.m_Script.empty()) { SetupNPCTalk(npcEntity);
scriptComponent->SetScript(npc.m_Script);
scriptComponent->SetSerialized(false);
LOG_DEBUG("Setting script to %s", npc.m_Script.c_str());
for (const auto& npc : npc.m_Flags) {
npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second);
} }
} }
} }
@ -154,8 +177,21 @@ void VanityUtilities::SpawnVanity() {
} }
} }
Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, LWOOBJID VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector<LDFBaseData*>& ldf){
const NiQuaternion& rotation, const std::vector<LOT>& inventory, const std::vector<LDFBaseData*>& ldf) { SceneObject obj;
obj.lot = lot;
// guratantee we have no collisions
do {
obj.id = ObjectIDManager::Instance()->GenerateObjectID();
} while(Game::zoneManager->GetSpawner(obj.id));
obj.position = position;
obj.rotation = rotation;
obj.settings = ldf;
Level::MakeSpawner(obj);
return obj.id;
}
Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector<LOT>& inventory, const std::vector<LDFBaseData*>& ldf) {
EntityInfo info; EntityInfo info;
info.lot = lot; info.lot = lot;
info.pos = position; info.pos = position;
@ -167,9 +203,6 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin
entity->SetVar(u"npcName", name); entity->SetVar(u"npcName", name);
if (entity->GetVar<bool>(u"noGhosting")) entity->SetIsGhostingCandidate(false); if (entity->GetVar<bool>(u"noGhosting")) entity->SetIsGhostingCandidate(false);
// Debug print
LOG_DEBUG("Spawning NPC %s (%i) at %f, %f, %f", name.c_str(), lot, position.x, position.y, position.z);
auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent && !inventory.empty()) { if (inventoryComponent && !inventory.empty()) {
@ -184,15 +217,6 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin
destroyableComponent->SetHealth(0); destroyableComponent->SetHealth(0);
} }
auto* scriptComponent = entity->GetComponent<ScriptComponent>();
if (scriptComponent == nullptr)
{
entity->AddComponent<ScriptComponent>("", false);
}
LOG_DEBUG("NPC has script component? %s", (entity->GetComponent<ScriptComponent>() != nullptr) ? "true" : "false");
Game::entityManager->ConstructEntity(entity); Game::entityManager->ConstructEntity(entity);
return entity; return entity;

View File

@ -13,6 +13,7 @@ struct VanityNPCLocation
struct VanityNPC struct VanityNPC
{ {
LWOOBJID m_ID = LWOOBJID_EMPTY;
std::string m_Name; std::string m_Name;
LOT m_LOT; LOT m_LOT;
std::vector<LOT> m_Equipment; std::vector<LOT> m_Equipment;
@ -44,6 +45,13 @@ public:
const std::vector<LDFBaseData*>& ldf const std::vector<LDFBaseData*>& ldf
); );
static LWOOBJID SpawnSpawner(
LOT lot,
const NiPoint3& position,
const NiQuaternion& rotation,
const std::vector<LDFBaseData*>& ldf
);
static std::string ParseMarkdown( static std::string ParseMarkdown(
const std::string& file const std::string& file
); );

View File

@ -1,5 +1,6 @@
#include "PerformanceManager.h" #include "PerformanceManager.h"
#include "CDZoneTableTable.h"
#include "CDClientManager.h"
#include "UserManager.h" #include "UserManager.h"
#define SOCIAL { lowFrameDelta } #define SOCIAL { lowFrameDelta }
@ -68,11 +69,30 @@ std::map<LWOMAPID, PerformanceProfile> PerformanceManager::m_Profiles = {
}; };
void PerformanceManager::SelectProfile(LWOMAPID mapID) { void PerformanceManager::SelectProfile(LWOMAPID mapID) {
const auto pair = m_Profiles.find(mapID); // Try to get it from zoneTable
CDZoneTableTable* zoneTable = CDClientManager::Instance().GetTable<CDZoneTableTable>();
if (zoneTable) {
const CDZoneTable* zone = zoneTable->Query(mapID);
if (zone) {
if (zone->serverPhysicsFramerate == "high"){
m_CurrentProfile = { highFrameDelta };
return;
}
if (zone->serverPhysicsFramerate == "medium"){
m_CurrentProfile = { mediumFrameDelta };
return;
}
if (zone->serverPhysicsFramerate == "low"){
m_CurrentProfile = { lowFrameDelta };
return;
}
}
}
// Fall back to hardcoded list and defaults
const auto pair = m_Profiles.find(mapID);
if (pair == m_Profiles.end()) { if (pair == m_Profiles.end()) {
m_CurrentProfile = m_DefaultProfile; m_CurrentProfile = m_DefaultProfile;
return; return;
} }

View File

@ -38,6 +38,76 @@ Level::~Level() {
} }
} }
void Level::MakeSpawner(SceneObject obj){
SpawnerInfo spawnInfo = SpawnerInfo();
SpawnerNode* node = new SpawnerNode();
spawnInfo.templateID = obj.lot;
spawnInfo.spawnerID = obj.id;
spawnInfo.templateScale = obj.scale;
node->position = obj.position;
node->rotation = obj.rotation;
node->config = obj.settings;
spawnInfo.nodes.push_back(node);
for (LDFBaseData* data : obj.settings) {
if (data) {
if (data->GetKey() == u"spawntemplate") {
spawnInfo.templateID = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawner_node_id") {
node->nodeID = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawner_name") {
spawnInfo.name = data->GetValueAsString();
}
if (data->GetKey() == u"max_to_spawn") {
spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawner_active_on_load") {
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"active_on_load") {
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"respawn") {
if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds
{
spawnInfo.respawnTime = std::stof(data->GetValueAsString());
} else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms?
{
spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000;
}
}
if (data->GetKey() == u"spawnsGroupOnSmash") {
spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") {
spawnInfo.spawnOnSmashGroupName = data->GetValueAsString();
}
if (data->GetKey() == u"groupID") { // Load object groups
std::string groupStr = data->GetValueAsString();
spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';');
spawnInfo.groups.erase(spawnInfo.groups.end() - 1);
}
if (data->GetKey() == u"no_auto_spawn") {
spawnInfo.noAutoSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
}
if (data->GetKey() == u"no_timed_spawn") {
spawnInfo.noTimedSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
}
if (data->GetKey() == u"spawnActivator") {
spawnInfo.spawnActivator = static_cast<LDFData<bool>*>(data)->GetValue();
}
}
}
Game::zoneManager->MakeSpawner(spawnInfo);
}
const void Level::PrintAllObjects() { const void Level::PrintAllObjects() {
for (std::map<uint32_t, Header>::iterator it = m_ChunkHeaders.begin(); it != m_ChunkHeaders.end(); ++it) { for (std::map<uint32_t, Header>::iterator it = m_ChunkHeaders.begin(); it != m_ChunkHeaders.end(); ++it) {
if (it->second.id == Level::ChunkTypeID::SceneObjectData) { if (it->second.id == Level::ChunkTypeID::SceneObjectData) {
@ -230,74 +300,7 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
} }
if (obj.lot == 176) { //Spawner if (obj.lot == 176) { //Spawner
SpawnerInfo spawnInfo = SpawnerInfo(); MakeSpawner(obj);
SpawnerNode* node = new SpawnerNode();
spawnInfo.templateID = obj.lot;
spawnInfo.spawnerID = obj.id;
spawnInfo.templateScale = obj.scale;
node->position = obj.position;
node->rotation = obj.rotation;
node->config = obj.settings;
spawnInfo.nodes.push_back(node);
for (LDFBaseData* data : obj.settings) {
if (data) {
if (data->GetKey() == u"spawntemplate") {
spawnInfo.templateID = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawner_node_id") {
node->nodeID = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawner_name") {
spawnInfo.name = data->GetValueAsString();
}
if (data->GetKey() == u"max_to_spawn") {
spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawner_active_on_load") {
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"active_on_load") {
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"respawn") {
if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds
{
spawnInfo.respawnTime = std::stof(data->GetValueAsString());
} else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms?
{
spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000;
}
}
if (data->GetKey() == u"spawnsGroupOnSmash") {
spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString());
}
if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") {
spawnInfo.spawnOnSmashGroupName = data->GetValueAsString();
}
if (data->GetKey() == u"groupID") { // Load object groups
std::string groupStr = data->GetValueAsString();
spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';');
spawnInfo.groups.erase(spawnInfo.groups.end() - 1);
}
if (data->GetKey() == u"no_auto_spawn") {
spawnInfo.noAutoSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
}
if (data->GetKey() == u"no_timed_spawn") {
spawnInfo.noTimedSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
}
if (data->GetKey() == u"spawnActivator") {
spawnInfo.spawnActivator = static_cast<LDFData<bool>*>(data)->GetValue();
}
}
}
Spawner* spawner = new Spawner(spawnInfo);
Game::zoneManager->AddSpawner(obj.id, spawner);
} else { //Regular object } else { //Regular object
EntityInfo info; EntityInfo info;
info.spawnerID = 0; info.spawnerID = 0;

View File

@ -53,6 +53,8 @@ public:
public: public:
Level(Zone* parentZone, const std::string& filepath); Level(Zone* parentZone, const std::string& filepath);
~Level(); ~Level();
static void MakeSpawner(SceneObject obj);
const void PrintAllObjects(); const void PrintAllObjects();

View File

@ -40,6 +40,9 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
Game::entityManager->SetGhostDistanceMax(max + min); Game::entityManager->SetGhostDistanceMax(max + min);
Game::entityManager->SetGhostDistanceMin(max); Game::entityManager->SetGhostDistanceMin(max);
m_PlayerLoseCoinsOnDeath = zone->PlayerLoseCoinsOnDeath; m_PlayerLoseCoinsOnDeath = zone->PlayerLoseCoinsOnDeath;
m_DisableSaveLocation = zone->disableSaveLoc;
m_MountsAllowed = zone->mountsAllowed;
m_PetsAllowed = zone->petsAllowed;
} }
} }

View File

@ -41,6 +41,9 @@ public:
void Update(float deltaTime); void Update(float deltaTime);
Entity* GetZoneControlObject() { return m_ZoneControlObject; } Entity* GetZoneControlObject() { return m_ZoneControlObject; }
bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; } bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; }
bool GetDisableSaveLocation() { return m_DisableSaveLocation; }
bool GetMountsAllowed() { return m_MountsAllowed; }
bool GetPetsAllowed() { return m_PetsAllowed; }
uint32_t GetUniqueMissionIdStartingValue(); uint32_t GetUniqueMissionIdStartingValue();
bool CheckIfAccessibleZone(LWOMAPID zoneID); bool CheckIfAccessibleZone(LWOMAPID zoneID);
@ -58,7 +61,10 @@ private:
Zone* m_pZone = nullptr; Zone* m_pZone = nullptr;
LWOZONEID m_ZoneID; LWOZONEID m_ZoneID;
bool m_PlayerLoseCoinsOnDeath; //Do players drop coins in this zone when smashed bool m_PlayerLoseCoinsOnDeath = false;
bool m_DisableSaveLocation = false;
bool m_MountsAllowed = true;
bool m_PetsAllowed = true;
std::map<LWOOBJID, Spawner*> m_Spawners; std::map<LWOOBJID, Spawner*> m_Spawners;
WorldConfig* m_WorldConfig = nullptr; WorldConfig* m_WorldConfig = nullptr;

View File

@ -25,6 +25,7 @@
|ban|`/ban <username>`|Bans a user from the server.|4| |ban|`/ban <username>`|Bans a user from the server.|4|
|approveproperty|`/approveproperty`|Approves the property the player is currently visiting.|5| |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| |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| |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| |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| |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());
}