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

@ -48,6 +48,8 @@ public:
*/ */
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) {
@ -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;
} }

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
@ -416,8 +422,13 @@ public:
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,13 +294,20 @@ 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) {
if (Game::zoneManager->GetMountsAllowed()){
playerInventoryComponent->HandlePossession(this); playerInventoryComponent->HandlePossession(this);
// TODO Check if mounts are allowed to be spawned } else {
ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Mounts are not allowed in this zone");
}
} else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY ) { } else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY ) {
if (Game::zoneManager->GetPetsAllowed()){
const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey);
if (databasePet.lot != LOT_NULL) { if (databasePet.lot != LOT_NULL) {
playerInventoryComponent->SpawnPet(this); 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 {
bool success = false; bool success = false;
@ -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

@ -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>();

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
@ -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
if (npc.m_LOT == 176){
npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf);
} else {
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); 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()) { if (!npc.m_Phrases.empty()) {
npcEntity->SetVar<std::vector<std::string>>(u"chats", m_PartyPhrases); npcEntity->SetVar<std::vector<std::string>>(u"chats", m_PartyPhrases);
SetupNPCTalk(npcEntity); SetupNPCTalk(npcEntity);
} }
} }
}
return; return;
} }
@ -111,29 +134,29 @@ 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 // Spawn the NPC
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); 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()){ if (!npc.m_Phrases.empty()){
npcEntity->SetVar<std::vector<std::string>>(u"chats", npc.m_Phrases); npcEntity->SetVar<std::vector<std::string>>(u"chats", npc.m_Phrases);
SetupNPCTalk(npcEntity);
}
auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>(); auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>();
LOG_DEBUG("Script: %s", npc.m_Script.c_str());
if (scriptComponent && !npc.m_Script.empty()) { if (scriptComponent && !npc.m_Script.empty()) {
scriptComponent->SetScript(npc.m_Script); scriptComponent->SetScript(npc.m_Script);
scriptComponent->SetSerialized(false); scriptComponent->SetSerialized(false);
LOG_DEBUG("Setting script to %s", npc.m_Script.c_str());
for (const auto& npc : npc.m_Flags) { for (const auto& npc : npc.m_Flags) {
npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second); npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second);
} }
} }
SetupNPCTalk(npcEntity);
}
}
} }
if (zoneID == 1200) { if (zoneID == 1200) {
@ -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

@ -54,6 +54,8 @@ 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();
std::map<uint32_t, Header> m_ChunkHeaders; std::map<uint32_t, Header> m_ChunkHeaders;

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());
}