diff --git a/NWNXLib/Utils/CMakeLists.txt b/NWNXLib/Utils/CMakeLists.txt index 700d64c2649..57b944c59a1 100644 --- a/NWNXLib/Utils/CMakeLists.txt +++ b/NWNXLib/Utils/CMakeLists.txt @@ -1 +1,2 @@ -nwnxlib_add("String.cpp") \ No newline at end of file +nwnxlib_add("String.cpp") +nwnxlib_add("VectorMath.cpp") diff --git a/NWNXLib/Utils/VectorMath.cpp b/NWNXLib/Utils/VectorMath.cpp new file mode 100644 index 00000000000..04f7a0a181b --- /dev/null +++ b/NWNXLib/Utils/VectorMath.cpp @@ -0,0 +1,74 @@ +#include "nwnx.hpp" +#include "cmath" + +namespace NWNXLib::VectorMath +{ + float MagnitudeSquared(const Vector& v) + { + return v.x * v.x + v.y *v.y + v.z * v.z; + } + + float Magnitude(const Vector& v) + { + float f = MagnitudeSquared(v); + + if (f == 1.0f) + return f; + else if (f <= 0.0f) + return 0.0f; + + return std::sqrt(f); + } + + float Dot(const Vector& a, const Vector& b) + { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + + Vector Add(const Vector& a, const Vector& b) + { + Vector v = a; + v.x += b.x; + v.y += b.y; + v.z += b.z; + return v; + } + + Vector Subtract(const Vector& a, const Vector& b) + { + Vector v = a; + v.x -= b.x; + v.y -= b.y; + v.z -= b.z; + return v; + } + + Vector Multiply(const Vector& v, const float f) + { + return Vector(v.x * f, v.y * f, v.z * f); + } + + Vector Normalize(const Vector &v) + { + float f = MagnitudeSquared(v); + + if (f == 1.0f) + return v; + else if (f <= 0.0f) + return Vector(1.0f, 0.0f, 0.0f); + + f = std::sqrt(1 / f); + return Multiply(v, f); + } + + Vector Lineproject(const Vector &a, const Vector &b, const Vector &c) + { + Vector v = Subtract(b, a); + float f = MagnitudeSquared(v); + + if (f) + f = Dot(v, Subtract(c, a)) / f; + + return Add(a, Multiply(v, f)); + } +} diff --git a/NWNXLib/nwnx.hpp b/NWNXLib/nwnx.hpp index 62b793d0b0e..9612f53d2f4 100644 --- a/NWNXLib/nwnx.hpp +++ b/NWNXLib/nwnx.hpp @@ -173,6 +173,18 @@ namespace String std::string Basename(const std::string& path); } +namespace VectorMath +{ + float MagnitudeSquared(const Vector& v); + float Magnitude(const Vector& v); + float Dot(const Vector& a, const Vector& b); + Vector Add(const Vector& a, const Vector& b); + Vector Subtract(const Vector& a, const Vector& b); + Vector Multiply(const Vector& v, float s); + Vector Normalize(const Vector &v); + Vector Lineproject(const Vector &a, const Vector &b, const Vector &c); +} + namespace Utils { std::string ObjectIDToString(const ObjectID id); diff --git a/Plugins/Snapshot/CMakeLists.txt b/Plugins/Snapshot/CMakeLists.txt new file mode 100644 index 00000000000..f17948b025d --- /dev/null +++ b/Plugins/Snapshot/CMakeLists.txt @@ -0,0 +1 @@ +add_plugin(Snapshot "Snapshot.cpp") diff --git a/Plugins/Snapshot/NWScript/nwnx_snapshot.nss b/Plugins/Snapshot/NWScript/nwnx_snapshot.nss new file mode 100644 index 00000000000..6b05afc8485 --- /dev/null +++ b/Plugins/Snapshot/NWScript/nwnx_snapshot.nss @@ -0,0 +1,251 @@ +/// @addtogroup snapshot Snapshot +/// @brief Functions to create cool object snapshots with filters! +/// @{ +/// @file nwnx_snapshot.nss +#include "nwnx" + +const string NWNX_Snapshot = "NWNX_Snapshot"; ///< @private + +const int NWNX_SNAPSHOT_COMPARISON_EQUALS = 0; +const int NWNX_SNAPSHOT_COMPARISON_NOT_EQUALS = 1; +const int NWNX_SNAPSHOT_COMPARISON_GREATER_THAN = 2; +const int NWNX_SNAPSHOT_COMPARISON_LESSER_THAN = 3; +const int NWNX_SNAPSHOT_COMPARISON_GREATER_THAN_OR_EQUALS = 4; +const int NWNX_SNAPSHOT_COMPARISON_LESSER_THAN_OR_EQUALS = 5; + +const int NWNX_SNAPSHOT_FILTER_TYPE_TAG = 1; +const int NWNX_SNAPSHOT_FILTER_TYPE_LOCALVAR = 2; +const int NWNX_SNAPSHOT_FILTER_TYPE_CREATURE = 3; +const int NWNX_SNAPSHOT_FILTER_TYPE_LOCATION = 4; +const int NWNX_SNAPSHOT_FILTER_TYPE_APPEARANCE = 5; +const int NWNX_SNAPSHOT_FILTER_TYPE_EFFECT = 6; +const int NWNX_SNAPSHOT_FILTER_TYPE_CUSTOM = 7; +const int NWNX_SNAPSHOT_FILTER_TYPE_REPUTATION = 8; + +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_DEFAULT = 0; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_STRING_REGEX = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCALVAR_INT = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCALVAR_FLOAT = 2; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCALVAR_OBJECT = 3; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCALVAR_STRING = 4; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_ABILITY = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_ABILITY_MOD = 2; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_HAS_CLASS = 3; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_HAS_CLASS_OF_LEVEL = 4; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_IS_PLAYER_CHARACTER = 5; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_RACIAL_TYPE = 6; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_LEVEL = 7; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_HAS_FEAT = 8; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_EXPERIENCE = 9; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_GOLD = 10; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_POISONED = 11; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_DISEASED = 12; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_SOUND_SET = 13; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CREATURE_PERCEPTION = 14; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCATION_AREA = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCATION_X = 2; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCATION_Y = 3; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCATION_Z = 4; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_LOCATION_ORIENTATION = 5; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_APPEARANCE_TYPE_CREATURE = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_APPEARANCE_TYPE_PLACEABLE = 2; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_EFFECT_HAS_SPELL_EFFECT = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_EFFECT_HAS_FEAT_EFFECT = 2; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_CUSTOM_RUN_CONDITIONAL_SCRIPT = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_REPUTATION_GET_REPUTATION = 1; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_REPUTATION_IS_FRIEND = 2; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_REPUTATION_IS_NEUTRAL = 3; +const int NWNX_SNAPSHOT_FILTER_SUBTYPE_REPUTATION_IS_ENEMY = 4; + +struct NWNX_Snapshot_Filter +{ + int nType; + int nSubtype; + int nComparison; + int bInvert; + + int nParam1; + int nParam2; + string sParam1; + string sParam2; + float fParam1; + float fParam2; + object oParam1; + object oParam2; +}; + +/// @brief Create a snapshot of objects that are within a shape. +/// @note Filters for nSnapshotID=0 will be automatically cleared after this call. +/// @param nShape One of SHAPE_* +/// @param fSize Depends on nShape: +/// SHAPE_SPHERE: the radius of the sphere. +/// SHAPE_SPELLCYLINDER: the length of the cylinder. Spell Cylinder's always have a radius of 1.5m. +/// SHAPE_CONE: the widest radius of the cone. +/// SHAPE_SPELLCONE: the length of the cone in the direction of locTarget. Spell cones are always 60 degrees with the origin at OBJECT_SELF. +/// SHAPE_CUBE: half the length of one of the sides of the cube. +/// @param locTarget This is the centre of the effect, usually GetSpellTargetLocation(), or the end of a cylinder or cone. +/// @param bLineOfSight Controls whether to do a line-of-sight check on the object returned. The line of sight check is done from origin to target object at a height 1m above the ground. +/// @param nObjectFilter This allows you to filter out undesired object types, using bitwise "or". For example, to return only creatures and doors, the value for this parameter would be OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR. +/// @param vOrigin This is only used for cylinders and cones, and specifies the origin of the effect(normally the spell-caster's position). +/// @param nSnapshotID An optional ID for this snapshot. Useful if you want to nest calls to this function. +/// @return The amount of objects in the snapshot. +int NWNX_Snapshot_CreateAreaShapeSnapshot(int nShape, float fSize, location locTarget, int bLineOfSight, int nObjectFilter, vector vOrigin, int nSnapshotID = 0); + +/// @brief Create a snapshot of objects that are within an area. +/// @note Filters for nSnapshotID=0 will be automatically cleared after this call. +/// @param oArea The area. +/// @param nObjectFilter This allows you to filter out undesired object types, using bitwise "or". For example, to return only creatures and doors, the value for this parameter would be OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR. +/// @param nSnapshotID An optional ID for this snapshot. Useful if you want to nest calls to this function. +/// @return The amount of objects in the snapshot. +int NWNX_Snapshot_CreateAreaSnapshot(object oArea, int nObjectFilter = OBJECT_TYPE_CREATURE, int nSnapshotID = 0); + +/// @brief Create a snapshot of objects that are within the whole module. +/// @note Filters for nSnapshotID=0 will be automatically cleared after this call. +/// @note Does not include player characters. +/// @param nObjectFilter This allows you to filter out undesired object types, using bitwise "or". For example, to return only creatures and doors, the value for this parameter would be OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR. +/// @param nSnapshotID An optional ID for this snapshot. Useful if you want to nest calls to this function. +/// @return The amount of objects in the snapshot. +int NWNX_Snapshot_CreateModuleSnapshot(int nObjectFilter = OBJECT_TYPE_CREATURE, int nSnapshotID = 0); + +/// @brief Create a snapshot of player objects. +/// @note Filters for nSnapshotID=0 will be automatically cleared after this call. +/// @param nSnapshotID An optional ID for this snapshot. Useful if you want to nest calls to this function. +/// @return The amount of objects in the snapshot. +int NWNX_Snapshot_CreatePlayerSnapshot(int nSnapshotID = 0); + +/// @brief Gets the object at nIndex from a snapshot. +/// @param nIndex The index of the object to get. +/// @param nSnapshotID An optional ID to get objects from a specific snapshot. +/// @return The object at nIndex or OBJECT_INVALID; +object NWNX_Snapshot_GetObjectFromSnapshot(int nIndex, int nSnapshotID = 0); + +/// @brief Prune all invalid objects from a snapshot. +/// @param nSnapshotID An optional ID to prune a specific snapshot. +/// @return The number of objects in the snapshot after pruning. +int NWNX_Snapshot_PruneSnapshot(int nSnapshotID = 0); + +/// @brief Clear a snapshot, removing all objects. +/// @param nSnapshotID An optional snapshot ID to clear a specific snapshot. +void NWNX_Snapshot_ClearSnapshot(int nSnapshotID = 0); + +/// @brief Sort a snapshot by distance to oTarget. +/// @note When sorting by descending, objects in a different area than oTarget will be at the front. +/// @param oTarget The target. +/// @param bAscending TRUE to sort ascending, FALSE for descending. +/// @param nSnapshotID An optional snapshot ID. +void NWNX_Snapshot_SortSnapshotByDistance(object oTarget, int bAscending = TRUE, int nSnapshotID = 0); + +/// @brief Add a filter to nSnapshotID, should be called before creating a snapshot. +/// @param nSnapshotID A snapshot ID. +/// @param strFilter A NWNX_Snapshot_Filter struct. +void NWNX_Snapshot_AddFilter(int nSnapshotID, struct NWNX_Snapshot_Filter strFilter); + +/// @brief Remove all filters associated with nSnapshotID. +/// @param nSnapshotID A snapshot ID. +void NWNX_Snapshot_ClearFilters(int nSnapshotID); + +/// @} + +int NWNX_Snapshot_CreateAreaShapeSnapshot(int nShape, float fSize, location locTarget, int bLineOfSight, int nObjectFilter, vector vOrigin, int nSnapshotID = 0) +{ + object oArea = GetAreaFromLocation(locTarget); + vector vPosition = GetPositionFromLocation(locTarget); + + NWNX_PushArgumentInt(nSnapshotID); + NWNX_PushArgumentFloat(vOrigin.z); + NWNX_PushArgumentFloat(vOrigin.y); + NWNX_PushArgumentFloat(vOrigin.x); + NWNX_PushArgumentInt(nObjectFilter); + NWNX_PushArgumentInt(bLineOfSight); + NWNX_PushArgumentFloat(vPosition.z); + NWNX_PushArgumentFloat(vPosition.y); + NWNX_PushArgumentFloat(vPosition.x); + NWNX_PushArgumentObject(oArea); + NWNX_PushArgumentFloat(fSize); + NWNX_PushArgumentInt(nShape); + NWNX_CallFunction(NWNX_Snapshot, "CreateAreaShapeSnapshot"); + + return NWNX_GetReturnValueInt(); +} + +int NWNX_Snapshot_CreateAreaSnapshot(object oArea, int nObjectFilter = OBJECT_TYPE_CREATURE, int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_PushArgumentInt(nObjectFilter); + NWNX_PushArgumentObject(oArea); + NWNX_CallFunction(NWNX_Snapshot, "CreateAreaSnapshot"); + + return NWNX_GetReturnValueInt(); +} + +int NWNX_Snapshot_CreateModuleSnapshot(int nObjectFilter = OBJECT_TYPE_CREATURE, int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_PushArgumentInt(nObjectFilter); + NWNX_CallFunction(NWNX_Snapshot, "CreateModuleSnapshot"); + + return NWNX_GetReturnValueInt(); +} + +int NWNX_Snapshot_CreatePlayerSnapshot(int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_CallFunction(NWNX_Snapshot, "CreatePlayerSnapshot"); + + return NWNX_GetReturnValueInt(); +} + +object NWNX_Snapshot_GetObjectFromSnapshot(int nIndex, int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_PushArgumentInt(nIndex); + NWNX_CallFunction(NWNX_Snapshot, "GetObjectFromSnapshot"); + + return NWNX_GetReturnValueObject(); +} + +int NWNX_Snapshot_PruneSnapshot(int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_CallFunction(NWNX_Snapshot, "PruneSnapshot"); + + return NWNX_GetReturnValueInt(); +} + +void NWNX_Snapshot_ClearSnapshot(int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_CallFunction(NWNX_Snapshot, "ClearSnapshot"); +} + +void NWNX_Snapshot_SortSnapshotByDistance(object oTarget, int bAscending = TRUE, int nSnapshotID = 0) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_PushArgumentInt(bAscending); + NWNX_PushArgumentObject(oTarget); + NWNX_CallFunction(NWNX_Snapshot, "SortSnapshotByDistance"); +} + +void NWNX_Snapshot_AddFilter(int nSnapshotID, struct NWNX_Snapshot_Filter strFilter) +{ + NWNX_PushArgumentObject(strFilter.oParam2); + NWNX_PushArgumentObject(strFilter.oParam1); + NWNX_PushArgumentFloat(strFilter.fParam2); + NWNX_PushArgumentFloat(strFilter.fParam1); + NWNX_PushArgumentString(strFilter.sParam2); + NWNX_PushArgumentString(strFilter.sParam1); + NWNX_PushArgumentInt(strFilter.nParam2); + NWNX_PushArgumentInt(strFilter.nParam1); + NWNX_PushArgumentInt(strFilter.bInvert); + NWNX_PushArgumentInt(strFilter.nComparison); + NWNX_PushArgumentInt(strFilter.nSubtype); + NWNX_PushArgumentInt(strFilter.nType); + NWNX_PushArgumentInt(nSnapshotID); + NWNX_CallFunction(NWNX_Snapshot, "AddFilter"); +} + +void NWNX_Snapshot_ClearFilters(int nSnapshotID) +{ + NWNX_PushArgumentInt(nSnapshotID); + NWNX_CallFunction(NWNX_Snapshot, "ClearFilters"); +} diff --git a/Plugins/Snapshot/README.md b/Plugins/Snapshot/README.md new file mode 100644 index 00000000000..a7ae9282f85 --- /dev/null +++ b/Plugins/Snapshot/README.md @@ -0,0 +1,4 @@ +@page snapshot Readme +@ingroup snapshot + +Functions to create cool object snapshots with filters! diff --git a/Plugins/Snapshot/Snapshot.cpp b/Plugins/Snapshot/Snapshot.cpp new file mode 100644 index 00000000000..59272a04e5c --- /dev/null +++ b/Plugins/Snapshot/Snapshot.cpp @@ -0,0 +1,941 @@ +#include "nwnx.hpp" + +#include "API/CNWSObject.hpp" +#include "API/CNWSArea.hpp" +#include "API/CNWSCreature.hpp" +#include "API/CNWSCreatureStats.hpp" +#include "API/CNWSCreatureAppearanceInfo.hpp" +#include "API/CNWSPlaceable.hpp" +#include "API/CVirtualMachine.hpp" +#include "API/CAppManager.hpp" +#include "API/CServerExoApp.hpp" +#include "API/CGameObjectArray.hpp" +#include "API/CNWVisibilityNode.hpp" + +#include +#include +#include +#include + +using namespace NWNXLib; +using namespace NWNXLib::API; +using namespace NWNXLib::VectorMath; + +enum ComparisonType +{ + Equals = 0, + NotEquals = 1, + GreaterThan = 2, + LesserThan = 3, + GreaterThanOrEquals = 4, + LesserThanOrEquals = 5, +}; + +enum FilterType +{ + Tag = 1, + LocalVariable = 2, + CreatureStats = 3, + Location = 4, + Appearance = 5, + Effect = 6, + Custom = 7, + Reputation = 8, + MAX, +}; + +enum FilterSubtype +{ + Default = 0, + + String_Regex = 1, + + LocalVar_Int = 1, + LocalVar_Float = 2, + LocalVar_Object = 3, + LocalVar_String = 4, + + Creature_Ability = 1, + Creature_AbilityMod = 2, + Creature_HasClass = 3, + Creature_HasClassOfLevel = 4, + Creature_IsPlayerCharacter = 5, + Creature_RacialType = 6, + Creature_Level = 7, + Creature_HasFeat = 8, + Creature_Experience = 9, + Creature_Gold = 10, + Creature_Poisoned = 11, + Creature_Diseased = 12, + Creature_SoundSet = 13, + Creature_Perception = 14, + + Location_Area = 1, + Location_X = 2, + Location_Y = 3, + Location_Z = 4, + Location_Orientation = 5, + + Appearance_Creature = 1, + Appearance_Placeable = 2, + + Effect_HasSpellEffect = 1, + Effect_HasFeatEffect = 2, + + Custom_RunConditionalScript = 1, + + Reputation_GetReputation = 1, + Reputation_IsFriend = 2, + Reputation_IsNeutral= 3, + Reputation_IsEnemy = 4, +}; + +enum FilterResult +{ + InvalidObjectTypeForFilter = -2, + InvalidFilter = -1, + False = 0, + True = 1, +}; + +struct SnapshotFilter +{ + FilterType type; + FilterSubtype subtype; + ComparisonType comparisonType; + bool invert; + + int32_t nParam1; + int32_t nParam2; + std::string sParam1; + std::string sParam2; + float fParam1; + float fParam2; + ObjectID oidParam1; + ObjectID oidParam2; +}; + +static std::unordered_map> s_SnapshotLists; +static std::unordered_map> s_FilterLists; + +static bool GetObjectIsInFilter(const std::vector &filters, CNWSObject *pObject) +{ + auto CompareString = [](const std::string& toCheck, const std::string &value, const ComparisonType &comparisonType) -> int32_t + { + switch (comparisonType) + { + case Equals: return toCheck == value; + case NotEquals: return toCheck != value; + default: return FilterResult::InvalidFilter; + } + }; + + auto CompareValue = [](const auto &toCheck, const auto &value, ComparisonType comparisonType) -> int32_t + { + switch (comparisonType) + { + case Equals: return toCheck == value; + case NotEquals: return toCheck != value; + case GreaterThan: return toCheck > value; + case LesserThan: return toCheck < value; + case GreaterThanOrEquals: return toCheck >= value; + case LesserThanOrEquals: return toCheck <= value; + default: return FilterResult::InvalidFilter; + } + }; + + for (const auto& filter : filters) + { + int32_t retVal = FilterResult::InvalidFilter; + + switch (filter.type) + { + case FilterType::Tag: + { + if (filter.subtype == Default) + retVal = CompareString(pObject->m_sTag.CStr(), filter.sParam1, filter.comparisonType); + else if (filter.subtype == String_Regex) + retVal = std::regex_match(pObject->m_sTag.CStr(), std::regex(filter.sParam1)); + } + break; + + case FilterType::LocalVariable: + { + if (auto *pVarTable = Utils::GetScriptVarTable(pObject)) + { + CExoString varName = filter.sParam1; + + switch (filter.subtype) + { + case FilterSubtype::LocalVar_Int: + retVal = CompareValue(pVarTable->GetInt(varName), filter.nParam1, filter.comparisonType); + break; + case FilterSubtype::LocalVar_Float: + retVal = CompareValue(pVarTable->GetFloat(varName), filter.fParam1, filter.comparisonType); + break; + case FilterSubtype::LocalVar_Object: + retVal = CompareValue(pVarTable->GetObject(varName), filter.oidParam1, filter.comparisonType); + break; + case FilterSubtype::LocalVar_String: + retVal = CompareString(pVarTable->GetString(varName).CStr(), filter.sParam2, filter.comparisonType); + break; + + default: break; + } + } + else + { + retVal = FilterResult::InvalidObjectTypeForFilter; + } + } + break; + + case FilterType::CreatureStats: + { + if (auto *pCreature = Utils::AsNWSCreature(pObject)) + { + switch (filter.subtype) + { + case FilterSubtype::Creature_Ability: + { + switch (filter.nParam1) + { + case Constants::Ability::Strength: + retVal = CompareValue(pCreature->m_pStats->GetSTRStat(), filter.nParam2, filter.comparisonType); + break; + case Constants::Ability::Dexterity: + retVal = CompareValue(pCreature->m_pStats->GetDEXStat(), filter.nParam2, filter.comparisonType); + break; + case Constants::Ability::Constitution: + retVal = CompareValue(pCreature->m_pStats->GetCONStat(), filter.nParam2, filter.comparisonType); + break; + case Constants::Ability::Intelligence: + retVal = CompareValue(pCreature->m_pStats->GetINTStat(), filter.nParam2, filter.comparisonType); + break; + case Constants::Ability::Wisdom: + retVal = CompareValue(pCreature->m_pStats->GetWISStat(), filter.nParam2, filter.comparisonType); + break; + case Constants::Ability::Charisma: + retVal = CompareValue(pCreature->m_pStats->GetCHAStat(), filter.nParam2, filter.comparisonType); + break; + } + } + break; + + case FilterSubtype::Creature_AbilityMod: + { + if (filter.nParam1 >= Constants::Ability::MIN && filter.nParam1 <= Constants::Ability::MAX) + retVal = CompareValue(pCreature->m_pStats->GetAbilityMod(filter.nParam1), filter.nParam2, filter.comparisonType); + } + break; + + case FilterSubtype::Creature_HasClass: + { + retVal = false; + for (uint8_t multiClass = 0; multiClass < 3; multiClass++) + { + if (pCreature->m_pStats->GetClass(multiClass) == filter.nParam1) + { + retVal = true; + break; + } + } + } + break; + + case FilterSubtype::Creature_HasClassOfLevel: + { + retVal = false; + for (uint8_t multiClass = 0; multiClass < 3; multiClass++) + { + if (pCreature->m_pStats->GetClass(multiClass) == filter.nParam1) + { + retVal = CompareValue(pCreature->m_pStats->GetClassLevel(multiClass), filter.nParam2, filter.comparisonType); + break; + } + } + } + break; + + case FilterSubtype::Creature_IsPlayerCharacter: + retVal = pCreature->m_pStats->m_bIsPC; + break; + + case FilterSubtype::Creature_RacialType: + retVal = CompareValue(pCreature->m_pStats->m_nRace, filter.nParam1, filter.comparisonType); + break; + + case FilterSubtype::Creature_Level: + retVal = CompareValue(pCreature->m_pStats->GetLevel(), filter.nParam1, filter.comparisonType); + break; + + case FilterSubtype::Creature_HasFeat: + retVal = pCreature->m_pStats->HasFeat(filter.nParam1); + break; + + case FilterSubtype::Creature_Experience: + retVal = CompareValue((int32_t)pCreature->m_pStats->m_nExperience, filter.nParam1, filter.comparisonType); + break; + + case FilterSubtype::Creature_Gold: + retVal = CompareValue((int32_t)pCreature->m_nGold, filter.nParam1, filter.comparisonType); + break; + + case FilterSubtype::Creature_Poisoned: + retVal = pCreature->m_bPoisoned; + break; + + case FilterSubtype::Creature_Diseased: + retVal = pCreature->m_bDiseased; + break; + + case FilterSubtype::Creature_SoundSet: + retVal = CompareValue((int32_t)pCreature->m_nSoundSet, filter.nParam1, filter.comparisonType); + break; + + case FilterSubtype::Creature_Perception: + { + uint8_t nSeen = 2, nHeard = 2; + bool bHeard = false, bSeen = false; + + switch (filter.nParam1) + { + case 0: nSeen = nHeard = 1; break; // PERCEPTION_SEEN_AND_HEARD + case 1: nSeen = nHeard = 0; break; // PERCEPTION_NOT_SEEN_AND_NOT_HEARD + case 2: nSeen = 0; nHeard = 1; break; // PERCEPTION_HEARD_AND_NOT_SEEN + case 3: nSeen = 1; nHeard = 0; break; // PERCEPTION_SEEN_AND_NOT_HEARD + case 4: nHeard = 0; break; // PERCEPTION_NOT_HEARD + case 5: nHeard = 1; break; // PERCEPTION_HEARD + case 6: nSeen = 0; break; // PERCEPTION_NOT_SEEN + case 7: nSeen = 1; break; // PERCEPTION_SEEN + } + + if (auto *pVisibilityNode = pCreature->GetVisibleListElement(filter.oidParam1)) + { + bSeen = pVisibilityNode->m_bSeen; + bHeard = pVisibilityNode->m_bHeard; + } + + retVal = ((nSeen == 2 || bSeen == nSeen)) && ((nHeard == 2 || bHeard == nHeard)); + } + break; + + default: break; + } + } + else + { + retVal = FilterResult::InvalidObjectTypeForFilter; + } + } + break; + + case FilterType::Location: + { + switch (filter.subtype) + { + case FilterSubtype::Location_Area: + retVal = pObject->m_oidArea == filter.oidParam1; + break; + + case FilterSubtype::Location_X: + retVal = CompareValue(pObject->m_vPosition.x, filter.fParam1, filter.comparisonType); + break; + + case FilterSubtype::Location_Y: + retVal = CompareValue(pObject->m_vPosition.y, filter.fParam1, filter.comparisonType); + break; + + case FilterSubtype::Location_Z: + retVal = CompareValue(pObject->m_vPosition.z, filter.fParam1, filter.comparisonType); + break; + + case FilterSubtype::Location_Orientation: + { + Vector vOrientation = pObject->m_vOrientation; + vOrientation.z = 0.0f; + Vector vNormal = Normalize(vOrientation); + float fAngle = std::atan2(vOrientation.y, vOrientation.x) * (180.0f / M_PI); + + if(vNormal.y < 0.0f) + fAngle = 360.0f + fAngle; + + fAngle = (float)(int32_t)(fAngle + 0.5f); + + retVal = CompareValue(fAngle, filter.fParam1, filter.comparisonType); + } + break; + + default: break; + } + } + break; + + case FilterType::Appearance: + { + switch (filter.subtype) + { + case FilterSubtype::Appearance_Creature: + { + if (auto *pCreature = Utils::AsNWSCreature(pObject)) + retVal = CompareValue(pCreature->m_cAppearance.m_nAppearanceType, filter.nParam1, filter.comparisonType); + else + retVal = FilterResult::InvalidObjectTypeForFilter; + } + break; + + case FilterSubtype::Appearance_Placeable: + { + if (auto *pPlaceable = Utils::AsNWSPlaceable(pObject)) + retVal = CompareValue(pPlaceable->m_nAppearance, filter.nParam1, filter.comparisonType); + else + retVal = FilterResult::InvalidObjectTypeForFilter; + } + break; + + default: break; + } + } + break; + + case FilterType::Effect: + { + switch (filter.subtype) + { + case FilterSubtype::Effect_HasSpellEffect: + retVal = pObject->HasSpellEffectApplied(filter.nParam1); + break; + + case FilterSubtype::Effect_HasFeatEffect: + retVal = pObject->GetHasFeatEffectApplied(filter.nParam1); + break; + + default: break; + } + } + break; + + case FilterType::Custom: + { + switch (filter.subtype) + { + case Custom_RunConditionalScript: + { + retVal = false; + CExoString sConditionalScript = filter.sParam1; + + if (Globals::VirtualMachine()->RunScript(&sConditionalScript, pObject->m_idSelf, true, 0)) + { + int32_t nParameterType; + void* pParameter; + + if (Globals::VirtualMachine()->GetRunScriptReturnValue(&nParameterType, &pParameter) && nParameterType == 3) + retVal = (intptr_t)pParameter != 0 ? true : false; + } + } + break; + + default: break; + } + } + break; + + case FilterType::Reputation: + { + int32_t bIsInParty; + switch (filter.subtype) + { + case FilterSubtype::Reputation_GetReputation: + retVal = CompareValue(pObject->GetReputation(filter.oidParam1, bIsInParty), filter.nParam1, filter.comparisonType); + break; + + case FilterSubtype::Reputation_IsFriend: + retVal = pObject->GetReputation(filter.oidParam1, bIsInParty) >= 90; + break; + + case FilterSubtype::Reputation_IsNeutral: + { + int32_t nReputation = pObject->GetReputation(filter.oidParam1, bIsInParty); + retVal = nReputation > 10 && nReputation < 90; + } + break; + + case FilterSubtype::Reputation_IsEnemy: + retVal = pObject->GetReputation(filter.oidParam1, bIsInParty) <= 10; + break; + + default: break; + } + } + break; + + default: break; + } + + if (retVal == FilterResult::InvalidObjectTypeForFilter) + return false; + else if (retVal != FilterResult::InvalidFilter) + { + if (filter.invert) + retVal = !retVal; + + if (!retVal) + return false; + } + } + + return true; +} + +NWNX_EXPORT ArgumentStack CreateAreaShapeSnapshot(ArgumentStack &&args) +{ + const auto shape = args.extract(); + const auto size = args.extract(); + auto *pArea = Utils::PopArea(args); + const auto x = args.extract(); + const auto y = args.extract(); + const auto z = args.extract(); + const auto lineOfSight = !!args.extract(); + const auto objectFilter = args.extract(); + const auto originX = args.extract(); + const auto originY = args.extract(); + const auto originZ = args.extract(); + const auto snapshotId = args.extract(); + + s_SnapshotLists.erase(snapshotId); + + if (!pArea) + return 0; + + const ObjectID oidObjectRunScript = Globals::VirtualMachine()->m_oidObjectRunScript[Globals::VirtualMachine()->m_nRecursionLevel]; + const float sizeSquared = size * size; + Vector target = {x, y, z}; + Vector origin = {originX, originY, originZ}; + Vector losOrigin{}; + float minX, maxX; + + switch (shape) + { + case 0:// SHAPE_SPELLCYLINDER + { + Vector dir = Normalize(Subtract(target, origin)); + + if (Dot(dir, dir) <= 0.00000001f) + dir = Vector(0.0f, 1.0f, 0.0f); + + target = Add(origin, Multiply(dir, size)); + float f = dir.x; + dir.x = -dir.y; + dir.y = f; + dir = Multiply(dir, 1.5f); + + Vector v[4]{}; + v[0] = Add(origin, dir); + v[1] = Subtract(origin, dir); + v[2] = Add(target, dir); + v[3] = Subtract(target, dir); + + minX = v[0].x; + maxX = v[0].x; + + for (int i = 1; i < 4; i++) + { + if (v[i].x < minX) + minX = v[i].x; + if (v[i].x > maxX) + maxX = v[i].x; + } + + losOrigin = origin; + break; + } + + case 1:// SHAPE_CONE + case 3:// SHAPE_SPELLCONE + { + if (shape == 3) + { + if (auto *pObject = Utils::AsNWSObject(Utils::GetGameObject(oidObjectRunScript))) + origin = pObject->m_vPosition; + } + + minX = ((target.x < origin.x) ? target.x : origin.x) - size; + maxX = ((target.x > origin.x) ? target.x : origin.x) + size; + losOrigin = origin; + break; + } + + case 2:// SHAPE_CUBE + default:// SHAPE_SPHERE + { + minX = target.x - size; + maxX = target.x + size; + losOrigin = target; + break; + } + } + + losOrigin.z += 1.0f; + + int32_t index; + if (!pArea->GetFirstObjectIndiceByX(&index, minX)) + return 0; + + auto filtersList = s_FilterLists.find(snapshotId); + bool hasFilters = filtersList != s_FilterLists.end() && !filtersList->second.empty(); + + s_SnapshotLists[snapshotId].reserve(50); + + while (index < pArea->m_aGameObjects.num) + { + if (auto *pObject = Utils::AsNWSObject(Utils::GetGameObject(pArea->m_aGameObjects[index]))) + { + bool bObjectIsInShape = false; + Vector objectPosition = pObject->m_vPosition; + + if (objectPosition.x > maxX) + break; + + auto IsAllowedObject = [&](int32_t nwscriptConstant, Constants::ObjectType::TYPE engineConstant) -> bool + { + return (objectFilter & nwscriptConstant) == nwscriptConstant && pObject->m_nObjectType == engineConstant; + }; + + if (((objectFilter & 32767) == 32767) || + (IsAllowedObject(1, Constants::ObjectType::Creature) && !Utils::AsNWSCreature(pObject)->m_pStats->GetIsDM()) || + IsAllowedObject(2, Constants::ObjectType::Item) || + IsAllowedObject(4, Constants::ObjectType::Trigger) || + IsAllowedObject(8, Constants::ObjectType::Door) || + IsAllowedObject(16, Constants::ObjectType::AreaOfEffect) || + IsAllowedObject(32, Constants::ObjectType::Waypoint) || + IsAllowedObject(64, Constants::ObjectType::Placeable) || + IsAllowedObject(128, Constants::ObjectType::Store) || + IsAllowedObject(256, Constants::ObjectType::Encounter) ) + { + switch (shape) + { + case 0:// SHAPE_SPELLCYLINDER + { + if (MagnitudeSquared(Subtract(objectPosition, origin)) <= sizeSquared && + Dot(Normalize(Subtract(target, origin)), Normalize(Subtract(objectPosition, origin))) >= 0.0f && + MagnitudeSquared(Subtract(objectPosition, Lineproject(origin, target, objectPosition))) <= 2.25f) + { + bObjectIsInShape = true; + } + break; + } + + case 1:// SHAPE_CONE + { + auto sqr = [](float f) -> float { return f * f; }; + Vector closestPoint = Lineproject(origin, target, objectPosition); + if (Dot(Normalize(Subtract(target, origin)), Normalize(Subtract(objectPosition, origin))) > 0.0f && + MagnitudeSquared(Subtract(objectPosition, closestPoint)) <= + sqr((size / Magnitude(Subtract(target, origin))) * Magnitude(closestPoint))) + { + bObjectIsInShape = true; + } + break; + } + + case 2:// SHAPE_CUBE + { + if ((objectPosition.x <= target.x + size) && (objectPosition.x >= target.x - size) && + (objectPosition.y <= target.y + size) && (objectPosition.y >= target.y - size) && + (objectPosition.z <= target.z + size) && (objectPosition.z >= target.z - size)) + { + bObjectIsInShape = true; + } + break; + } + + case 3:// SHAPE_SPELLCONE + { + const float f = MagnitudeSquared(Subtract(Lineproject(origin, target, objectPosition), origin)); + if (Dot(Normalize(Subtract(target, origin)), Normalize(Subtract(objectPosition, origin))) >= 0.866f && + f <= sizeSquared && f >= 0.01f) + { + bObjectIsInShape = true; + } + break; + } + + default:// SHAPE_SPHERE + { + Vector distance = Subtract(objectPosition, target); + if (Dot(distance, distance) <= sizeSquared) + { + bObjectIsInShape = true; + } + break; + } + } + + if (bObjectIsInShape && lineOfSight) + { + Vector impact{}; + ObjectID oidImpact; + objectPosition.z += 1.0f; + + if (!pArea->ClearLineOfSight(losOrigin, objectPosition, &impact, &oidImpact, oidObjectRunScript, pObject->m_idSelf, true)) + bObjectIsInShape = false; + } + + if (bObjectIsInShape && hasFilters) + bObjectIsInShape = GetObjectIsInFilter(filtersList->second, pObject); + + if (bObjectIsInShape) + s_SnapshotLists[snapshotId].emplace_back(pObject->m_idSelf); + } + } + + index++; + } + + if (snapshotId == 0) + s_FilterLists.erase(snapshotId); + + return (int32_t)s_SnapshotLists[snapshotId].size(); +} + +NWNX_EXPORT ArgumentStack CreateAreaSnapshot(ArgumentStack &&args) +{ + auto *pArea = Utils::PopArea(args); + const auto objectFilter = args.extract(); + const auto snapshotId = args.extract(); + + s_SnapshotLists.erase(snapshotId); + + if (!pArea || !pArea->m_aGameObjects.num) + return 0; + + auto filtersList = s_FilterLists.find(snapshotId); + bool hasFilters = filtersList != s_FilterLists.end() && !filtersList->second.empty(); + s_SnapshotLists[snapshotId].reserve(100); + + int32_t index = 0; + while (index < pArea->m_aGameObjects.num) + { + if (auto *pObject = Utils::AsNWSObject(Utils::GetGameObject(pArea->m_aGameObjects[index]))) + { + auto IsAllowedObject = [&](int32_t nwscriptConstant, Constants::ObjectType::TYPE engineConstant) -> bool + { + return (objectFilter & nwscriptConstant) == nwscriptConstant && pObject->m_nObjectType == engineConstant; + }; + + if (((objectFilter & 32767) == 32767) || + (IsAllowedObject(1, Constants::ObjectType::Creature) && !Utils::AsNWSCreature(pObject)->m_pStats->GetIsDM()) || + IsAllowedObject(2, Constants::ObjectType::Item) || + IsAllowedObject(4, Constants::ObjectType::Trigger) || + IsAllowedObject(8, Constants::ObjectType::Door) || + IsAllowedObject(16, Constants::ObjectType::AreaOfEffect) || + IsAllowedObject(32, Constants::ObjectType::Waypoint) || + IsAllowedObject(64, Constants::ObjectType::Placeable) || + IsAllowedObject(128, Constants::ObjectType::Store) || + IsAllowedObject(256, Constants::ObjectType::Encounter) ) + { + if (!hasFilters || (hasFilters && GetObjectIsInFilter(filtersList->second, pObject))) + s_SnapshotLists[snapshotId].emplace_back(pObject->m_idSelf); + } + } + + index++; + } + + if (snapshotId == 0) + s_FilterLists.erase(snapshotId); + + return (int32_t)s_SnapshotLists[snapshotId].size(); +} + +NWNX_EXPORT ArgumentStack CreateModuleSnapshot(ArgumentStack &&args) +{ + const auto objectFilter = args.extract(); + const auto snapshotId = args.extract(); + + s_SnapshotLists.erase(snapshotId); + + auto *pGameObjectArray = Globals::AppManager()->m_pServerExoApp->GetObjectArray(); + + if (!pGameObjectArray) + return 0; + + auto filtersList = s_FilterLists.find(snapshotId); + bool hasFilters = filtersList != s_FilterLists.end() && !filtersList->second.empty(); + s_SnapshotLists[snapshotId].reserve(250); + + ObjectID oidObject = pGameObjectArray->m_nNextObjectArrayID[0] - 1; + while (oidObject > 0) + { + if (auto *pObject = Utils::AsNWSObject(Utils::GetGameObject(oidObject))) + { + auto IsAllowedObject = [&](int32_t nwscriptConstant, Constants::ObjectType::TYPE engineConstant) -> bool + { + return (objectFilter & nwscriptConstant) == nwscriptConstant && pObject->m_nObjectType == engineConstant; + }; + + if (((objectFilter & 32767) == 32767) || + (IsAllowedObject(1, Constants::ObjectType::Creature) && !Utils::AsNWSCreature(pObject)->m_pStats->GetIsDM()) || + IsAllowedObject(2, Constants::ObjectType::Item) || + IsAllowedObject(4, Constants::ObjectType::Trigger) || + IsAllowedObject(8, Constants::ObjectType::Door) || + IsAllowedObject(16, Constants::ObjectType::AreaOfEffect) || + IsAllowedObject(32, Constants::ObjectType::Waypoint) || + IsAllowedObject(64, Constants::ObjectType::Placeable) || + IsAllowedObject(128, Constants::ObjectType::Store) || + IsAllowedObject(256, Constants::ObjectType::Encounter) ) + { + if (!hasFilters || (hasFilters && GetObjectIsInFilter(filtersList->second, pObject))) + s_SnapshotLists[snapshotId].emplace_back(pObject->m_idSelf); + } + } + + oidObject--; + } + + if (snapshotId == 0) + s_FilterLists.erase(snapshotId); + + return (int32_t)s_SnapshotLists[snapshotId].size(); +} + +NWNX_EXPORT ArgumentStack CreatePlayerSnapshot(ArgumentStack &&args) +{ + const auto snapshotId = args.extract(); + + auto filtersList = s_FilterLists.find(snapshotId); + bool hasFilters = filtersList != s_FilterLists.end() && !filtersList->second.empty(); + s_SnapshotLists.erase(snapshotId); + s_SnapshotLists[snapshotId].reserve(25); + + ObjectID oidObject = Globals::AppManager()->m_pServerExoApp->GetFirstPCObject(); + while (oidObject != Constants::OBJECT_INVALID) + { + if (auto *pObject = Utils::AsNWSObject(Utils::GetGameObject(oidObject))) + { + if (!hasFilters || (hasFilters && GetObjectIsInFilter(filtersList->second, pObject))) + s_SnapshotLists[snapshotId].emplace_back(pObject->m_idSelf); + } + + oidObject = Globals::AppManager()->m_pServerExoApp->GetNextPCObject(); + } + + if (snapshotId == 0) + s_FilterLists.erase(snapshotId); + + return (int32_t)s_SnapshotLists[snapshotId].size(); +} + +NWNX_EXPORT ArgumentStack GetObjectFromSnapshot(ArgumentStack &&args) +{ + const auto index = args.extract(); + ASSERT_OR_THROW(index >= 0); + const auto snapshotID = args.extract(); + + auto snapshotList = s_SnapshotLists.find(snapshotID); + if (snapshotList != s_SnapshotLists.end()) + { + if ((size_t)index < snapshotList->second.size()) + return snapshotList->second[index]; + } + + return Constants::OBJECT_INVALID; +} + +NWNX_EXPORT ArgumentStack PruneSnapshot(ArgumentStack &&args) +{ + const auto snapshotID = args.extract(); + + auto snapshotList = s_SnapshotLists.find(snapshotID); + if (snapshotList != s_SnapshotLists.end()) + { + snapshotList->second.erase(std::remove_if(snapshotList->second.begin(), snapshotList->second.end(), + [](const ObjectID& oid) { return !Utils::GetGameObject(oid); }), snapshotList->second.end()); + + if (snapshotList->second.empty()) + { + s_SnapshotLists.erase(snapshotID); + return 0; + } + + return (int32_t)snapshotList->second.size(); + } + + return 0; +} + +NWNX_EXPORT ArgumentStack ClearSnapshot(ArgumentStack &&args) +{ + s_SnapshotLists.erase(args.extract()); + return {}; +} + +NWNX_EXPORT ArgumentStack SortSnapshotByDistance(ArgumentStack &&args) +{ + const auto *pTarget = Utils::PopObject(args); + const auto snapshotID = args.extract(); + const auto ascending = !!args.extract(); + + auto snapshotList = s_SnapshotLists.find(snapshotID); + if (pTarget && snapshotList != s_SnapshotLists.end() && snapshotList->second.size() > 1) + { + Vector vTarget = pTarget->m_vPosition; + ObjectID oidTargetArea = pTarget->m_oidArea; + + std::sort(snapshotList->second.begin(), snapshotList->second.end(), + [vTarget, oidTargetArea, ascending](const ObjectID& oidA, const ObjectID& oidB) + { + float fDistanceA = 99999.0f; + float fDistanceB = 99999.0f; + + if (auto *pObjectA = Utils::AsNWSObject(Utils::GetGameObject(oidA))) + { + if (pObjectA->m_oidArea == oidTargetArea) + fDistanceA = MagnitudeSquared(Subtract(vTarget, pObjectA->m_vPosition)); + } + + if (auto *pObjectB = Utils::AsNWSObject(Utils::GetGameObject(oidB))) + { + if (pObjectB->m_oidArea == oidTargetArea) + fDistanceB = MagnitudeSquared(Subtract(vTarget, pObjectB->m_vPosition)); + } + + return ascending ? fDistanceA < fDistanceB : fDistanceA > fDistanceB; + }); + } + + return {}; +} + +NWNX_EXPORT ArgumentStack AddFilter(ArgumentStack &&args) +{ + const auto snapshotID = args.extract(); + const auto type = args.extract(); + ASSERT_OR_THROW(type >= FilterType::Tag); + ASSERT_OR_THROW(type < FilterType::MAX); + const auto subtype = args.extract(); + const auto comparisonType = args.extract(); + ASSERT_OR_THROW(comparisonType >= ComparisonType::Equals); + ASSERT_OR_THROW(comparisonType <= ComparisonType::LesserThanOrEquals); + const auto invert = !!args.extract(); + const auto nParam1 = args.extract(); + const auto nParam2 = args.extract(); + const auto sParam1 = args.extract(); + const auto sParam2 = args.extract(); + const auto fParam1 = args.extract(); + const auto fParam2 = args.extract(); + const auto oidParam1 = args.extract(); + const auto oidParam2 = args.extract(); + + SnapshotFilter str = {(FilterType)type, (FilterSubtype)subtype, (ComparisonType)comparisonType, invert, + nParam1, nParam2, sParam1, sParam2, fParam1, fParam2, oidParam1, oidParam2}; + s_FilterLists[snapshotID].emplace_back(str); + + return {}; +} + +NWNX_EXPORT ArgumentStack ClearFilters(ArgumentStack &&args) +{ + s_FilterLists.erase(args.extract()); + return {}; +}