diff --git a/CMakeLists.txt b/CMakeLists.txt index 163196fb..b9596649 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,7 @@ set(INCLUDED_DIRECTORIES "dScripts/ai" "dScripts/client" "dScripts/EquipmentScripts" + "dScripts/EquipmentTriggers" "dScripts/zone" "dScripts/02_server/DLU" "dScripts/02_server/Enemy" diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index cbab87db..16b2c5a1 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -824,6 +824,22 @@ std::vector Entity::GetScriptComponents() { return comps; } +void Entity::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName) { + if (notificationName == "HitOrHealResult" || notificationName == "Hit") { + auto* destroyableComponent = GetComponent(); + if (!destroyableComponent) return; + destroyableComponent->Subscribe(scriptObjId, scriptToAdd); + } +} + +void Entity::Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName) { + if (notificationName == "HitOrHealResult" || notificationName == "Hit") { + auto* destroyableComponent = GetComponent(); + if (!destroyableComponent) return; + destroyableComponent->Unsubscribe(scriptObjId); + } +} + void Entity::SetProximityRadius(float proxRadius, std::string name) { ProximityMonitorComponent* proxMon = GetComponent(); if (!proxMon) { diff --git a/dGame/Entity.h b/dGame/Entity.h index 6c0968f8..e0d38308 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -26,8 +26,13 @@ class Spawner; class ScriptComponent; class dpEntity; class Component; +class Item; class Character; +namespace CppScripts { + class Script; +}; + /** * An entity in the world. Has multiple components. */ @@ -139,6 +144,9 @@ public: std::vector GetScriptComponents(); + void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName); + void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName); + void SetProximityRadius(float proxRadius, std::string name); void SetProximityRadius(dpEntity* entity, std::string name); diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index c899d9e8..6902917f 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -631,6 +631,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 auto* attacker = EntityManager::Instance()->GetEntity(source); m_Parent->OnHit(attacker); m_Parent->OnHitOrHealResult(attacker, sourceDamage); + NotifySubscribers(attacker, sourceDamage); for (const auto& cb : m_OnHitCallbacks) { cb(attacker); @@ -648,6 +649,29 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 Smash(source, eKillType::VIOLENT, u"", skillID); } +void DestroyableComponent::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd) { + m_SubscribedScripts.insert(std::make_pair(scriptObjId, scriptToAdd)); + Game::logger->LogDebug("DestroyableComponent", "Added script %llu to entity %llu", scriptObjId, m_Parent->GetObjectID()); + Game::logger->LogDebug("DestroyableComponent", "Number of subscribed scripts %i", m_SubscribedScripts.size()); +} + +void DestroyableComponent::Unsubscribe(LWOOBJID scriptObjId) { + auto foundScript = m_SubscribedScripts.find(scriptObjId); + if (foundScript != m_SubscribedScripts.end()) { + m_SubscribedScripts.erase(foundScript); + Game::logger->LogDebug("DestroyableComponent", "Removed script %llu from entity %llu", scriptObjId, m_Parent->GetObjectID()); + } else { + Game::logger->LogDebug("DestroyableComponent", "Tried to remove a script for Entity %llu but script %llu didnt exist", m_Parent->GetObjectID(), scriptObjId); + } + Game::logger->LogDebug("DestroyableComponent", "Number of subscribed scripts %i", m_SubscribedScripts.size()); +} + +void DestroyableComponent::NotifySubscribers(Entity* attacker, uint32_t damage) { + for (auto script : m_SubscribedScripts) { + script.second->NotifyHitOrHealResult(m_Parent, attacker, damage); + } +} + void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType, uint32_t skillID) { if (m_iHealth > 0) { SetArmor(0); diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index b8e81b33..5bb990a7 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -7,6 +7,10 @@ #include "Entity.h" #include "Component.h" +namespace CppScripts { + class Script; +}; //! namespace CppScripts + /** * Represents the stats of an entity, for example its health, imagination and armor. Also handles factions, which * indicate which enemies this entity has. @@ -422,6 +426,17 @@ public: */ void AddFactionNoLookup(int32_t faction) { m_FactionIDs.push_back(faction); }; + /** + * Notify subscribed scripts of Damage actions. + * + * @param attacker The attacking Entity + * @param damage The amount of damage that was done + */ + void NotifySubscribers(Entity* attacker, uint32_t damage); + + void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd); + void Unsubscribe(LWOOBJID scriptObjId); + private: /** * Whether or not the health should be serialized @@ -557,6 +572,11 @@ private: * The list of callbacks that will be called when this entity gets hit */ std::vector> m_OnHitCallbacks; + + /** + * The list of scripts subscribed to this components actions + */ + std::map m_SubscribedScripts; }; #endif // DESTROYABLECOMPONENT_H diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 8215664c..eeb6afa7 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -27,8 +27,9 @@ #include "dConfig.h" #include "eItemType.h" #include "eUnequippableActiveType.h" +#include "CppScripts.h" -InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { +InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document): Component(parent) { this->m_Dirty = true; this->m_Equipped = {}; this->m_Pushed = {}; @@ -867,6 +868,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) { AddItemSkills(item->GetLot()); + EquipScripts(item); + EntityManager::Instance()->SerializeEntity(m_Parent); } @@ -895,6 +898,8 @@ void InventoryComponent::UnEquipItem(Item* item) { PurgeProxies(item); + UnequipScripts(item); + EntityManager::Instance()->SerializeEntity(m_Parent); // Trigger property event @@ -904,6 +909,37 @@ void InventoryComponent::UnEquipItem(Item* item) { } } + +void InventoryComponent::EquipScripts(Item* equippedItem) { + CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); + if (!compRegistryTable) return; + int32_t scriptComponentID = compRegistryTable->GetByIDAndType(equippedItem->GetLot(), COMPONENT_TYPE_SCRIPT, -1); + if (scriptComponentID > -1) { + CDScriptComponentTable* scriptCompTable = CDClientManager::Instance()->GetTable("ScriptComponent"); + CDScriptComponent scriptCompData = scriptCompTable->GetByID(scriptComponentID); + auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name); + if (!itemScript) { + Game::logger->Log("InventoryComponent", "null script?"); + } + itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId()); + } +} + +void InventoryComponent::UnequipScripts(Item* unequippedItem) { + CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); + if (!compRegistryTable) return; + int32_t scriptComponentID = compRegistryTable->GetByIDAndType(unequippedItem->GetLot(), COMPONENT_TYPE_SCRIPT, -1); + if (scriptComponentID > -1) { + CDScriptComponentTable* scriptCompTable = CDClientManager::Instance()->GetTable("ScriptComponent"); + CDScriptComponent scriptCompData = scriptCompTable->GetByID(scriptComponentID); + auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name); + if (!itemScript) { + Game::logger->Log("InventoryComponent", "null script?"); + } + itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId()); + } +} + void InventoryComponent::HandlePossession(Item* item) { auto* characterComponent = m_Parent->GetComponent(); if (!characterComponent) return; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 8ae35f6c..44636d95 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -352,6 +352,20 @@ public: */ static uint32_t FindSkill(LOT lot); + /** + * Call this when you equip an item. This calls OnFactionTriggerItemEquipped for any scripts found on the items. + * + * @param equippedItem The item script to lookup and call equip on + */ + void EquipScripts(Item* equippedItem); + + /** + * Call this when you unequip an item. This calls OnFactionTriggerItemUnequipped for any scripts found on the items. + * + * @param unequippedItem The item script to lookup and call unequip on + */ + void UnequipScripts(Item* unequippedItem); + ~InventoryComponent() override; private: diff --git a/dScripts/CMakeLists.txt b/dScripts/CMakeLists.txt index fce3b721..ac600dbc 100644 --- a/dScripts/CMakeLists.txt +++ b/dScripts/CMakeLists.txt @@ -39,6 +39,12 @@ foreach(file ${DSCRIPTS_SOURCES_EQUIPMENTSCRIPTS}) set(DSCRIPTS_SOURCES ${DSCRIPTS_SOURCES} "EquipmentScripts/${file}") endforeach() +add_subdirectory(EquipmentTriggers) + +foreach(file ${DSCRIPTS_SOURCES_EQUIPMENTTRIGGERSSCRIPTS}) + set(DSCRIPTS_SOURCES ${DSCRIPTS_SOURCES} "EquipmentTriggers/${file}") +endforeach() + add_subdirectory(zone) foreach(file ${DSCRIPTS_SOURCES_ZONE}) diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index dce77e4b..745a74ae 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -278,6 +278,9 @@ #include "ImaginationBackpackHealServer.h" #include "LegoDieRoll.h" #include "BuccaneerValiantShip.h" +#include "GemPack.h" +#include "ShardArmor.h" +#include "TeslaPack.h" // Survival scripts #include "AgSurvivalStromling.h" @@ -837,6 +840,12 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new BuccaneerValiantShip(); else if (scriptName == "scripts\\EquipmentScripts\\FireFirstSkillonStartup.lua") script = new FireFirstSkillonStartup(); + else if (scriptName == "scripts\\equipmenttriggers\\gempack.lua") + script = new GemPack(); + else if (scriptName == "scripts\\equipmenttriggers\\shardarmor.lua") + script = new ShardArmor(); + else if (scriptName == "scripts\\equipmenttriggers\\coilbackpack.lua") + script = new TeslaPack(); // FB else if (scriptName == "scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua") diff --git a/dScripts/CppScripts.h b/dScripts/CppScripts.h index 916d2638..e4a6d655 100644 --- a/dScripts/CppScripts.h +++ b/dScripts/CppScripts.h @@ -172,6 +172,13 @@ namespace CppScripts { */ virtual void OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) {}; + /** + * Invoked when self has received either a hit or heal. Only used for scripts subscribed to an entity. + * + * Equivalent to 'function notifyHitOrHealResult(self, msg)' + */ + virtual void NotifyHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) {}; + /** * Invoked when a player has responsed to a mission. * @@ -316,6 +323,22 @@ namespace CppScripts { virtual void OnCinematicUpdate(Entity* self, Entity* sender, eCinematicEvent event, const std::u16string& pathName, float_t pathTime, float_t totalTime, int32_t waypoint) { }; + + /** + * Used by items to tell their owner that they were equipped. + * + * @param itemOwner The owner of the item + * @param itemObjId The items Object ID + */ + virtual void OnFactionTriggerItemEquipped(Entity* itemOwner, LWOOBJID itemObjId) {}; + + /** + * Used by items to tell their owner that they were unequipped. + * + * @param itemOwner The owner of the item + * @param itemObjId The items Object ID + */ + virtual void OnFactionTriggerItemUnequipped(Entity* itemOwner, LWOOBJID itemObjId) {}; }; Script* GetScript(Entity* parent, const std::string& scriptName); diff --git a/dScripts/EquipmentTriggers/CMakeLists.txt b/dScripts/EquipmentTriggers/CMakeLists.txt new file mode 100644 index 00000000..416ef553 --- /dev/null +++ b/dScripts/EquipmentTriggers/CMakeLists.txt @@ -0,0 +1,3 @@ +set(DSCRIPTS_SOURCES_EQUIPMENTTRIGGERSSCRIPTS + "CoilBackpackBase.cpp" + PARENT_SCOPE) diff --git a/dScripts/EquipmentTriggers/CoilBackpackBase.cpp b/dScripts/EquipmentTriggers/CoilBackpackBase.cpp new file mode 100644 index 00000000..d3102e0e --- /dev/null +++ b/dScripts/EquipmentTriggers/CoilBackpackBase.cpp @@ -0,0 +1,25 @@ +#include "CoilBackpackBase.h" + +#include "Entity.h" +#include "SkillComponent.h" + +void CoilBackpackBase::OnFactionTriggerItemEquipped(Entity* itemOwner, LWOOBJID itemObjId) { + itemOwner->Subscribe(itemObjId, this, "HitOrHealResult"); + itemOwner->SetVar(u"coilCount", 0); +} + +void CoilBackpackBase::NotifyHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) { + if (damage > 0) { + self->SetVar(u"coilCount", self->GetVar(u"coilCount") + 1); + if (self->GetVar(u"coilCount") > 4) { + auto* skillComponent = self->GetComponent(); + if (!skillComponent) return; + skillComponent->CalculateBehavior(m_SkillId, m_BehaviorId, self->GetObjectID()); + self->SetVar(u"coilCount", 0); + } + } +} + +void CoilBackpackBase::OnFactionTriggerItemUnequipped(Entity* itemOwner, LWOOBJID itemObjId) { + itemOwner->Unsubscribe(itemObjId, "HitOrHealResult"); +} diff --git a/dScripts/EquipmentTriggers/CoilBackpackBase.h b/dScripts/EquipmentTriggers/CoilBackpackBase.h new file mode 100644 index 00000000..290c6c0f --- /dev/null +++ b/dScripts/EquipmentTriggers/CoilBackpackBase.h @@ -0,0 +1,21 @@ +#ifndef __GemPackBase__H__ +#define __GemPackBase__H__ + +#include "CppScripts.h" + +class CoilBackpackBase: public CppScripts::Script { +public: + CoilBackpackBase(uint32_t skillId, uint32_t behaviorId) { + m_SkillId = skillId; + m_BehaviorId = behaviorId; + }; + + void OnFactionTriggerItemEquipped(Entity* itemOwner, LWOOBJID itemObjId) override; + void NotifyHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) override; + void OnFactionTriggerItemUnequipped(Entity* itemOwner, LWOOBJID itemObjId) override; +private: + uint32_t m_SkillId = 0; + uint32_t m_BehaviorId = 0; +}; + +#endif //!__GemPackBase__H__ diff --git a/dScripts/EquipmentTriggers/GemPack.h b/dScripts/EquipmentTriggers/GemPack.h new file mode 100644 index 00000000..13bf7b0b --- /dev/null +++ b/dScripts/EquipmentTriggers/GemPack.h @@ -0,0 +1,14 @@ +#ifndef __GEMPACK__H__ +#define __GEMPACK__H__ + +#include "CoilBackpackBase.h" + +class GemPack : public CoilBackpackBase { +public: + GemPack() : CoilBackpackBase(skillId, behaviorId) {}; +private: + static const uint32_t skillId = 1488; + static const uint32_t behaviorId = 36779; +}; + +#endif //!__GEMPACK__H__ diff --git a/dScripts/EquipmentTriggers/ShardArmor.h b/dScripts/EquipmentTriggers/ShardArmor.h new file mode 100644 index 00000000..01d2fe33 --- /dev/null +++ b/dScripts/EquipmentTriggers/ShardArmor.h @@ -0,0 +1,14 @@ +#ifndef __SHARDARMOR__H__ +#define __SHARDARMOR__H__ + +#include "CoilBackpackBase.h" + +class ShardArmor : public CoilBackpackBase { +public: + ShardArmor() : CoilBackpackBase(skillId, behaviorId) {}; +private: + static const uint32_t skillId = 1249; + static const uint32_t behaviorId = 29086; +}; + +#endif //!__SHARDARMOR__H__ diff --git a/dScripts/EquipmentTriggers/TeslaPack.h b/dScripts/EquipmentTriggers/TeslaPack.h new file mode 100644 index 00000000..6e8bc9a4 --- /dev/null +++ b/dScripts/EquipmentTriggers/TeslaPack.h @@ -0,0 +1,14 @@ +#ifndef __TESLAPACK__H__ +#define __TESLAPACK__H__ + +#include "CoilBackpackBase.h" + +class TeslaPack : public CoilBackpackBase { +public: + TeslaPack() : CoilBackpackBase(skillId, behaviorId) {}; +private: + static const uint32_t skillId = 1001; + static const uint32_t behaviorId = 20917; +}; + +#endif //!__TESLAPACK__H__