From 1e4fa884c3ad0a84e6c68907da4be18641f21cb9 Mon Sep 17 00:00:00 2001 From: Morderon Date: Fri, 11 Sep 2020 16:41:05 -0400 Subject: [PATCH 1/5] Modified Functions: UnpackEvent - now displays ID, useful to tell linked applied effects. New functions: GetTrueEffect(Count) RemoveEffectById SetEffectImmunityBypass ReplaceEffectByElement --- Plugins/Effect/Effect.cpp | 293 +++++++++++++++++----- Plugins/Effect/Effect.hpp | 12 + Plugins/Effect/NWScript/nwnx_effect.nss | 152 ++++++++++- Plugins/Effect/NWScript/nwnx_effect_t.nss | 34 +++ 4 files changed, 414 insertions(+), 77 deletions(-) diff --git a/Plugins/Effect/Effect.cpp b/Plugins/Effect/Effect.cpp index 4366236bdd1..95f8a22b697 100644 --- a/Plugins/Effect/Effect.cpp +++ b/Plugins/Effect/Effect.cpp @@ -2,6 +2,8 @@ #include "API/Constants.hpp" #include "API/Globals.hpp" +#include "API/CAppManager.hpp" +#include "API/CServerExoApp.hpp" #include "API/CExoString.hpp" #include "API/CGameEffect.hpp" #include "API/Functions.hpp" @@ -38,6 +40,11 @@ Effect::Effect(Services::ProxyServiceList* services) REGISTER(GetEffectExpiredData); REGISTER(GetEffectExpiredCreator); REGISTER(ReplaceEffect); + REGISTER(GetTrueEffectCount); + REGISTER(GetTrueEffect); + REGISTER(ReplaceEffectByElement); + REGISTER(RemoveEffectById); + REGISTER(SetEffectImmunityBypass); #undef REGISTER @@ -47,6 +54,85 @@ Effect::~Effect() { } +ArgumentStack Effect::ResolveUnpack(CGameEffect *eff, bool bLink /*=true*/) +{ + ArgumentStack stack; + + Services::Events::InsertArgument(stack, std::to_string(eff->m_nID)); + Services::Events::InsertArgument(stack, (int32_t)eff->m_nType); + Services::Events::InsertArgument(stack, (int32_t)eff->m_nSubType); + Services::Events::InsertArgument(stack, (float)eff->m_fDuration); + Services::Events::InsertArgument(stack, (int32_t)eff->m_nExpiryCalendarDay); + Services::Events::InsertArgument(stack, (int32_t)eff->m_nExpiryTimeOfDay); + Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidCreator); + Services::Events::InsertArgument(stack, (int32_t)eff->m_nSpellId); + Services::Events::InsertArgument(stack, (int32_t)eff->m_bExpose); + Services::Events::InsertArgument(stack, (int32_t)eff->m_bShowIcon); + Services::Events::InsertArgument(stack, (int32_t)eff->m_nCasterLevel); + + if(bLink) + { + // The DestroyGameEffect at the end of this function will delete any linked effects + // as well so we make a copy of the linked effects and send those for unpacking + CGameEffect *leftLinkEff = nullptr; + if (eff->m_pLinkLeft != nullptr) + { + leftLinkEff = new CGameEffect(true); + leftLinkEff->CopyEffect(eff->m_pLinkLeft, 0); + } + Services::Events::InsertArgument(stack, leftLinkEff); + Services::Events::InsertArgument(stack, eff->m_pLinkLeft != nullptr); + + CGameEffect *rightLinkEff = nullptr; + if (eff->m_pLinkRight != nullptr) + { + rightLinkEff = new CGameEffect(true); + rightLinkEff->CopyEffect(eff->m_pLinkRight, 0); + } + Services::Events::InsertArgument(stack, rightLinkEff); + Services::Events::InsertArgument(stack, eff->m_pLinkRight != nullptr); + } + + Services::Events::InsertArgument(stack, (int32_t)eff->m_nNumIntegers); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 0 ? eff->m_nParamInteger[0] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 1 ? eff->m_nParamInteger[1] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 2 ? eff->m_nParamInteger[2] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 3 ? eff->m_nParamInteger[3] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 4 ? eff->m_nParamInteger[4] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 5 ? eff->m_nParamInteger[5] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 6 ? eff->m_nParamInteger[6] : -1)); + Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 7 ? eff->m_nParamInteger[7] : -1)); + + Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[0]); + Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[1]); + Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[2]); + Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[3]); + + Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[0].CStr())); + Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[1].CStr())); + Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[2].CStr())); + Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[3].CStr())); + Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[4].CStr())); + Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[5].CStr())); + + Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[0]); + Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[1]); + Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[2]); + Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[3]); + + Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[0].x); + Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[0].y); + Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[0].z); + + Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[1].x); + Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[1].y); + Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[1].z); + + Services::Events::InsertArgument(stack, std::string(eff->m_sCustomTag.CStr())); + + return stack; +} + ArgumentStack Effect::PackEffect(ArgumentStack&& args) { CGameEffect *eff = new CGameEffect(true); @@ -116,78 +202,13 @@ ArgumentStack Effect::PackEffect(ArgumentStack&& args) return Services::Events::Arguments(eff); } + ArgumentStack Effect::UnpackEffect(ArgumentStack&& args) { ArgumentStack stack; auto eff = Services::Events::ExtractArgument(args); - Services::Events::InsertArgument(stack, (int32_t)eff->m_nType); - Services::Events::InsertArgument(stack, (int32_t)eff->m_nSubType); - Services::Events::InsertArgument(stack, (float)eff->m_fDuration); - Services::Events::InsertArgument(stack, (int32_t)eff->m_nExpiryCalendarDay); - Services::Events::InsertArgument(stack, (int32_t)eff->m_nExpiryTimeOfDay); - Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidCreator); - Services::Events::InsertArgument(stack, (int32_t)eff->m_nSpellId); - Services::Events::InsertArgument(stack, (int32_t)eff->m_bExpose); - Services::Events::InsertArgument(stack, (int32_t)eff->m_bShowIcon); - Services::Events::InsertArgument(stack, (int32_t)eff->m_nCasterLevel); - - // The DestroyGameEffect at the end of this function will delete any linked effects - // as well so we make a copy of the linked effects and send those for unpacking - CGameEffect *leftLinkEff = nullptr; - if (eff->m_pLinkLeft != nullptr) - { - leftLinkEff = new CGameEffect(true); - leftLinkEff->CopyEffect(eff->m_pLinkLeft, 0); - } - Services::Events::InsertArgument(stack, leftLinkEff); - Services::Events::InsertArgument(stack, eff->m_pLinkLeft != nullptr); - - CGameEffect *rightLinkEff = nullptr; - if (eff->m_pLinkRight != nullptr) - { - rightLinkEff = new CGameEffect(true); - rightLinkEff->CopyEffect(eff->m_pLinkRight, 0); - } - Services::Events::InsertArgument(stack, rightLinkEff); - Services::Events::InsertArgument(stack, eff->m_pLinkRight != nullptr); - - Services::Events::InsertArgument(stack, (int32_t)eff->m_nNumIntegers); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 0 ? eff->m_nParamInteger[0] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 1 ? eff->m_nParamInteger[1] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 2 ? eff->m_nParamInteger[2] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 3 ? eff->m_nParamInteger[3] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 4 ? eff->m_nParamInteger[4] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 5 ? eff->m_nParamInteger[5] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 6 ? eff->m_nParamInteger[6] : -1)); - Services::Events::InsertArgument(stack, (int32_t)(eff->m_nNumIntegers > 7 ? eff->m_nParamInteger[7] : -1)); - - Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[0]); - Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[1]); - Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[2]); - Services::Events::InsertArgument(stack, (float)eff->m_nParamFloat[3]); - - Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[0].CStr())); - Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[1].CStr())); - Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[2].CStr())); - Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[3].CStr())); - Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[4].CStr())); - Services::Events::InsertArgument(stack, std::string(eff->m_sParamString[5].CStr())); - - Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[0]); - Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[1]); - Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[2]); - Services::Events::InsertArgument(stack, (ObjectID)eff->m_oidParamObjectID[3]); - - Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[0].x); - Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[0].y); - Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[0].z); - - Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[1].x); - Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[1].y); - Services::Events::InsertArgument(stack, (float)eff->m_vParamVector[1].z); - - Services::Events::InsertArgument(stack, std::string(eff->m_sCustomTag.CStr())); + stack = ResolveUnpack(eff, true); Utils::DestroyGameEffect(eff); return stack; @@ -285,4 +306,142 @@ ArgumentStack Effect::ReplaceEffect(ArgumentStack&& args) return found; } +ArgumentStack Effect::GetTrueEffectCount(ArgumentStack&& args) +{ + int32_t retVal = 0; + auto objectId = Services::Events::ExtractArgument(args); + + if(objectId != Constants::OBJECT_INVALID) + { + if (auto *pObject = Utils::AsNWSObject(Globals::AppManager()->m_pServerExoApp->GetGameObject(objectId))) + { + retVal = pObject->m_appliedEffects.num; + } + } + + return Services::Events::Arguments(retVal); +} + +ArgumentStack Effect::GetTrueEffect(ArgumentStack&& args) +{ + auto objectId = Services::Events::ExtractArgument(args); + ASSERT_OR_THROW(objectId != Constants::OBJECT_INVALID); + auto *pObject = Utils::AsNWSObject(Globals::AppManager()->m_pServerExoApp->GetGameObject(objectId)); + ASSERT_OR_THROW(pObject); + auto it = Services::Events::ExtractArgument(args); + ASSERT_OR_THROW(it >= 0); + ASSERT_OR_THROW(it < pObject->m_appliedEffects.num); + + auto *eff = pObject->m_appliedEffects.element[it]; + + ArgumentStack stack = ResolveUnpack(eff, false); + + return stack; +} + +ArgumentStack Effect::ReplaceEffectByElement(ArgumentStack&& args) +{ + auto objectId = Services::Events::ExtractArgument(args); + if(objectId != Constants::OBJECT_INVALID) + { + if(auto *pObject = Utils::AsNWSObject(Globals::AppManager()->m_pServerExoApp->GetGameObject(objectId))) + { + auto element = Services::Events::ExtractArgument(args); + ASSERT_OR_THROW(element >= 0); + ASSERT_OR_THROW(element < pObject->m_appliedEffects.num); + auto eff = pObject->m_appliedEffects.element[element]; + + eff->m_sCustomTag = Services::Events::ExtractArgument(args).c_str(); + + auto vector1z = Services::Events::ExtractArgument(args); + auto vector1y = Services::Events::ExtractArgument(args); + auto vector1x = Services::Events::ExtractArgument(args); + eff->m_vParamVector[1] = {vector1x, vector1y, vector1z}; + + auto vector0z = Services::Events::ExtractArgument(args); + auto vector0y = Services::Events::ExtractArgument(args); + auto vector0x = Services::Events::ExtractArgument(args); + eff->m_vParamVector[0] = {vector0x, vector0y, vector0z}; + + eff->m_oidParamObjectID[3] = Services::Events::ExtractArgument(args); + eff->m_oidParamObjectID[2] = Services::Events::ExtractArgument(args); + eff->m_oidParamObjectID[1] = Services::Events::ExtractArgument(args); + eff->m_oidParamObjectID[0] = Services::Events::ExtractArgument(args); + + eff->m_sParamString[5] = Services::Events::ExtractArgument(args).c_str(); + eff->m_sParamString[4] = Services::Events::ExtractArgument(args).c_str(); + eff->m_sParamString[3] = Services::Events::ExtractArgument(args).c_str(); + eff->m_sParamString[2] = Services::Events::ExtractArgument(args).c_str(); + eff->m_sParamString[1] = Services::Events::ExtractArgument(args).c_str(); + eff->m_sParamString[0] = Services::Events::ExtractArgument(args).c_str(); + + eff->m_nParamFloat[3] = Services::Events::ExtractArgument(args); + eff->m_nParamFloat[2] = Services::Events::ExtractArgument(args); + eff->m_nParamFloat[1] = Services::Events::ExtractArgument(args); + eff->m_nParamFloat[0] = Services::Events::ExtractArgument(args); + + eff->m_nParamInteger[7] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[6] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[5] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[4] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[3] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[2] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[1] = Services::Events::ExtractArgument(args); + eff->m_nParamInteger[0] = Services::Events::ExtractArgument(args); + + eff->m_nCasterLevel = Services::Events::ExtractArgument(args); + eff->m_bShowIcon = Services::Events::ExtractArgument(args); + eff->m_bExpose = Services::Events::ExtractArgument(args); + eff->m_nSpellId = Services::Events::ExtractArgument(args); + eff->m_oidCreator = Services::Events::ExtractArgument(args); + eff->m_nExpiryTimeOfDay = Services::Events::ExtractArgument(args); + eff->m_nExpiryCalendarDay = Services::Events::ExtractArgument(args); + eff->m_fDuration = Services::Events::ExtractArgument(args); + eff->m_nSubType = Services::Events::ExtractArgument(args); + } + } + + return Services::Events::Arguments(); +} + +ArgumentStack Effect::RemoveEffectById(ArgumentStack&& args) +{ + int32_t ret = 0; + auto objectId = Services::Events::ExtractArgument(args); + if(objectId != Constants::OBJECT_INVALID) + { + if (auto *pObject = Utils::AsNWSObject(Globals::AppManager()->m_pServerExoApp->GetGameObject(objectId))) + { + auto id = Services::Events::ExtractArgument(args); + auto it = std::find_if(id.begin(), id.end(), [](unsigned char c) { return !std::isdigit(c); }); + if(!id.empty() && it == id.end()) + ret = pObject->RemoveEffectById(std::stoi(id)); + } + } + + return Services::Events::Arguments(ret); +} + +int32_t Effect::GetEffectImmunityHook(CNWSCreatureStats *pStats, uint8_t nType, CNWSCreature * pVersus, BOOL bConsiderFeats) +{ + if(g_plugin->m_bBypassImm) + return false; + + return g_plugin->m_GetEffectImmunityHook->CallOriginal(pStats, nType, pVersus, bConsiderFeats); +} + +void Effect::InitEffectImmHook() +{ + g_plugin->m_GetEffectImmunityHook = GetServices()->m_hooks->RequestExclusiveHook(&GetEffectImmunityHook); +} + +ArgumentStack Effect::SetEffectImmunityBypass(ArgumentStack&& args) +{ + if(!g_plugin->m_GetEffectImmunityHook) + InitEffectImmHook(); + + g_plugin->m_bBypassImm = Services::Events::ExtractArgument(args); + return Services::Events::Arguments(); +} + } diff --git a/Plugins/Effect/Effect.hpp b/Plugins/Effect/Effect.hpp index 027b9115a05..87e63a41fef 100644 --- a/Plugins/Effect/Effect.hpp +++ b/Plugins/Effect/Effect.hpp @@ -18,6 +18,9 @@ class Effect : public NWNXLib::Plugin std::string m_effectExpiredData; uint32_t m_effectExpiredDepth; ObjectID m_effectExpiredCreator; + bool m_bBypassImm = false; + + NWNXLib::Hooking::FunctionHook* m_GetEffectImmunityHook; ArgumentStack PackEffect(ArgumentStack&& args); ArgumentStack UnpackEffect(ArgumentStack&& args); @@ -25,6 +28,15 @@ class Effect : public NWNXLib::Plugin ArgumentStack GetEffectExpiredData(ArgumentStack&& args); ArgumentStack GetEffectExpiredCreator(ArgumentStack&& args); ArgumentStack ReplaceEffect(ArgumentStack&& args); + ArgumentStack GetTrueEffectCount(ArgumentStack&& args); + ArgumentStack GetTrueEffect(ArgumentStack&& args); + ArgumentStack ReplaceEffectByElement(ArgumentStack&& args); + ArgumentStack RemoveEffectById(ArgumentStack&& args); + ArgumentStack SetEffectImmunityBypass(ArgumentStack&& args); + static int32_t GetEffectImmunityHook(CNWSCreatureStats *pStats, uint8_t nType, CNWSCreature * pVersus, BOOL bConsiderFeats=true); + + ArgumentStack ResolveUnpack(CGameEffect *eff, bool bLink=true); + void InitEffectImmHook(); }; } diff --git a/Plugins/Effect/NWScript/nwnx_effect.nss b/Plugins/Effect/NWScript/nwnx_effect.nss index fdc61035bd4..95b25f77298 100644 --- a/Plugins/Effect/NWScript/nwnx_effect.nss +++ b/Plugins/Effect/NWScript/nwnx_effect.nss @@ -9,6 +9,7 @@ const string NWNX_Effect = "NWNX_Effect"; ///< @private /// An unpacked effect struct NWNX_EffectUnpacked { + string sID; ///< @todo Describe int nType; ///< @todo Describe int nSubType; ///< @todo Describe @@ -90,16 +91,40 @@ object NWNX_Effect_GetEffectExpiredCreator(); /// @return Number of internal effects updated. int NWNX_Effect_ReplaceEffect(object obj, effect eOld, effect eNew); -/// @} +/// @brief Gets the true effect count +/// @param oObjece The object to get the count of. +/// @return the number of effects (item propertiies and other non-exposed effects included) +int NWNX_Effect_GetTrueEffectCount(object oObject); -struct NWNX_EffectUnpacked NWNX_Effect_UnpackEffect(effect e) -{ - string sFunc = "UnpackEffect"; +/// @brief Gets a specific effect on an object. This can grab effects normally hidden from developers, such as item properties. +/// @param oObject The object with the effect +/// @param nElement The point in the array to retrieve (0 to GetTrueEffectCount()) +/// @return A constructed NWNX_EffectUnpacked. +struct NWNX_EffectUnpacked NWNX_Effect_GetTrueEffect(object oObject, int nElement); - NWNX_PushArgumentEffect(NWNX_Effect, sFunc, e); - NWNX_CallFunction(NWNX_Effect, sFunc); +/// @brief Replaces an already applied effect with another. +/// @param oObject The object with the effect to replace +/// @param nElement The array element to be replaced +/// @param e The unpacked effect to replace it with. +/// @note Cannot replace an effect with a different type. +void NWNX_Effect_ReplaceEffectByElement(object oObject, int nElement, struct NWNX_EffectUnpacked e); + +/// @brief Removes effect by ID +/// @param oObject The object to remove the effect from +/// @param sID The id of the effect, can be retrieved by unpacking effects. +/// @return FALSE/0 on failure TRUE/1 on success. +int NWNX_Effect_RemoveEffectById(object oObject, string sID); + +/// @brief Disables effect-level immunities. +/// @param nEnable set to true to turn on. +void NWNX_Effect_SetEffectImmunityBypass(int nEnable); +/// @} + +struct NWNX_EffectUnpacked __NWNX_Effect_ResolveUnpack(string sFunc, int bLink=TRUE) +{ struct NWNX_EffectUnpacked n; + n.sTag = NWNX_GetReturnValueString(NWNX_Effect, sFunc); float fZ = NWNX_GetReturnValueFloat(NWNX_Effect, sFunc); @@ -134,10 +159,13 @@ struct NWNX_EffectUnpacked NWNX_Effect_UnpackEffect(effect e) n.nParam0 = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); n.nNumIntegers = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); - n.bLinkRightValid = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); - n.eLinkRight = NWNX_GetReturnValueEffect(NWNX_Effect, sFunc); - n.bLinkLeftValid = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); - n.eLinkLeft = NWNX_GetReturnValueEffect(NWNX_Effect, sFunc); + if(bLink) + { + n.bLinkRightValid = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); + n.eLinkRight = NWNX_GetReturnValueEffect(NWNX_Effect, sFunc); + n.bLinkLeftValid = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); + n.eLinkLeft = NWNX_GetReturnValueEffect(NWNX_Effect, sFunc); + } n.nCasterLevel = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); n.bShowIcon = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); @@ -151,9 +179,20 @@ struct NWNX_EffectUnpacked NWNX_Effect_UnpackEffect(effect e) n.nSubType = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); n.nType = NWNX_GetReturnValueInt(NWNX_Effect, sFunc); + n.sID = NWNX_GetReturnValueString(NWNX_Effect, sFunc); return n; } + +struct NWNX_EffectUnpacked NWNX_Effect_UnpackEffect(effect e) +{ + string sFunc = "UnpackEffect"; + + NWNX_PushArgumentEffect(NWNX_Effect, sFunc, e); + NWNX_CallFunction(NWNX_Effect, sFunc); + + return __NWNX_Effect_ResolveUnpack(sFunc); +} effect NWNX_Effect_PackEffect(struct NWNX_EffectUnpacked e) { string sFunc = "PackEffect"; @@ -257,3 +296,96 @@ int NWNX_Effect_ReplaceEffect(object obj, effect eOld, effect eNew) return NWNX_GetReturnValueInt(NWNX_Effect, sFunc); } + +int NWNX_Effect_GetTrueEffectCount(object oObject) +{ + string sFunc = "GetTrueEffectCount"; + NWNX_PushArgumentObject(NWNX_Effect, sFunc, oObject); + NWNX_CallFunction(NWNX_Effect, sFunc); + + return NWNX_GetReturnValueInt(NWNX_Effect,sFunc); +} + +struct NWNX_EffectUnpacked NWNX_Effect_GetTrueEffect(object oObject, int nElement) +{ + string sFunc = "GetTrueEffect"; + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nElement); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, oObject); + NWNX_CallFunction(NWNX_Effect, sFunc); + + return __NWNX_Effect_ResolveUnpack(sFunc, FALSE); +} + +void NWNX_Effect_ReplaceEffectByElement(object oObject, int nElement, struct NWNX_EffectUnpacked e) +{ + string sFunc = "ReplaceEffectByElement"; + + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nSubType); + + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.fDuration); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nExpiryCalendarDay); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nExpiryTimeOfDay); + + NWNX_PushArgumentObject(NWNX_Effect, sFunc, e.oCreator); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nSpellId); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.bExpose); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.bShowIcon); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nCasterLevel); + + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam0); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam1); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam2); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam3); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam4); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam5); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam6); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, e.nParam7); + + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.fParam0); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.fParam1); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.fParam2); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.fParam3); + + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sParam0); + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sParam1); + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sParam2); + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sParam3); + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sParam4); + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sParam5); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, e.oParam0); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, e.oParam1); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, e.oParam2); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, e.oParam3); + + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.vParam0.x); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.vParam0.y); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.vParam0.z); + + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.vParam1.x); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.vParam1.y); + NWNX_PushArgumentFloat(NWNX_Effect, sFunc, e.vParam1.z); + + NWNX_PushArgumentString(NWNX_Effect, sFunc, e.sTag); + + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nElement); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, oObject); + NWNX_CallFunction(NWNX_Effect, sFunc); + +} + +int NWNX_Effect_RemoveEffectById(object oObject, string sID) +{ + string sFunc = "RemoveEffectById"; + NWNX_PushArgumentString(NWNX_Effect, sFunc, sID); + NWNX_PushArgumentObject(NWNX_Effect, sFunc, oObject); + NWNX_CallFunction(NWNX_Effect, sFunc); + + return NWNX_GetReturnValueInt(NWNX_Effect,sFunc); +} + +void NWNX_Effect_SetEffectImmunityBypass(int nEnable) +{ + string sFunc = "SetEffectImmunityBypass"; + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nEnable); + NWNX_CallFunction(NWNX_Effect, sFunc); +} diff --git a/Plugins/Effect/NWScript/nwnx_effect_t.nss b/Plugins/Effect/NWScript/nwnx_effect_t.nss index 0356712dca5..42f7396aad0 100644 --- a/Plugins/Effect/NWScript/nwnx_effect_t.nss +++ b/Plugins/Effect/NWScript/nwnx_effect_t.nss @@ -4,6 +4,8 @@ void printeff(struct NWNX_EffectUnpacked n) { string s = "Unpacked effect: \n"; + + s += "sID = " + n.sID + "\n"; s += "nType = " + IntToString(n.nType) + "\n"; s += "nSubType = " + IntToString(n.nSubType) + "\n"; @@ -98,5 +100,37 @@ void main() e = GetNextEffect(oCreature); } + e = EffectLinkEffects(EffectAbilityIncrease(ABILITY_STRENGTH, 6), EffectAbilityIncrease(ABILITY_DEXTERITY, 7)); + int i; + int nCount = NWNX_Effect_GetTrueEffectCount(oCreature); + int nCon = GetAbilityScore(oCreature, ABILITY_CONSTITUTION); + int nFound; + string sID; + ApplyEffectToObject(DURATION_TYPE_PERMANENT, e, oCreature); + struct NWNX_EffectUnpacked replace; + NWNX_Tests_Report("NWNX_Effect", "GetTrueEffectCount", NWNX_Effect_GetTrueEffectCount(oCreature) > nCount); + for(i=0;inCon); + NWNX_Tests_Report("NWNX_Effect", "RemoveEffectById", NWNX_Effect_RemoveEffectById(oCreature, sID)); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectImmunity(IMMUNITY_TYPE_ABILITY_DECREASE), oCreature); + NWNX_Effect_SetEffectImmunityBypass(TRUE); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectAbilityDecrease(ABILITY_CONSTITUTION, 7), oCreature); + NWNX_Effect_SetEffectImmunityBypass(FALSE); + NWNX_Tests_Report("NWNX_Effect", "SetEffectImmunityBypass", GetAbilityScore(oCreature,ABILITY_CONSTITUTION) Date: Fri, 11 Sep 2020 16:45:30 -0400 Subject: [PATCH 2/5] Effect changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 572223829bc..5ec7e1e3a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ N/A - Creature: {Get|Set}LastItemCasterLeve() - Creature: GetArmorClassVersus() - Effect: ReplaceEffect() +- Effect: GetTrueEffectCount() +- Effect: GetTrueEffect() +- Effect: RemoveEffectById() +- Effect: ReplaceEffectByElement() +- Effect: SetEffectImmunityBypass() - Player: ToggleDM() - ItemProperty: GetActiveProperty() - Object: DoSpellImmunity() @@ -44,6 +49,7 @@ N/A ### Changed - Effect: (Un)PackEffect now supports vector params +- Effect: UnpackEffect additionally now supports outputting the effect ID. - Events: added a `RESULT` event data tag to LearnScroll in ItemEvents - Weapon: SetWeapon****Feat functions may be called multiple times for the same weapon, associating a new feat each time - Weapon: weapon feats defined in the 2da are no longer overridden by SetWeapon***Feat and will be used in addition to any set feats From 9000d362e25a687b90216273893391c92c4dc62f Mon Sep 17 00:00:00 2001 From: Morderon Date: Sat, 26 Sep 2020 19:25:47 -0400 Subject: [PATCH 3/5] Adds Damage reduction material based reduction: The +5 (or whatever) remains in effect and the material property acts as an or. % based critical/sneak attack immunity. Known issues: When multiple linked damage reduction is restored incorrectly. % based immunities only work if the item property hook is active. To do: Documentation --- Plugins/Effect/Effect.cpp | 194 ++++++++++++++++++++++++ Plugins/Effect/Effect.hpp | 21 +++ Plugins/Effect/NWScript/nwnx_effect.nss | 22 +++ 3 files changed, 237 insertions(+) diff --git a/Plugins/Effect/Effect.cpp b/Plugins/Effect/Effect.cpp index 95f8a22b697..097102cbbe1 100644 --- a/Plugins/Effect/Effect.cpp +++ b/Plugins/Effect/Effect.cpp @@ -10,6 +10,14 @@ #include "API/CVirtualMachine.hpp" #include "API/CNWSObject.hpp" #include "Utils.hpp" +#include "Services/Config/Config.hpp" +#include "API/CNWSCreatureStats.hpp" +#include "API/CNWSCreature.hpp" +#include "API/CNWRules.hpp" +#include "API/CNWItemProperty.hpp" +#include "API/CNWSCombatRound.hpp" +#include "API/CNWSItem.hpp" +#include "API/CNWSInventory.hpp" #include @@ -45,9 +53,24 @@ Effect::Effect(Services::ProxyServiceList* services) REGISTER(ReplaceEffectByElement); REGISTER(RemoveEffectById); REGISTER(SetEffectImmunityBypass); + REGISTER(SetDamageReductionBypass); #undef REGISTER + auto bDR = GetServices()->m_config->Get("EXTEND_DAMAGE_REDUCTION_ITEM_PROPERTIES", false); + auto bSC = GetServices()->m_config->Get("EXTEND_SNEAK_CRIT_IMM_ITEM_PROPERTIES", false); + + if(bDR || bSC) + { + GetServices()->m_hooks->RequestSharedHook(&OnItemPropertyAppliedHook); + if(bDR) + GetServices()->m_hooks->RequestSharedHook(&OnApplyDamageReductionHook); + if(bSC) + { + GetServices()->m_hooks->RequestSharedHook(&OnApplyEffectImmunityHook); + m_GetEffectImmunityHook = GetServices()->m_hooks->RequestExclusiveHook(&GetEffectImmunityHook); + } + } } Effect::~Effect() @@ -427,6 +450,40 @@ int32_t Effect::GetEffectImmunityHook(CNWSCreatureStats *pStats, uint8_t nType, if(g_plugin->m_bBypassImm) return false; + if(nType == Constants::ImmunityType::CriticalHit || nType == Constants::ImmunityType::SneakAttack) + { + if(bConsiderFeats && pStats->HasFeat(Constants::Feat::DeathlessMastery)) + return true; + + auto effectList = pStats->m_pBaseCreature->m_appliedEffects; + uint8_t highest = 0; + for (int32_t i = 0; i < effectList.num; i++) + { + auto *eff = effectList.element[i]; + + if(eff->m_nType==Constants::EffectTrueType::Immunity && eff->m_nParamInteger[0]==nType) + { + if((eff->m_nParamInteger[1] == Constants::RacialType::All || (pVersus != nullptr && eff->m_nParamInteger[1] == pVersus->m_pStats->m_nRace)) && //race check + (eff->m_nParamInteger[2] == Constants::Alignment::All || (pVersus != nullptr && eff->m_nParamInteger[2] == pVersus->m_pStats->m_nAlignmentLawChaos)) && + (eff->m_nParamInteger[3] == Constants::Alignment::All || (pVersus != nullptr && eff->m_nParamInteger[3] == pVersus->m_pStats->m_nAlignmentGoodEvil))) + { + if(eff->m_nParamInteger[4] == 0 || eff->m_nParamInteger[4] == 100) + return true; + + if(eff->m_nParamInteger[4] > highest) + highest = eff->m_nParamInteger[4]; + } + + } + + } + + if(highest > 0 && Globals::Rules()->RollDice(1, 100) <= highest) + return true; + + return false; + } + return g_plugin->m_GetEffectImmunityHook->CallOriginal(pStats, nType, pVersus, bConsiderFeats); } @@ -444,4 +501,141 @@ ArgumentStack Effect::SetEffectImmunityBypass(ArgumentStack&& args) return Services::Events::Arguments(); } +void Effect::InitDamageReductionHooks() +{ + GetServices()->m_hooks->RequestSharedHook(&DoDamageReductionHook); + g_plugin->m_bInitRed = true; +} + +void Effect::OnItemPropertyAppliedHook(bool before, CServerAIMaster*, CNWSItem*, CNWItemProperty *pItemProperty, CNWSCreature*, uint32_t, BOOL) +{ + if(before) + { + if(pItemProperty->m_nParam1Value > 0) + { + if(pItemProperty->m_nPropertyName==Constants::ItemProperty::DamageReduction || pItemProperty->m_nPropertyName==Constants::ItemProperty::ImmunityMiscellaneous) + { + g_plugin->m_iMaterial=pItemProperty->m_nParam1Value; + } + } + } +} + +void Effect::OnApplyDamageReductionHook(bool before, CNWSEffectListHandler*, CNWSObject*, CGameEffect* pEffect, BOOL) +{ + if(before && g_plugin->m_iMaterial > 0) + { + pEffect->SetInteger(3, g_plugin->m_iMaterial); + g_plugin->m_iMaterial=0; + } +} + +void Effect::DoDamageReductionHook(bool before, CNWSObject *pObject, CNWSCreature *pCreature, int32_t, uint8_t, BOOL, BOOL) +{ + static std::unordered_map sEffects; + if(before) + { + CNWSItem* pWeapon = nullptr; + pWeapon = pCreature->m_pcCombatRound->GetCurrentAttackWeapon(); + if(pWeapon == nullptr) + return; //no need to continue as there is no material type + + if(pWeapon->m_nBaseItem == Constants::BaseItem::HeavyCrossbow || pWeapon->m_nBaseItem == Constants::BaseItem::LightCrossbow) + pWeapon = pCreature->m_pInventory->GetItemInSlot(Constants::EquipmentSlot::Bolts); + else if(pWeapon->m_nBaseItem == Constants::BaseItem::Longbow || pWeapon->m_nBaseItem == Constants::BaseItem::Shortbow) + pWeapon = pCreature->m_pInventory->GetItemInSlot(Constants::EquipmentSlot::Arrows); + else if(pWeapon->m_nBaseItem == Constants::BaseItem::Sling) + pWeapon = pCreature->m_pInventory->GetItemInSlot(Constants::EquipmentSlot::Bullets); + if(pWeapon == nullptr) + return; + bool bRemoveDR; + for (int i = 0; i < pObject->m_appliedEffects.num; i++) + { + auto *eff = pObject->m_appliedEffects.element[i]; + bRemoveDR=false; + if(eff->m_nType==Constants::EffectTrueType::DamageReduction && eff->m_nParamInteger[3] > 0) + { + auto redType = eff->m_nParamInteger[3]; + auto range = g_plugin->m_bypass.equal_range(redType); + for (auto it= range.first; it != range.second; ++it) + { + auto bypass = it->second; + for (int i = 0; i < pWeapon->m_lstPassiveProperties.num; i++) + { + auto property = pWeapon->GetPassiveProperty(i); + if (property->m_nPropertyName == bypass.m_nPropertyName && + (property->m_nCostTableValue == bypass.m_nCostTableValue || bypass.m_nCostTableValue==-1) && + (property->m_nSubType == bypass.m_nSubType || bypass.m_nSubType==-1) && + (property->m_nParam1Value == bypass.m_nParam1Value || bypass.m_nParam1Value==-1)) + { + if(!bypass.bReverse) + { + bRemoveDR=true; + } + break; //as long as we found a property, break + } + if(bypass.bReverse && i==pWeapon->m_lstPassiveProperties.num-1) //last property and we still didn't find it, so remove DR + bRemoveDR=true; + } + if(bRemoveDR) break; // no reason to kep checking + } + + } + + if(bRemoveDR) + { + sEffects[eff->m_nID] = eff->m_nParamInteger[1]; + eff->m_nParamInteger[1]=0; + } + } + } + else + { + for (int i = 0; i < pObject->m_appliedEffects.num; i++) + { + auto *eff = pObject->m_appliedEffects.element[i]; + + if(eff->m_nType==Constants::EffectTrueType::DamageReduction) + { + auto original = sEffects.find(eff->m_nID); + if (original != std::end(sEffects)) + { + eff->m_nParamInteger[1]=original->second; + sEffects.erase(eff->m_nID); + } + } + } + } +} + +ArgumentStack Effect::SetDamageReductionBypass(ArgumentStack&& args) +{ + if(!g_plugin->m_bInitRed) + InitDamageReductionHooks(); + + auto material = Services::Events::ExtractArgument(args); + auto propType = Services::Events::ExtractArgument(args); + auto subType = Services::Events::ExtractArgument(args); + auto costValue = Services::Events::ExtractArgument(args); + auto param1Value = Services::Events::ExtractArgument(args); + auto reverse = Services::Events::ExtractArgument(args); + m_bypassRed ip; + ip.m_nPropertyName=propType; + ip.m_nSubType=subType; + ip.m_nParam1Value=param1Value; + ip.m_nCostTableValue=costValue; + ip.bReverse=reverse; + g_plugin->m_bypass.insert(std::make_pair(material, ip)); + return Services::Events::Arguments(); +} + +void Effect::OnApplyEffectImmunityHook(bool before, CNWSEffectListHandler*, CNWSObject *, CGameEffect * pEffect, BOOL) +{ + if(before && g_plugin->m_iMaterial > 0) + { + pEffect->SetInteger(4, g_plugin->m_iMaterial); + g_plugin->m_iMaterial=0; + } +} + } diff --git a/Plugins/Effect/Effect.hpp b/Plugins/Effect/Effect.hpp index 87e63a41fef..034c7cdac52 100644 --- a/Plugins/Effect/Effect.hpp +++ b/Plugins/Effect/Effect.hpp @@ -15,10 +15,21 @@ class Effect : public NWNXLib::Plugin virtual ~Effect(); private: + struct m_bypassRed + { + uint16_t m_nPropertyName; + int32_t m_nSubType; + int32_t m_nCostTableValue; + int32_t m_nParam1Value; + bool bReverse; + }; std::string m_effectExpiredData; uint32_t m_effectExpiredDepth; ObjectID m_effectExpiredCreator; bool m_bBypassImm = false; + bool m_bInitRed = false; + uint8_t m_iMaterial; + std::unordered_multimap m_bypass; NWNXLib::Hooking::FunctionHook* m_GetEffectImmunityHook; @@ -33,10 +44,20 @@ class Effect : public NWNXLib::Plugin ArgumentStack ReplaceEffectByElement(ArgumentStack&& args); ArgumentStack RemoveEffectById(ArgumentStack&& args); ArgumentStack SetEffectImmunityBypass(ArgumentStack&& args); + ArgumentStack SetDamageReductionBypass(ArgumentStack&& args); + static int32_t GetEffectImmunityHook(CNWSCreatureStats *pStats, uint8_t nType, CNWSCreature * pVersus, BOOL bConsiderFeats=true); + static void OnItemPropertyAppliedHook(bool, CServerAIMaster*, CNWSItem*, CNWItemProperty*, CNWSCreature*, uint32_t, BOOL); + static void OnApplyDamageReductionHook(bool, CNWSEffectListHandler*, CNWSObject*, CGameEffect*, BOOL); + static void OnApplyEffectImmunityHook(bool, CNWSEffectListHandler*, CNWSObject*, CGameEffect*, BOOL); + static void DoDamageReductionHook(bool, CNWSObject*, CNWSCreature*, int32_t, uint8_t, BOOL, BOOL); + + ArgumentStack ResolveUnpack(CGameEffect *eff, bool bLink=true); void InitEffectImmHook(); + void InitDamageReductionHooks(); + }; } diff --git a/Plugins/Effect/NWScript/nwnx_effect.nss b/Plugins/Effect/NWScript/nwnx_effect.nss index 95b25f77298..d67377347bf 100644 --- a/Plugins/Effect/NWScript/nwnx_effect.nss +++ b/Plugins/Effect/NWScript/nwnx_effect.nss @@ -119,6 +119,15 @@ int NWNX_Effect_RemoveEffectById(object oObject, string sID); /// @param nEnable set to true to turn on. void NWNX_Effect_SetEffectImmunityBypass(int nEnable); +/// @brief Sets what item property bypasses a damage reduction material +/// @param nMaterial the material or cost value of the damage reduction to be bypaassed +/// @param nPropertyType The Property type (ITEM_PROPERTY*) of the item property that bypasses the reduction +/// @param nSubType The subtype of the item property. Set to -1 to ignore. +/// @param nCostValue The Cost Param Value of the item property +/// @param nParamValue The Param 1 Value of the item property +/// @param bReverse The damage reduction will instead resist damage if this item property is present. +void NWNX_Effect_SetDamageReductionBypass(int nMaterial, int nPropertyType, int nSubType=-1, int nCostValue=-1, int nParamValue=-1, int bReverse=FALSE); + /// @} struct NWNX_EffectUnpacked __NWNX_Effect_ResolveUnpack(string sFunc, int bLink=TRUE) @@ -389,3 +398,16 @@ void NWNX_Effect_SetEffectImmunityBypass(int nEnable) NWNX_PushArgumentInt(NWNX_Effect, sFunc, nEnable); NWNX_CallFunction(NWNX_Effect, sFunc); } + +void NWNX_Effect_SetDamageReductionBypass(int nMaterial, int nPropertyType, int nSubType=-1, int nCostValue=-1, int nParamValue=-1, int bReverse=FALSE) +{ + string sFunc = "SetDamageReductionBypass"; + NWNX_PushArgumentInt(NWNX_Effect, sFunc, bReverse); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nParamValue); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nCostValue); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nSubType); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nPropertyType); + NWNX_PushArgumentInt(NWNX_Effect, sFunc, nMaterial); + + NWNX_CallFunction(NWNX_Effect, sFunc); +} From 0baeb833dbd220991eb2f31bf9e3a4ab228a2298 Mon Sep 17 00:00:00 2001 From: Morderon Date: Sat, 26 Sep 2020 19:30:38 -0400 Subject: [PATCH 4/5] Ext script --- Plugins/Effect/NWScript/nwnx_effect_ext.nss | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Plugins/Effect/NWScript/nwnx_effect_ext.nss diff --git a/Plugins/Effect/NWScript/nwnx_effect_ext.nss b/Plugins/Effect/NWScript/nwnx_effect_ext.nss new file mode 100644 index 00000000000..76b30427691 --- /dev/null +++ b/Plugins/Effect/NWScript/nwnx_effect_ext.nss @@ -0,0 +1,24 @@ + +//Same as EffectDamageReduction, but you can set a material +effect EffectDamageReductionExt(int nAmount, int nDamagePower, int nMaterial, int nLimit=0); + +//Same as EffectImmunity, but can set a %. Critical hits and sneak attacks ONLY +effect EffectImmunityExt(int nImmunityType, int nPercent); + +effect EffectDamageReductionExt(int nAmount, int nDamagePower, int nMaterial, int nLimit=0) +{ + effect e = EffectDamageReduction(nAmount, nDamagePower, nLimit); + struct NWNX_EffectUnpacked unpacked = NWNX_Effect_UnpackEffect(e); + unpacked.nParam3=nMaterial; + return NWNX_Effect_PackEffect(unpacked); +} + +effect EffectImmunityExt(int nImmunityType, int nPercent) +{ + effect e = EffectImmunity(nImmunityType); + if(nImmunityType != IMMUNITY_TYPE_SNEAK_ATTACK && nImmunityType != IMMUNITY_TYPE_CRITICAL_HIT) + return e; + struct NWNX_EffectUnpacked unpacked = NWNX_Effect_UnpackEffect(e); + unpacked.nParam4=nPercent; + return NWNX_Effect_PackEffect(unpacked); +} From c160c4bc53a1ada32f24281f02619bd1dc63f306 Mon Sep 17 00:00:00 2001 From: Morderon Date: Sat, 26 Sep 2020 20:13:48 -0400 Subject: [PATCH 5/5] typos --- Plugins/Effect/NWScript/nwnx_effect.nss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Effect/NWScript/nwnx_effect.nss b/Plugins/Effect/NWScript/nwnx_effect.nss index d67377347bf..047b13b66f3 100644 --- a/Plugins/Effect/NWScript/nwnx_effect.nss +++ b/Plugins/Effect/NWScript/nwnx_effect.nss @@ -92,8 +92,8 @@ object NWNX_Effect_GetEffectExpiredCreator(); int NWNX_Effect_ReplaceEffect(object obj, effect eOld, effect eNew); /// @brief Gets the true effect count -/// @param oObjece The object to get the count of. -/// @return the number of effects (item propertiies and other non-exposed effects included) +/// @param oObject The object to get the count of. +/// @return the number of effects (item properties and other non-exposed effects included) int NWNX_Effect_GetTrueEffectCount(object oObject); /// @brief Gets a specific effect on an object. This can grab effects normally hidden from developers, such as item properties.