diff --git a/NWNXLib/API/API/CNWSMessage.hpp b/NWNXLib/API/API/CNWSMessage.hpp index 76d1324aa39..c01f2a50a94 100644 --- a/NWNXLib/API/API/CNWSMessage.hpp +++ b/NWNXLib/API/API/CNWSMessage.hpp @@ -56,6 +56,7 @@ struct CNWSMessage : CNWMessage CNWSMessage(); ~CNWSMessage(); + static BOOL GetLocStringServer(uint32_t dwPlayerID, CExoLocString sFirstLoc, CExoLocString sLastLoc, CExoString& sOut, float& fSoundLength, uint8_t nGender = 0); OBJECT_ID ReadOBJECTIDServer(); void WriteCExoLocStringServer(const CExoLocString & sLocString, uint8_t nGender = 0); void WriteOBJECTIDServer(OBJECT_ID oidObjectId); diff --git a/NWNXLib/Utils.cpp b/NWNXLib/Utils.cpp index e0de16786ce..21dc226b5c5 100644 --- a/NWNXLib/Utils.cpp +++ b/NWNXLib/Utils.cpp @@ -548,11 +548,29 @@ int32_t NWScriptObjectTypeToEngineObjectType(int32_t nwscriptObjectType) } } +int32_t EngineObjectTypeToNWScriptObjectType(int32_t engineObjectType) +{ + switch (engineObjectType) + { + case Constants::ObjectType::Creature: return 1; + case Constants::ObjectType::Item: return 2; + case Constants::ObjectType::Trigger: return 4; + case Constants::ObjectType::Door: return 8; + case Constants::ObjectType::AreaOfEffect: return 16; + case Constants::ObjectType::Waypoint: return 32; + case Constants::ObjectType::Placeable: return 64; + case Constants::ObjectType::Store: return 128; + case Constants::ObjectType::Encounter: return 256; + case Constants::ObjectType::Sound: return 32767; // :( + default: return 0; + } +} + void UpdateClientObjectForPlayer(ObjectID oidObject, CNWSPlayer* pPlayer) { for (auto* pLuo : pPlayer->m_lstActiveObjectsLastUpdate) { - if (pLuo->m_nId == oidObject) + if (pLuo->m_nId == oidObject) { pPlayer->m_lstActiveObjectsLastUpdate.Remove(pLuo); delete pLuo; @@ -571,4 +589,23 @@ void UpdateClientObject(ObjectID oidObject) } } +CItemRepository* GetItemRepository(ObjectID oidTarget) +{ + auto *pTarget = Utils::GetGameObject(oidTarget); + if (!pTarget) + return nullptr; + + switch (pTarget->m_nObjectType) + { + case Constants::ObjectType::Creature: + return Utils::AsNWSCreature(pTarget)->m_pcItemRepository; + case Constants::ObjectType::Placeable: + return Utils::AsNWSPlaceable(pTarget)->m_pcItemRepository; + case Constants::ObjectType::Item: + return Utils::AsNWSItem(pTarget)->m_pItemRepository; + } + + return nullptr; +} + } diff --git a/NWNXLib/nwnx.hpp b/NWNXLib/nwnx.hpp index 4f34b173ab0..8704e49ca56 100644 --- a/NWNXLib/nwnx.hpp +++ b/NWNXLib/nwnx.hpp @@ -253,8 +253,10 @@ namespace Utils CNWSDoor* PopDoor(ArgumentStack& args, bool throwOnFail=false); int32_t NWScriptObjectTypeToEngineObjectType(int32_t nwscriptObjectType); + int32_t EngineObjectTypeToNWScriptObjectType(int32_t engineObjectType); void UpdateClientObject(ObjectID oidObject); void UpdateClientObjectForPlayer(ObjectID oidObject, CNWSPlayer* oidPlayer); + CItemRepository* GetItemRepository(ObjectID oidTarget); } namespace POS diff --git a/Plugins/Item/Item.cpp b/Plugins/Item/Item.cpp index 838fa1cca1a..c1da144c6ca 100644 --- a/Plugins/Item/Item.cpp +++ b/Plugins/Item/Item.cpp @@ -275,24 +275,6 @@ NWNX_EXPORT ArgumentStack GetMinEquipLevel(ArgumentStack&& args) return -1; } -CItemRepository* GetObjectItemRepository(OBJECT_ID oidPossessor) -{ - auto pPossessor = Utils::GetGameObject(oidPossessor); - if (!pPossessor) return nullptr; - - switch (pPossessor->m_nObjectType) - { - case Constants::ObjectType::Creature: - return Utils::AsNWSCreature(pPossessor)->m_pcItemRepository; - case Constants::ObjectType::Placeable: - return Utils::AsNWSPlaceable(pPossessor)->m_pcItemRepository; - case Constants::ObjectType::Item: - return Utils::AsNWSItem(pPossessor)->m_pItemRepository; - } - - return nullptr; -} - NWNX_EXPORT ArgumentStack MoveTo(ArgumentStack&& args) { if (auto *pItem = Utils::PopItem(args)) @@ -312,7 +294,7 @@ NWNX_EXPORT ArgumentStack MoveTo(ArgumentStack&& args) // Is the item already on/in the target? if (oidRealItemPossessor == oidRealTargetPossessor) { - auto pTargetItemRepo = GetObjectItemRepository(pTarget->m_idSelf); + auto pTargetItemRepo = Utils::GetItemRepository(pTarget->m_idSelf); if ((pTargetItemRepo) && (pTargetItemRepo->GetItemInRepository(pItem))) { LOG_DEBUG("NWNX_Item_MoveTo: Item is already on the target!"); @@ -360,7 +342,7 @@ NWNX_EXPORT ArgumentStack MoveTo(ArgumentStack&& args) case Constants::ObjectType::Store: { auto pTargetStore = Utils::AsNWSStore(pTarget); - auto pOriginalOwnerRepository = GetObjectItemRepository(pItem->m_oidPossessor); + auto pOriginalOwnerRepository = Utils::GetItemRepository(pItem->m_oidPossessor); pTargetStore->AcquireItem(pItem, false, 0xFF, 0xFF); diff --git a/Plugins/NWSQLiteExtensions/AreaObjectsVirtualTable.cpp b/Plugins/NWSQLiteExtensions/AreaObjectsVirtualTable.cpp new file mode 100644 index 00000000000..19907cffab1 --- /dev/null +++ b/Plugins/NWSQLiteExtensions/AreaObjectsVirtualTable.cpp @@ -0,0 +1,357 @@ +#include "nwnx.hpp" +#include "API/Database.hpp" +#include "API/CNWSArea.hpp" +#include "API/CNWSObject.hpp" +#include "API/CNWSPlaceable.hpp" + + +using namespace NWNXLib; +using namespace NWNXLib::API; + +static bool s_AreaObjectsVirtualTableEnabled = false; +static int SetupAreaObjectsVirtualTableModule(sqlite3 *db); + +void AreaObjectsVirtualTable() __attribute__((constructor)); +void AreaObjectsVirtualTable() +{ + if ((s_AreaObjectsVirtualTableEnabled = Config::Get("ENABLE_INVENTORY_VIRTUAL_TABLE_MODULE", false))) + { + LOG_INFO("Enabling the AreaObjects Virtual Table Module for the module database."); + static Hooks::Hook s_DatabaseSetupHook = Hooks::HookFunction(&NWSQLite::Database::Setup, + +[](NWSQLite::Database *pThis) -> void + { + s_DatabaseSetupHook->CallOriginal(pThis); + + if (strcmp(pThis->m_label.c_str(), "Module()") == 0) + { + if (SetupAreaObjectsVirtualTableModule(pThis->connection().get()) != SQLITE_OK) + { + LOG_ERROR("Failed to setup the AreaObjects Virtual Table Module."); + s_AreaObjectsVirtualTableEnabled = false; + } + } + }, Hooks::Order::Early); + } +} + +typedef struct vAreaObjects_tab vAreaObjects_tab; +struct vAreaObjects_tab +{ + sqlite3_vtab base; +}; + +typedef struct vAreaObjects_cursor vAreaObjects_cursor; +struct vAreaObjects_cursor +{ + sqlite3_vtab_cursor base; + ObjectID oidArea; + int32_t currentObjectIndex; + int32_t objectFilter; + bool skipStaticPlaceables; +}; + +namespace AreaObjectsColumns +{ + enum TYPE + { + ObjectID = 0, + AreaObjectID, + ObjectFilter, + SkipStaticPlaceables, + Num, // Keep Last + }; + constexpr int32_t MAX = 3; + constexpr int32_t NUM = MAX + 1; + static_assert(MAX == SkipStaticPlaceables); + static_assert(NUM == Num); + + constexpr const char* ToColumnWithType(const unsigned value) + { + constexpr const char* TYPE_STRINGS[] = + { + "oid INTEGER", + "area_oid INTEGER HIDDEN", + "object_filter INTEGER HIDDEN", + "skip_static_placeables INTEGER HIDDEN", + }; + static_assert((sizeof(TYPE_STRINGS) / sizeof(TYPE_STRINGS[0])) == NUM); + return (value > MAX) ? "(invalid)" : TYPE_STRINGS[value]; + } + + static CExoString GetSchema() + { + CExoString sSchema = "CREATE TABLE x("; + for (int i = 0; i < NUM; i++) + { + sSchema.Format("%s%s%s", sSchema.CStr(), i ? ", " : "", ToColumnWithType(i)); + } + sSchema.Format("%s);", sSchema.CStr()); + return sSchema; + } +} + +static void vAreaObjectsSetErrmsg(vAreaObjects_cursor *pCur, const char *zFmt, ...) +{ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + +static CNWSObject* vAreaObjectsGetCurrentObject(ObjectID oidArea, int32_t nCurrentObjectIndex) +{ + if (auto *pArea = Utils::AsNWSArea(Utils::GetGameObject(oidArea))) + { + if (nCurrentObjectIndex < pArea->m_aGameObjects.num) + { + return Utils::AsNWSObject(Utils::GetGameObject(pArea->m_aGameObjects[nCurrentObjectIndex])); + } + } + + return nullptr; +} + +static int vAreaObjectsConnect(sqlite3 *db, void *pAux, int argc, const char* const *argv, sqlite3_vtab **ppVtab, char **pzErr) +{ + (void)pAux; (void)argc; (void)argv; (void)pzErr; + + vAreaObjects_tab *pNewVtab = nullptr; + int32_t ret = sqlite3_declare_vtab(db, AreaObjectsColumns::GetSchema().CStr()); + if (ret == SQLITE_OK) + { + pNewVtab = static_cast(sqlite3_malloc(sizeof(vAreaObjects_tab))); + if (!pNewVtab) + return SQLITE_NOMEM; + + memset(pNewVtab, 0, sizeof(vAreaObjects_tab)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + + *ppVtab = (sqlite3_vtab*)pNewVtab; + return ret; +} + +static int vAreaObjectsDisconnect(sqlite3_vtab *pVtab) +{ + auto *p2daVtab = (vAreaObjects_tab*)pVtab; + sqlite3_free(p2daVtab); + return SQLITE_OK; +} + +static int vAreaObjectsOpen(sqlite3_vtab*, sqlite3_vtab_cursor **ppCursor) +{ + auto *pCursor = static_cast(sqlite3_malloc(sizeof(vAreaObjects_cursor))); + if(!pCursor) return SQLITE_NOMEM; + memset(pCursor, 0, sizeof(vAreaObjects_cursor)); + pCursor->oidArea = Constants::OBJECT_INVALID; + *ppCursor = &pCursor->base; + return SQLITE_OK; +} + +static int vAreaObjectsClose(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vAreaObjects_cursor*)cur; + sqlite3_free(pCursor); + return SQLITE_OK; +} + +static int vAreaIncrementObjectIndexIfInvalid(vAreaObjects_cursor *pCursor) +{ + auto *pArea = Utils::AsNWSArea(Utils::GetGameObject(pCursor->oidArea)); + if (!pArea) + return SQLITE_ERROR; + + CGameObject *pGO; + while (pCursor->currentObjectIndex < pArea->m_aGameObjects.num && (pGO = Utils::GetGameObject(pArea->m_aGameObjects[pCursor->currentObjectIndex])) && + (!(pCursor->objectFilter & Utils::EngineObjectTypeToNWScriptObjectType(pGO->m_nObjectType)) || + (pCursor->skipStaticPlaceables && pGO->m_nObjectType == Constants::ObjectType::Placeable && Utils::AsNWSPlaceable(pGO)->m_bStaticObject))) + { + pCursor->currentObjectIndex++; + } + + return SQLITE_OK; +} + +static int vAreaObjectsNext(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vAreaObjects_cursor*)cur; + pCursor->currentObjectIndex++; + return vAreaIncrementObjectIndexIfInvalid(pCursor); +} + +static int vAreaObjectsColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int nColumn) +{ + auto *pCursor = (vAreaObjects_cursor*)cur; + + if (nColumn == AreaObjectsColumns::AreaObjectID || + nColumn == AreaObjectsColumns::ObjectFilter || + nColumn == AreaObjectsColumns::SkipStaticPlaceables) + return SQLITE_OK; + + if (auto *pObject = vAreaObjectsGetCurrentObject(pCursor->oidArea, pCursor->currentObjectIndex)) + { + switch (nColumn) + { + case AreaObjectsColumns::ObjectID: + { + sqlite3_result_int(ctx, pObject->m_idSelf); + break; + } + + default: + LOG_WARNING("Forgot to implement a column?"); + break; + } + } + + return SQLITE_OK; +} + +static int vAreaObjectsRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) +{ + auto *pCursor = (vAreaObjects_cursor*)cur; + *pRowid = pCursor->currentObjectIndex; + return SQLITE_OK; +} + +static int vAreaObjectsEOF(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vAreaObjects_cursor*)cur; + auto *pArea = Utils::AsNWSArea(Utils::GetGameObject(pCursor->oidArea)); + if (!pArea) + return true; + return pCursor->currentObjectIndex >= pArea->m_aGameObjects.num; +} + +static int vAreaObjectsFilter(sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) +{ + (void)idxStr; + auto *pCursor = (vAreaObjects_cursor*)cur; + pCursor->oidArea = Constants::OBJECT_INVALID; + pCursor->currentObjectIndex = 0; + pCursor->objectFilter = 32767; + pCursor->skipStaticPlaceables = false; + + if ((idxNum & 1) && argc >= 1) + { + if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) + pCursor->oidArea = sqlite3_value_int(argv[0]); + else if (sqlite3_value_type(argv[0]) == SQLITE_TEXT) + pCursor->oidArea = Utils::StringToObjectID((const char*)sqlite3_value_text(argv[0])); + else + { + vAreaObjectsSetErrmsg(pCursor, "AreaObjectsVirtualTable: Invalid Target Parameter Specified!"); + return SQLITE_ERROR; + } + } + else + { + vAreaObjectsSetErrmsg(pCursor, "AreaObjectsVirtualTable: No Area Specified!"); + return SQLITE_ERROR; + } + + if ((idxNum & 2) && argc >= 2) + { + if (sqlite3_value_type(argv[1]) == SQLITE_INTEGER) + pCursor->objectFilter = sqlite3_value_int(argv[1]); + } + + if ((idxNum & 4) && argc >= 3) + { + if (sqlite3_value_type(argv[2]) == SQLITE_INTEGER) + pCursor->skipStaticPlaceables = !!sqlite3_value_int(argv[2]); + } + + auto *pArea = Utils::AsNWSArea(Utils::GetGameObject(pCursor->oidArea)); + if (!pArea) + { + vAreaObjectsSetErrmsg(pCursor, "AreaObjectsVirtualTable: Invalid Area Or Non-Area Object Specified! ObjectID = %s", Utils::ObjectIDToString(pCursor->oidArea).c_str()); + return SQLITE_ERROR; + } + + return vAreaIncrementObjectIndexIfInvalid(pCursor); +} + +static int vAreaObjectsBestIndex(sqlite3_vtab*, sqlite3_index_info *pIndexInfo) +{ + int nTargetAreaConstraintIndex = -1; + int nObjectTypeFilterConstraintIndex = -1; + int nSkipStaticPlaceablesConstraintIndex = -1; + for (int i = 0; i < pIndexInfo->nConstraint; i++) + { + auto constraint = pIndexInfo->aConstraint[i]; + if (constraint.iColumn == AreaObjectsColumns::AreaObjectID) + { + if (constraint.usable) + nTargetAreaConstraintIndex = i; + } + + if (constraint.iColumn == AreaObjectsColumns::ObjectFilter) + { + if (constraint.usable) + nObjectTypeFilterConstraintIndex = i; + } + + if (constraint.iColumn == AreaObjectsColumns::SkipStaticPlaceables) + { + if (constraint.usable) + nSkipStaticPlaceablesConstraintIndex = i; + } + } + + if (nTargetAreaConstraintIndex != -1) + { + pIndexInfo->idxNum |= 1; + pIndexInfo->aConstraintUsage[nTargetAreaConstraintIndex].argvIndex = 1; + pIndexInfo->aConstraintUsage[nTargetAreaConstraintIndex].omit = true; + } + + if (nObjectTypeFilterConstraintIndex != -1) + { + pIndexInfo->idxNum |= 2; + pIndexInfo->aConstraintUsage[nObjectTypeFilterConstraintIndex].argvIndex = 2; + pIndexInfo->aConstraintUsage[nObjectTypeFilterConstraintIndex].omit = true; + } + + if (nSkipStaticPlaceablesConstraintIndex != -1) + { + pIndexInfo->idxNum |= 4; + pIndexInfo->aConstraintUsage[nSkipStaticPlaceablesConstraintIndex].argvIndex = 3; + pIndexInfo->aConstraintUsage[nSkipStaticPlaceablesConstraintIndex].omit = true; + } + + return SQLITE_OK; +} + +static int SetupAreaObjectsVirtualTableModule(sqlite3 *db) +{ + static sqlite3_module vAreaObjectsModule = + { + 0, + nullptr, + vAreaObjectsConnect, + vAreaObjectsBestIndex, + vAreaObjectsDisconnect, + nullptr, + vAreaObjectsOpen, + vAreaObjectsClose, + vAreaObjectsFilter, + vAreaObjectsNext, + vAreaObjectsEOF, + vAreaObjectsColumn, + vAreaObjectsRowid, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }; + + return sqlite3_create_module_v2(db, "areaobjects", &vAreaObjectsModule, nullptr, nullptr); +} diff --git a/Plugins/NWSQLiteExtensions/CMakeLists.txt b/Plugins/NWSQLiteExtensions/CMakeLists.txt index 3c8b80fece1..5c5636c7dde 100644 --- a/Plugins/NWSQLiteExtensions/CMakeLists.txt +++ b/Plugins/NWSQLiteExtensions/CMakeLists.txt @@ -1,3 +1,9 @@ add_plugin(NWSQLiteExtensions "NWSQLiteExtensions.cpp" - "TwoDAVirtualTable.cpp") + "TwoDAVirtualTable.cpp" + "InventoryVirtualTable.cpp" + "ItemPropertiesVirtualTable.cpp" + "ScriptVarsVirtualTable.cpp" + "PlayersVirtualTable.cpp" + "AreaObjectsVirtualTable.cpp" + "GameObjectFunctions.cpp") diff --git a/Plugins/NWSQLiteExtensions/GameObjectFunctions.cpp b/Plugins/NWSQLiteExtensions/GameObjectFunctions.cpp new file mode 100644 index 00000000000..cf56f028df5 --- /dev/null +++ b/Plugins/NWSQLiteExtensions/GameObjectFunctions.cpp @@ -0,0 +1,412 @@ +#include "nwnx.hpp" +#include "API/CAppManager.hpp" +#include "API/CServerExoApp.hpp" +#include "API/CServerExoAppInternal.hpp" +#include "API/CNWSObject.hpp" +#include "API/CNWSCreature.hpp" +#include "API/CNWSItem.hpp" +#include "API/CNWSModule.hpp" +#include "API/CNWSArea.hpp" +#include "API/CNWSStore.hpp" +#include "API/CNWSPlaceable.hpp" +#include "API/CNWSDoor.hpp" +#include "API/CNWVisibilityNode.hpp" +#include "API/Database.hpp" + +using namespace NWNXLib; +using namespace NWNXLib::API; + +static void SetupGameObjectFunctionsHook(NWSQLite::Database*); +static bool s_EnableGameObjectFunctions = false; + +void GameObjectFunctions() __attribute__((constructor)); +void GameObjectFunctions() +{ + if ((s_EnableGameObjectFunctions = Config::Get("ENABLE_GAMEOBJECT_FUNCTIONS", false))) + { + LOG_INFO("Enabling SQLite GameObject functions for the module database."); + static Hooks::Hook s_DatabaseSetupHook = Hooks::HookFunction(&NWSQLite::Database::Setup, + +[](NWSQLite::Database *pThis) -> void + { + s_DatabaseSetupHook->CallOriginal(pThis); + + if (strcmp(pThis->m_label.c_str(), "Module()") == 0) + { + SetupGameObjectFunctionsHook(pThis); + } + }, Hooks::Order::Early); + } +} + +static ObjectID GetObjectIDFromSqliteValue(sqlite3_value *arg) +{ + ObjectID oid = Constants::OBJECT_INVALID; + if (sqlite3_value_type(arg) == SQLITE_INTEGER) + oid = sqlite3_value_int(arg); + else if (sqlite3_value_type(arg) == SQLITE_TEXT) + oid = Utils::StringToObjectID((const char*)sqlite3_value_text(arg)); + return oid; +} + +static void SetupGameObjectFunctionsHook(NWSQLite::Database *pThis) +{ +#define NWNX_SQLITE_FUNCTION(name, num_args, body) \ + sqlite3_create_function(pThis->connection().get(), name, num_args, SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_DIRECTONLY, nullptr, \ + [](sqlite3_context *ctx, int argc, sqlite3_value **argv) -> void body, nullptr, nullptr); + +#define NWNX_SQLITE_EXPOSE_INTEGER_INTERNAL(cast_type, name, func_or_variable) \ + NWNX_SQLITE_FUNCTION(name, 1, \ + { \ + (void)argc; \ + int32_t retVal = 0; \ + if (auto *pObject = Utils::AsNWS##cast_type(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0])))) \ + retVal = pObject->func_or_variable; \ + sqlite3_result_int(ctx, retVal); \ + }) +#define NWNX_SQLITE_EXPOSE_FLOAT_INTERNAL(cast_type, name, func_or_variable) \ + NWNX_SQLITE_FUNCTION(name, 1, \ + { \ + (void)argc; \ + float retVal = 0.0f; \ + if (auto *pObject = Utils::AsNWS##cast_type(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0])))) \ + retVal = pObject->func_or_variable; \ + sqlite3_result_double(ctx, retVal); \ + }) +#define NWNX_SQLITE_EXPOSE_STRING_INTERNAL(cast_type, name, func_or_variable) \ + NWNX_SQLITE_FUNCTION(name, 1, \ + { \ + (void)argc; \ + CExoString retVal; \ + if (auto *pObject = Utils::AsNWS##cast_type(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0])))) \ + retVal = pObject->func_or_variable; \ + sqlite3_result_text(ctx, retVal.CStr(), -1, SQLITE_TRANSIENT); \ + }) + +#define NWNX_SQLITE_EXPOSE_INTEGER_OBJECT(name, func_or_variable) \ + NWNX_SQLITE_EXPOSE_INTEGER_INTERNAL(Object, name, func_or_variable) +#define NWNX_SQLITE_EXPOSE_FLOAT_OBJECT(name, func_or_variable) \ + NWNX_SQLITE_EXPOSE_FLOAT_INTERNAL(Object, name, func_or_variable) +#define NWNX_SQLITE_EXPOSE_INTEGER_ITEM(name, func_or_variable) \ + NWNX_SQLITE_EXPOSE_INTEGER_INTERNAL(Item, name, func_or_variable) + + + NWNX_SQLITE_EXPOSE_INTEGER_OBJECT("plot", m_bPlotObject) + NWNX_SQLITE_EXPOSE_INTEGER_OBJECT("useable", m_bUseable) + NWNX_SQLITE_EXPOSE_INTEGER_OBJECT("current_hit_points", GetCurrentHitPoints()) + NWNX_SQLITE_EXPOSE_INTEGER_OBJECT("max_hit_points", GetMaxHitPoints()) + NWNX_SQLITE_EXPOSE_INTEGER_OBJECT("dead", GetDead()) + NWNX_SQLITE_EXPOSE_FLOAT_OBJECT("pos_x", m_vPosition.x) + NWNX_SQLITE_EXPOSE_FLOAT_OBJECT("pos_y", m_vPosition.y) + NWNX_SQLITE_EXPOSE_FLOAT_OBJECT("pos_z", m_vPosition.z) + + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("stacksize", m_nStackSize) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("gold_value", GetCost()) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("weight", GetWeight()) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("identified", m_bIdentified) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("droppable", m_bDroppable) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("pickpocketable", m_bPickPocketable) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("cursed", m_bCursed) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("stolen", m_bStolen) + NWNX_SQLITE_EXPOSE_INTEGER_ITEM("baseitem", m_nBaseItem) + + NWNX_SQLITE_FUNCTION("tag", 1, + { + (void)argc; + CExoString retVal; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pObject = Utils::AsNWSObject(pGameObject)) + retVal = pObject->m_sTag; + else if (auto *pArea = Utils::AsNWSArea(pGameObject)) + retVal = pArea->m_sTag; + else if (auto *pModule = Utils::AsNWSModule(pGameObject)) + retVal = pModule->m_sTag; + } + sqlite3_result_text(ctx, retVal.CStr(), -1, SQLITE_TRANSIENT); + }) + NWNX_SQLITE_FUNCTION("resref", 1, + { + (void)argc; + CExoString retVal; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pObject = Utils::AsNWSObject(pGameObject)) + retVal = pObject->m_sTemplate; + else if (auto *pArea = Utils::AsNWSArea(pGameObject)) + retVal = pArea->m_cResRef; + } + sqlite3_result_text(ctx, retVal.CStr(), -1, SQLITE_TRANSIENT); + }) + NWNX_SQLITE_FUNCTION("name", -1, + { + CExoString retVal; + if (argc >= 1) + { + float fSoundDuration; + bool bOriginalName = false; + if (argc >= 2 && sqlite3_value_type(argv[1]) == SQLITE_INTEGER) + bOriginalName = !!sqlite3_value_int(argv[1]); + + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pStore = Utils::AsNWSStore(pGameObject)) + { + static CExoLocString sTemp; + CNWSMessage::GetLocStringServer(0xFFFFFFFF, pStore->m_sName, sTemp, retVal, fSoundDuration); + } + else if (auto *pObject = Utils::AsNWSObject(pGameObject)) + { + if (!bOriginalName) + { + if (auto *pCreature = Utils::AsNWSCreature(pGameObject)) + { + if (!pCreature->m_sDisplayName.IsEmpty()) + retVal = pCreature->m_sDisplayName; + } + else if (auto *pPlaceable = Utils::AsNWSPlaceable(pGameObject)) + { + if (!pPlaceable->m_sDisplayName.IsEmpty()) + retVal = pPlaceable->m_sDisplayName; + } + else if (auto *pItem = Utils::AsNWSItem(pGameObject)) + { + if (!pItem->m_sDisplayName.IsEmpty()) + retVal = pItem->m_sDisplayName; + } + else if (auto *pDoor = Utils::AsNWSDoor(pGameObject)) + { + if (!pDoor->m_sDisplayName.IsEmpty()) + retVal = pDoor->m_sDisplayName; + } + } + + if (retVal.IsEmpty()) + { + CNWSMessage::GetLocStringServer(0xFFFFFFFF, pObject->GetFirstName(), pObject->GetLastName(), retVal, fSoundDuration); + } + } + else if (auto *pArea = Utils::AsNWSArea(pGameObject)) + { + if (bOriginalName || pArea->m_sDisplayName.IsEmpty()) + pArea->m_lsName.GetString(Globals::AppManager()->m_pServerExoApp->GetModuleLanguage(), &retVal); + else + retVal = pArea->m_sDisplayName; + } + else if (auto *pModule = Utils::AsNWSModule(pGameObject)) + { + pModule->m_lsModuleName.GetString(Globals::AppManager()->m_pServerExoApp->GetModuleLanguage(), &retVal); + } + } + } + sqlite3_result_text(ctx, retVal.CStr(), -1, SQLITE_TRANSIENT); + }) + NWNX_SQLITE_FUNCTION("uuid", 1, + { + (void)argc; + CExoString retVal; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pObject = Utils::AsNWSObject(pGameObject)) + retVal = pObject->m_pUUID.m_uuid; + else if (auto *pArea = Utils::AsNWSArea(pGameObject)) + retVal = pArea->m_pUUID.m_uuid; + } + sqlite3_result_text(ctx, retVal.CStr(), -1, SQLITE_TRANSIENT); + }) + + NWNX_SQLITE_FUNCTION("area", 1, + { + (void)argc; + ObjectID oidArea = Constants::OBJECT_INVALID; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pObject = Utils::AsNWSObject(pGameObject)) + oidArea = pObject->m_oidArea; + else if (auto *pArea = Utils::AsNWSArea(pGameObject)) + oidArea = pArea->m_idSelf; + } + sqlite3_result_int(ctx, oidArea); + }) + + NWNX_SQLITE_FUNCTION("object_type", 1, + { + (void)argc; + int32_t nObjectType = 0; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + nObjectType = Utils::EngineObjectTypeToNWScriptObjectType(pGameObject->m_nObjectType); + sqlite3_result_int(ctx, nObjectType); + }) + + NWNX_SQLITE_FUNCTION("local_int", 2, + { + (void)argc; + int32_t retVal = 0; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pScriptVarTable = Utils::GetScriptVarTable(pGameObject)) + { + CExoString sVarName = (const char*)sqlite3_value_text(argv[1]); + retVal = pScriptVarTable->GetInt(sVarName); + } + } + sqlite3_result_int(ctx, retVal); + }) + NWNX_SQLITE_FUNCTION("local_string", 2, + { + (void)argc; + CExoString retVal; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pScriptVarTable = Utils::GetScriptVarTable(pGameObject)) + { + CExoString sVarName = (const char*)sqlite3_value_text(argv[1]); + retVal = pScriptVarTable->GetString(sVarName); + } + } + sqlite3_result_text(ctx, retVal.CStr(), -1, SQLITE_TRANSIENT); + }) + NWNX_SQLITE_FUNCTION("local_float", 2, + { + (void)argc; + float retVal = 0.0f; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pScriptVarTable = Utils::GetScriptVarTable(pGameObject)) + { + CExoString sVarName = (const char*)sqlite3_value_text(argv[1]); + retVal = pScriptVarTable->GetFloat(sVarName); + } + } + sqlite3_result_double(ctx, retVal); + }) + NWNX_SQLITE_FUNCTION("local_object", 2, + { + (void)argc; + ObjectID retVal = Constants::OBJECT_INVALID; + if (auto *pGameObject = Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))) + { + if (auto *pScriptVarTable = Utils::GetScriptVarTable(pGameObject)) + { + CExoString sVarName = (const char*)sqlite3_value_text(argv[1]); + retVal = pScriptVarTable->GetObject(sVarName); + } + } + sqlite3_result_int(ctx, retVal); + }) + + NWNX_SQLITE_FUNCTION("int_to_hex_string", 1, + { + (void)argc; + ObjectID oid = 0; + if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) + oid = sqlite3_value_int(argv[0]); + sqlite3_result_text(ctx, Utils::ObjectIDToString(oid).c_str(), -1, SQLITE_TRANSIENT); + }) + + NWNX_SQLITE_FUNCTION("hex_string_to_int", 1, + { + (void)argc; + std::string oid; + if (sqlite3_value_type(argv[0]) == SQLITE_TEXT) + oid = (const char*)sqlite3_value_text(argv[0]); + sqlite3_result_int(ctx, Utils::StringToObjectID(oid)); + }) + + NWNX_SQLITE_FUNCTION("has_inventory", 1, + { + (void)argc; + sqlite3_result_int(ctx, Utils::GetItemRepository(GetObjectIDFromSqliteValue(argv[0])) != nullptr); + }) + + NWNX_SQLITE_FUNCTION("in_range", 3, + { + (void)argc; + bool retVal = false; + CNWSObject *pObject1 = Utils::AsNWSObject(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))); + CNWSObject *pObject2 = Utils::AsNWSObject(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[1]))); + double fDistance = sqlite3_value_double(argv[2]); + if (pObject1 && pObject2 && pObject1->m_oidArea == pObject2->m_oidArea && fDistance >= 0.0) + retVal = Vector::MagnitudeSquared(pObject1->m_vPosition - pObject2->m_vPosition) <= (fDistance * fDistance); + sqlite3_result_int(ctx, retVal); + }) + + NWNX_SQLITE_FUNCTION("distance_between", 2, + { + (void)argc; + CNWSObject *pSource = Utils::AsNWSObject(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))); + CNWSObject *pTarget = Utils::AsNWSObject(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[1]))); + + if (!pSource) + { + sqlite3_result_error(ctx, "distance_between: invalid source object.", -1); + return; + } + if (!pTarget) + { + sqlite3_result_error(ctx, "distance_between: invalid target object.", -1); + return; + } + if (pSource->m_oidArea != pTarget->m_oidArea) + { + sqlite3_result_error(ctx, "distance_between: source and target are in different areas.", -1); + return; + } + sqlite3_result_double(ctx, Vector::Magnitude(pSource->m_vPosition - pTarget->m_vPosition)); + }) + + NWNX_SQLITE_FUNCTION("perception_heard", 2, + { + (void)argc; + CNWSCreature *pSource = Utils::AsNWSCreature(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))); + CNWSCreature *pTarget = Utils::AsNWSCreature(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[1]))); + + if (!pSource) + { + sqlite3_result_error(ctx, "perception_heard: invalid or non-creature source object.", -1); + return; + } + if (!pTarget) + { + sqlite3_result_error(ctx, "perception_heard: invalid or non-creature target object.", -1); + return; + } + if (pSource->m_oidArea != pTarget->m_oidArea) + { + sqlite3_result_error(ctx, "perception_heard: source and target are in different areas.", -1); + return; + } + + int32_t retVal = 0; + if (auto *pNode = pSource->GetVisibleListElement(pTarget->m_idSelf)) + retVal = pNode->m_bHeard; + sqlite3_result_int(ctx, retVal); + }) + + NWNX_SQLITE_FUNCTION("perception_seen", 2, + { + (void)argc; + CNWSCreature *pSource = Utils::AsNWSCreature(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[0]))); + CNWSCreature *pTarget = Utils::AsNWSCreature(Utils::GetGameObject(GetObjectIDFromSqliteValue(argv[1]))); + + if (!pSource) + { + sqlite3_result_error(ctx, "perception_seen: invalid or non-creature source object.", -1); + return; + } + if (!pTarget) + { + sqlite3_result_error(ctx, "perception_seen: invalid or non-creature target object.", -1); + return; + } + if (pSource->m_oidArea != pTarget->m_oidArea) + { + sqlite3_result_error(ctx, "perception_seen: source and target are in different areas.", -1); + return; + } + + int32_t retVal = 0; + if (auto *pNode = pSource->GetVisibleListElement(pTarget->m_idSelf)) + retVal = pNode->m_bSeen; + sqlite3_result_int(ctx, retVal); + }) +} diff --git a/Plugins/NWSQLiteExtensions/InventoryVirtualTable.cpp b/Plugins/NWSQLiteExtensions/InventoryVirtualTable.cpp new file mode 100644 index 00000000000..6b155889696 --- /dev/null +++ b/Plugins/NWSQLiteExtensions/InventoryVirtualTable.cpp @@ -0,0 +1,365 @@ +#include "nwnx.hpp" +#include "API/Database.hpp" +#include "API/CNWSModule.hpp" +#include "API/CNWSCreature.hpp" +#include "API/CNWSPlaceable.hpp" +#include "API/CNWSItem.hpp" +#include "API/CItemRepository.hpp" + +using namespace NWNXLib; +using namespace NWNXLib::API; + +static bool s_InventoryVirtualTableEnabled = false; +static int SetupInventoryVirtualTableModule(sqlite3 *db); + +void InventoryVirtualTable() __attribute__((constructor)); +void InventoryVirtualTable() +{ + if ((s_InventoryVirtualTableEnabled = Config::Get("ENABLE_INVENTORY_VIRTUAL_TABLE_MODULE", false))) + { + LOG_INFO("Enabling the Inventory Virtual Table Module for the module database."); + static Hooks::Hook s_DatabaseSetupHook = Hooks::HookFunction(&NWSQLite::Database::Setup, + +[](NWSQLite::Database *pThis) -> void + { + s_DatabaseSetupHook->CallOriginal(pThis); + + if (strcmp(pThis->m_label.c_str(), "Module()") == 0) + { + if (SetupInventoryVirtualTableModule(pThis->connection().get()) != SQLITE_OK) + { + LOG_ERROR("Failed to setup the Inventory Virtual Table Module."); + s_InventoryVirtualTableEnabled = false; + } + } + }, Hooks::Order::Early); + } +} + +typedef struct vInventory_tab vInventory_tab; +struct vInventory_tab +{ + sqlite3_vtab base; +}; + +typedef struct vInventory_cursor vInventory_cursor; +struct vInventory_cursor +{ + sqlite3_vtab_cursor base; + uint32_t rowId; + ObjectID oidMainTarget; + uint32_t mainItemIndex; + ObjectID oidCurrentTarget; + uint32_t currentItemIndex; + bool bCheckBags; +}; + +namespace InventoryColumns +{ + enum TYPE + { + ObjectID = 0, + TargetObjectID, + CheckContainers, + Num, // Keep Last + }; + constexpr int32_t MAX = 2; + constexpr int32_t NUM = MAX + 1; + static_assert(MAX == CheckContainers); + static_assert(NUM == Num); + + constexpr const char* ToColumnWithType(const unsigned value) + { + constexpr const char* TYPE_STRINGS[] = + { + "oid INTEGER", + "target_oid INTEGER HIDDEN", + "check_containers INTEGER HIDDEN" + }; + static_assert((sizeof(TYPE_STRINGS) / sizeof(TYPE_STRINGS[0])) == NUM); + return (value > MAX) ? "(invalid)" : TYPE_STRINGS[value]; + } + + static CExoString GetSchema() + { + CExoString sSchema = "CREATE TABLE x("; + for (int i = 0; i < NUM; i++) + { + sSchema.Format("%s%s%s", sSchema.CStr(), i ? ", " : "", ToColumnWithType(i)); + } + sSchema.Format("%s);", sSchema.CStr()); + return sSchema; + } +} + +static void vInventorySetErrmsg(vInventory_cursor *pCur, const char *zFmt, ...) +{ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + +static CNWSItem* vInventoryGetCurrentItem(ObjectID oidTarget, uint32_t nCurrentItemIndex) +{ + uint32_t nCount = 0; + if (auto *pItemRepository = Utils::GetItemRepository(oidTarget)) + { + CExoLinkedListPosition pos = pItemRepository->m_oidItems.GetHeadPos(); + while (pos) + { + if (nCount == nCurrentItemIndex) + return pItemRepository->ItemListGetItem(pos); + + nCount++; + pItemRepository->m_oidItems.GetNext(pos); + } + } + + return nullptr; +} + +static int vInventoryConnect(sqlite3 *db, void *pAux, int argc, const char* const *argv, sqlite3_vtab **ppVtab, char **pzErr) +{ + (void)pAux; (void)argc; (void)argv; (void)pzErr; + + vInventory_tab *pNewVtab = nullptr; + int32_t ret = sqlite3_declare_vtab(db, InventoryColumns::GetSchema().CStr()); + if (ret == SQLITE_OK) + { + pNewVtab = static_cast(sqlite3_malloc(sizeof(vInventory_tab))); + if (!pNewVtab) + return SQLITE_NOMEM; + + memset(pNewVtab, 0, sizeof(vInventory_tab)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + + *ppVtab = (sqlite3_vtab*)pNewVtab; + return ret; +} + +static int vInventoryDisconnect(sqlite3_vtab *pVtab) +{ + auto *p2daVtab = (vInventory_tab*)pVtab; + sqlite3_free(p2daVtab); + return SQLITE_OK; +} + +static int vInventoryOpen(sqlite3_vtab*, sqlite3_vtab_cursor **ppCursor) +{ + auto *pCursor = static_cast(sqlite3_malloc(sizeof(vInventory_cursor))); + if(!pCursor) return SQLITE_NOMEM; + memset(pCursor, 0, sizeof(vInventory_cursor)); + pCursor->oidMainTarget = Constants::OBJECT_INVALID; + pCursor->oidCurrentTarget = Constants::OBJECT_INVALID; + *ppCursor = &pCursor->base; + return SQLITE_OK; +} + +static int vInventoryClose(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vInventory_cursor*)cur; + sqlite3_free(pCursor); + return SQLITE_OK; +} + +static int vInventoryNext(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vInventory_cursor*)cur; + CNWSItem *pItem = nullptr; + + if (pCursor->bCheckBags) + pItem = vInventoryGetCurrentItem(pCursor->oidCurrentTarget, pCursor->currentItemIndex); + + pCursor->currentItemIndex++; + + if (pItem && pItem->m_pItemRepository) + { + pCursor->oidCurrentTarget = pItem->m_idSelf; + pCursor->mainItemIndex = pCursor->currentItemIndex; + pCursor->currentItemIndex = 0; + } + + pCursor->rowId++; + + return SQLITE_OK; +} + +static int vInventoryColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int nColumn) +{ + auto *pCursor = (vInventory_cursor*)cur; + + if (nColumn == InventoryColumns::TargetObjectID || nColumn == InventoryColumns::CheckContainers) + return SQLITE_OK; + + if (auto *pItem = vInventoryGetCurrentItem(pCursor->oidCurrentTarget, pCursor->currentItemIndex)) + { + switch (nColumn) + { + case InventoryColumns::ObjectID: + { + sqlite3_result_int(ctx, pItem->m_idSelf); + break; + } + + default: + LOG_WARNING("Forgot to implement a column?"); + break; + } + } + + return SQLITE_OK; +} + +static int vInventoryRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) +{ + auto *pCursor = (vInventory_cursor*)cur; + *pRowid = pCursor->rowId; + return SQLITE_OK; +} + +static int vInventoryEOF(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vInventory_cursor*)cur; + auto *pItemRepository = Utils::GetItemRepository(pCursor->oidCurrentTarget); + if (!pItemRepository) + return true; + + if (pCursor->oidCurrentTarget != pCursor->oidMainTarget) + { + if (pCursor->currentItemIndex >= pItemRepository->m_oidItems.Count()) + { + pCursor->oidCurrentTarget = pCursor->oidMainTarget; + pCursor->currentItemIndex = pCursor->mainItemIndex; + pItemRepository = Utils::GetItemRepository(pCursor->oidCurrentTarget); + + if (!pItemRepository) + return true; + } + } + + return pCursor->currentItemIndex >= pItemRepository->m_oidItems.Count(); +} + +static int vInventoryFilter(sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) +{ + (void)idxStr; + auto *pCursor = (vInventory_cursor*)cur; + pCursor->oidMainTarget = Constants::OBJECT_INVALID; + pCursor->oidCurrentTarget = Constants::OBJECT_INVALID; + pCursor->currentItemIndex = 0; + pCursor->mainItemIndex = 0; + pCursor->bCheckBags = false; + + if ((idxNum & 1) && argc >= 1) + { + if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) + { + ObjectID oidTarget = sqlite3_value_int(argv[0]); + pCursor->oidMainTarget = oidTarget; + pCursor->oidCurrentTarget = oidTarget; + } + else if (sqlite3_value_type(argv[0]) == SQLITE_TEXT) + { + ObjectID oidTarget = Utils::StringToObjectID((const char*)sqlite3_value_text(argv[0])); + pCursor->oidMainTarget = oidTarget; + pCursor->oidCurrentTarget = oidTarget; + } + else + { + vInventorySetErrmsg(pCursor, "InventoryVirtualTable: Invalid Target Parameter Specified!"); + return SQLITE_ERROR; + } + } + else + { + vInventorySetErrmsg(pCursor, "InventoryVirtualTable: No Target Specified!"); + return SQLITE_ERROR; + } + + if ((idxNum & 2) && argc >= 2) + { + if (sqlite3_value_type(argv[1]) == SQLITE_INTEGER) + pCursor->bCheckBags = !!sqlite3_value_int(argv[1]); + } + + auto *pItemRepository = Utils::GetItemRepository(pCursor->oidCurrentTarget); + if (!pItemRepository) + { + vInventorySetErrmsg(pCursor, "InventoryVirtualTable: Invalid Target Specified! ObjectID = %s", Utils::ObjectIDToString(pCursor->oidCurrentTarget).c_str()); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +static int vInventoryBestIndex(sqlite3_vtab*, sqlite3_index_info *pIndexInfo) +{ + int nTargetInventoryConstraintIndex = -1; + int nCheckBagsConstraintIndex = -1; + for (int i = 0; i < pIndexInfo->nConstraint; i++) + { + auto constraint = pIndexInfo->aConstraint[i]; + if (constraint.iColumn == InventoryColumns::TargetObjectID) + { + if (constraint.usable) + nTargetInventoryConstraintIndex = i; + } + + if (constraint.iColumn == InventoryColumns::CheckContainers) + { + if (constraint.usable) + nCheckBagsConstraintIndex = i; + } + } + + if (nTargetInventoryConstraintIndex != -1) + { + pIndexInfo->idxNum |= 1; + pIndexInfo->aConstraintUsage[nTargetInventoryConstraintIndex].argvIndex = 1; + pIndexInfo->aConstraintUsage[nTargetInventoryConstraintIndex].omit = true; + + } + + if (nCheckBagsConstraintIndex != -1) + { + pIndexInfo->idxNum |= 2; + pIndexInfo->aConstraintUsage[nCheckBagsConstraintIndex].argvIndex = 2; + pIndexInfo->aConstraintUsage[nCheckBagsConstraintIndex].omit = true; + } + + return SQLITE_OK; +} + +static int SetupInventoryVirtualTableModule(sqlite3 *db) +{ + static sqlite3_module vInventoryModule = + { + 0, + nullptr, + vInventoryConnect, + vInventoryBestIndex, + vInventoryDisconnect, + nullptr, + vInventoryOpen, + vInventoryClose, + vInventoryFilter, + vInventoryNext, + vInventoryEOF, + vInventoryColumn, + vInventoryRowid, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }; + + return sqlite3_create_module_v2(db, "inventory", &vInventoryModule, nullptr, nullptr); +} diff --git a/Plugins/NWSQLiteExtensions/ItemPropertiesVirtualTable.cpp b/Plugins/NWSQLiteExtensions/ItemPropertiesVirtualTable.cpp new file mode 100644 index 00000000000..a4eb5501c85 --- /dev/null +++ b/Plugins/NWSQLiteExtensions/ItemPropertiesVirtualTable.cpp @@ -0,0 +1,321 @@ +#include "nwnx.hpp" +#include "API/Database.hpp" +#include "API/CNWSItem.hpp" +#include "API/CGameEffect.hpp" + +using namespace NWNXLib; +using namespace NWNXLib::API; + +static bool s_ItemPropertiesVirtualTableEnabled = false; +static int SetupItemPropertiesVirtualTableModule(sqlite3 *db); + +void ItemPropertiesVirtualTable() __attribute__((constructor)); +void ItemPropertiesVirtualTable() +{ + if ((s_ItemPropertiesVirtualTableEnabled = Config::Get("ENABLE_ITEMPROPERTIES_VIRTUAL_TABLE_MODULE", false))) + { + LOG_INFO("Enabling the ItemProperties Virtual Table Module for the module database."); + static Hooks::Hook s_DatabaseSetupHook = Hooks::HookFunction(&NWSQLite::Database::Setup, + +[](NWSQLite::Database *pThis) -> void + { + s_DatabaseSetupHook->CallOriginal(pThis); + + if (strcmp(pThis->m_label.c_str(), "Module()") == 0) + { + if (SetupItemPropertiesVirtualTableModule(pThis->connection().get()) != SQLITE_OK) + { + LOG_ERROR("Failed to setup the ItemProperties Virtual Table Module."); + s_ItemPropertiesVirtualTableEnabled = false; + } + } + }, Hooks::Order::Early); + } +} + +typedef struct vItemProperties_tab vItemProperties_tab; +struct vItemProperties_tab +{ + sqlite3_vtab base; +}; + +typedef struct vItemProperties_cursor vItemProperties_cursor; +struct vItemProperties_cursor +{ + sqlite3_vtab_cursor base; + ObjectID oidItem; + uint32_t currentItemPropertyIndex; +}; + +namespace ItemPropertiesColumns +{ + enum TYPE + { + Type = 0, + SubType, + CostTable, + CostTableValue, + Param1, + Param1Value, + Tag, + ItemOID, + Num, // Keep Last + }; + constexpr int32_t MAX = 7; + constexpr int32_t NUM = MAX + 1; + static_assert(MAX == ItemOID); + static_assert(NUM == Num); + + constexpr const char* ToColumnWithType(const unsigned value) + { + constexpr const char* TYPE_STRINGS[] = + { + "type INTEGER", + "subtype INTEGER", + "cost_table INTEGER", + "cost_table_value INTEGER", + "param1 INTEGER", + "param1_value INTEGER", + "tag TEXT", + "target_item_oid INTEGER HIDDEN", + }; + static_assert((sizeof(TYPE_STRINGS) / sizeof(TYPE_STRINGS[0])) == NUM); + return (value > MAX) ? "(invalid)" : TYPE_STRINGS[value]; + } + + static CExoString GetSchema() + { + CExoString sSchema = "CREATE TABLE x("; + for (int i = 0; i < NUM; i++) + { + sSchema.Format("%s%s%s", sSchema.CStr(), i ? ", " : "", ToColumnWithType(i)); + } + sSchema.Format("%s);", sSchema.CStr()); + return sSchema; + } +} + +static void vItemPropertiesSetErrmsg(vItemProperties_cursor *pCur, const char *zFmt, ...) +{ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + +static int vItemPropertiesConnect(sqlite3 *db, void *pAux, int argc, const char* const *argv, sqlite3_vtab **ppVtab, char **pzErr) +{ + (void)pAux; (void)argc; (void)argv; (void)pzErr; + + vItemProperties_tab *pNewVtab = nullptr; + int32_t ret = sqlite3_declare_vtab(db, ItemPropertiesColumns::GetSchema().CStr()); + if (ret == SQLITE_OK) + { + pNewVtab = static_cast(sqlite3_malloc(sizeof(vItemProperties_tab))); + if (!pNewVtab) + return SQLITE_NOMEM; + + memset(pNewVtab, 0, sizeof(vItemProperties_tab)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + + *ppVtab = (sqlite3_vtab*)pNewVtab; + return ret; +} + +static int vItemPropertiesDisconnect(sqlite3_vtab *pVtab) +{ + auto *p2daVtab = (vItemProperties_tab*)pVtab; + sqlite3_free(p2daVtab); + return SQLITE_OK; +} + +static int vItemPropertiesOpen(sqlite3_vtab*, sqlite3_vtab_cursor **ppCursor) +{ + auto *pCursor = static_cast(sqlite3_malloc(sizeof(vItemProperties_cursor))); + if(!pCursor) return SQLITE_NOMEM; + memset(pCursor, 0, sizeof(vItemProperties_cursor)); + pCursor->oidItem = Constants::OBJECT_INVALID; + *ppCursor = &pCursor->base; + return SQLITE_OK; +} + +static int vItemPropertiesClose(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vItemProperties_cursor*)cur; + sqlite3_free(pCursor); + return SQLITE_OK; +} + +static int vItemPropertiesNext(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vItemProperties_cursor*)cur; + pCursor->currentItemPropertyIndex++; + return SQLITE_OK; +} + +static CGameEffect *vItemPropertiesGetCurrentItemProperty(ObjectID oidItem, uint32_t nCurrentItemPropertyIndex) +{ + int32_t nIndex = 0; + uint32_t nCount = 0; + if (auto *pItem = Utils::AsNWSItem(Utils::GetGameObject(oidItem))) + { + while (nIndex < pItem->m_appliedEffects.num) + { + auto *pEffect = pItem->m_appliedEffects[nIndex]; + if (pEffect->m_bExpose && pEffect->m_nType == Constants::EffectTrueType::ItemProperty && + (pEffect->GetDurationType() == Constants::EffectDurationType::Permanent || + pEffect->GetDurationType() == Constants::EffectDurationType::Temporary)) + { + if (nCount == nCurrentItemPropertyIndex) + return pEffect; + + nCount++; + } + + nIndex++; + } + } + + return nullptr; +} + +static int vItemPropertiesColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int nColumn) +{ + auto *pCursor = (vItemProperties_cursor*)cur; + + if (nColumn == ItemPropertiesColumns::ItemOID) + return SQLITE_OK; + + if (auto *pItemProperty = vItemPropertiesGetCurrentItemProperty(pCursor->oidItem, pCursor->currentItemPropertyIndex)) + { + switch (nColumn) + { + case ItemPropertiesColumns::Type: + case ItemPropertiesColumns::SubType: + case ItemPropertiesColumns::CostTable: + case ItemPropertiesColumns::CostTableValue: + case ItemPropertiesColumns::Param1: + case ItemPropertiesColumns::Param1Value: + { + sqlite3_result_int(ctx, pItemProperty->GetInteger(nColumn)); + break; + } + + case ItemPropertiesColumns::Tag: + { + sqlite3_result_text(ctx, pItemProperty->m_sCustomTag.CStr(), -1, SQLITE_TRANSIENT); + break; + } + + default: + LOG_WARNING("Forgot to implement a column?"); + break; + } + } + + return SQLITE_OK; +} + +static int vItemPropertiesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) +{ + auto *pCursor = (vItemProperties_cursor*)cur; + *pRowid = pCursor->currentItemPropertyIndex; + return SQLITE_OK; +} + +static int vItemPropertiesEOF(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vItemProperties_cursor*)cur; + return !vItemPropertiesGetCurrentItemProperty(pCursor->oidItem, pCursor->currentItemPropertyIndex); +} + +static int vItemPropertiesFilter(sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) +{ + (void)idxStr; + auto *pCursor = (vItemProperties_cursor*)cur; + pCursor->oidItem = Constants::OBJECT_INVALID; + pCursor->currentItemPropertyIndex = 0; + + if ((idxNum & 1) && argc >= 1) + { + if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) + pCursor->oidItem = sqlite3_value_int(argv[0]); + else if (sqlite3_value_type(argv[0]) == SQLITE_TEXT) + pCursor->oidItem = Utils::StringToObjectID((const char*)sqlite3_value_text(argv[0])); + else + { + vItemPropertiesSetErrmsg(pCursor, "ItemPropertyVirtualTable: Invalid Target Parameter Specified!"); + return SQLITE_ERROR; + } + } + else + { + vItemPropertiesSetErrmsg(pCursor, "ItemPropertyVirtualTable: No Item Specified!"); + return SQLITE_ERROR; + } + + auto *pItem= Utils::AsNWSItem(Utils::GetGameObject(pCursor->oidItem)); + if (!pItem) + { + vItemPropertiesSetErrmsg(pCursor, "ItemPropertyVirtualTable: Invalid Or Non-Item Target Specified! ObjectID = %s", Utils::ObjectIDToString(pCursor->oidItem).c_str()); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +static int vItemPropertiesBestIndex(sqlite3_vtab*, sqlite3_index_info *pIndexInfo) +{ + int nTargetItemConstraintIndex = -1; + for (int i = 0; i < pIndexInfo->nConstraint; i++) + { + auto constraint = pIndexInfo->aConstraint[i]; + if (constraint.iColumn == ItemPropertiesColumns::ItemOID) + { + if (constraint.usable) + nTargetItemConstraintIndex = i; + } + } + + if (nTargetItemConstraintIndex != -1) + { + pIndexInfo->idxNum |= 1; + pIndexInfo->aConstraintUsage[nTargetItemConstraintIndex].argvIndex = 1; + pIndexInfo->aConstraintUsage[nTargetItemConstraintIndex].omit = true; + } + + return SQLITE_OK; +} + +static int SetupItemPropertiesVirtualTableModule(sqlite3 *db) +{ + static sqlite3_module vItemPropertiesModule = + { + 0, + nullptr, + vItemPropertiesConnect, + vItemPropertiesBestIndex, + vItemPropertiesDisconnect, + nullptr, + vItemPropertiesOpen, + vItemPropertiesClose, + vItemPropertiesFilter, + vItemPropertiesNext, + vItemPropertiesEOF, + vItemPropertiesColumn, + vItemPropertiesRowid, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }; + + return sqlite3_create_module_v2(db, "itemproperties", &vItemPropertiesModule, nullptr, nullptr); +} diff --git a/Plugins/NWSQLiteExtensions/NWSQLiteExtensions.cpp b/Plugins/NWSQLiteExtensions/NWSQLiteExtensions.cpp index 53c52951fa0..6c112a924e8 100644 --- a/Plugins/NWSQLiteExtensions/NWSQLiteExtensions.cpp +++ b/Plugins/NWSQLiteExtensions/NWSQLiteExtensions.cpp @@ -1,6 +1,6 @@ #include "nwnx.hpp" -#include "API/CNWSObject.hpp" #include "API/Database.hpp" +#include "API/CNWSObject.hpp" #include using namespace NWNXLib; @@ -22,8 +22,8 @@ void NWSQLiteExtensions() +[](CNWSObject *pThis) -> std::shared_ptr { if (!pThis->m_sqlite_db) - pThis->m_sqlite_db = std::make_shared(CExoString::F("Object(%x[%u],tag=%s)", pThis->m_idSelf, - pThis->m_nObjectType, pThis->m_sTag.CStr()), ""); + pThis->m_sqlite_db = std::make_shared( + CExoString::F("Object(%x[%u],tag=%s)", pThis->m_idSelf, pThis->m_nObjectType, pThis->m_sTag.CStr()), ""); return pThis->m_sqlite_db; }, Hooks::Order::Final); diff --git a/Plugins/NWSQLiteExtensions/PlayersVirtualTable.cpp b/Plugins/NWSQLiteExtensions/PlayersVirtualTable.cpp new file mode 100644 index 00000000000..8d1b87aacea --- /dev/null +++ b/Plugins/NWSQLiteExtensions/PlayersVirtualTable.cpp @@ -0,0 +1,265 @@ +#include "nwnx.hpp" +#include "API/Database.hpp" +#include "API/CNWSPlayer.hpp" +#include "API/CNWSCreature.hpp" +#include "API/CAppManager.hpp" +#include "API/CServerExoApp.hpp" +#include "API/CServerExoAppInternal.hpp" + + +using namespace NWNXLib; +using namespace NWNXLib::API; + +static bool s_PlayersVirtualTableEnabled = false; +static int SetupPlayersVirtualTableModule(sqlite3 *db); + +void PlayersVirtualTable() __attribute__((constructor)); +void PlayersVirtualTable() +{ + if ((s_PlayersVirtualTableEnabled = Config::Get("ENABLE_PLAYERS_VIRTUAL_TABLE_MODULE", false))) + { + LOG_INFO("Enabling the Players Virtual Table Module for the module database."); + static Hooks::Hook s_DatabaseSetupHook = Hooks::HookFunction(&NWSQLite::Database::Setup, + +[](NWSQLite::Database *pThis) -> void + { + s_DatabaseSetupHook->CallOriginal(pThis); + + if (strcmp(pThis->m_label.c_str(), "Module()") == 0) + { + if (SetupPlayersVirtualTableModule(pThis->connection().get()) != SQLITE_OK) + { + LOG_ERROR("Failed to setup the Players Virtual Table Module."); + s_PlayersVirtualTableEnabled = false; + } + } + }, Hooks::Order::Early); + } +} + +typedef struct vPlayers_tab vPlayers_tab; +struct vPlayers_tab +{ + sqlite3_vtab base; +}; + +typedef struct vPlayers_cursor vPlayers_cursor; +struct vPlayers_cursor +{ + sqlite3_vtab_cursor base; + uint32_t currentPlayerIndex; +}; + +namespace PlayersColumns +{ + enum TYPE + { + PCOID = 0, + ControlledOID, + DM, + PlayerDM, + Num, // Keep Last + }; + constexpr int32_t MAX = 3; + constexpr int32_t NUM = MAX + 1; + static_assert(MAX == PlayerDM); + static_assert(NUM == Num); + + constexpr const char* ToColumnWithType(const unsigned value) + { + constexpr const char* TYPE_STRINGS[] = + { + "oid_pc INTEGER", + "oid_controlled INTEGER", + "dm INTEGER", + "player_dm INTEGER", + }; + static_assert((sizeof(TYPE_STRINGS) / sizeof(TYPE_STRINGS[0])) == NUM); + return (value > MAX) ? "(invalid)" : TYPE_STRINGS[value]; + } + + static CExoString GetSchema() + { + CExoString sSchema = "CREATE TABLE x("; + for (int i = 0; i < NUM; i++) + { + sSchema.Format("%s%s%s", sSchema.CStr(), i ? ", " : "", ToColumnWithType(i)); + } + sSchema.Format("%s);", sSchema.CStr()); + return sSchema; + } +} + +static int vPlayersConnect(sqlite3 *db, void *pAux, int argc, const char* const *argv, sqlite3_vtab **ppVtab, char **pzErr) +{ + (void)pAux; (void)argc; (void)argv; (void)pzErr; + + vPlayers_tab *pNewVtab = nullptr; + int32_t ret = sqlite3_declare_vtab(db, PlayersColumns::GetSchema().CStr()); + if (ret == SQLITE_OK) + { + pNewVtab = static_cast(sqlite3_malloc(sizeof(vPlayers_tab))); + if (!pNewVtab) + return SQLITE_NOMEM; + + memset(pNewVtab, 0, sizeof(vPlayers_tab)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + + *ppVtab = (sqlite3_vtab*)pNewVtab; + return ret; +} + +static int vPlayersDisconnect(sqlite3_vtab *pVtab) +{ + auto *p2daVtab = (vPlayers_tab*)pVtab; + sqlite3_free(p2daVtab); + return SQLITE_OK; +} + +static int vPlayersOpen(sqlite3_vtab*, sqlite3_vtab_cursor **ppCursor) +{ + auto *pCursor = static_cast(sqlite3_malloc(sizeof(vPlayers_cursor))); + if(!pCursor) return SQLITE_NOMEM; + memset(pCursor, 0, sizeof(vPlayers_cursor)); + *ppCursor = &pCursor->base; + return SQLITE_OK; +} + +static int vPlayersClose(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vPlayers_cursor*)cur; + sqlite3_free(pCursor); + return SQLITE_OK; +} + +static int vPlayersNext(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vPlayers_cursor*)cur; + pCursor->currentPlayerIndex++; + return SQLITE_OK; +} + +static CNWSPlayer *vPlayersGetCurrentPlayer(uint32_t nCurrentPlayerIndex) +{ + auto *pServerExoAppInternal = g_pAppManager->m_pServerExoApp->m_pcExoAppInternal; + auto *pPlayerList = pServerExoAppInternal->m_pNWSPlayerList; + uint32_t nCount = 0; + + CExoLinkedListPosition pos = pPlayerList->GetHeadPos(); + while (pos) + { + if (auto *pPlayer = static_cast(pPlayerList->GetAtPos(pos))) + { + if (pPlayer->m_oidPCObject != Constants::OBJECT_INVALID) + { + if (nCount == nCurrentPlayerIndex) + return pPlayer; + nCount++; + } + } + + pPlayerList->GetNext(pos); + } + + return nullptr; +} + +static int vPlayersColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int nColumn) +{ + auto *pCursor = (vPlayers_cursor*)cur; + + if (auto *pPlayer = vPlayersGetCurrentPlayer(pCursor->currentPlayerIndex)) + { + switch (nColumn) + { + case PlayersColumns::PCOID: + { + sqlite3_result_int(ctx, pPlayer->m_oidPCObject); + break; + } + + case PlayersColumns::ControlledOID: + { + sqlite3_result_int(ctx, pPlayer->m_oidNWSObject); + break; + } + + case PlayersColumns::DM: + { + sqlite3_result_int(ctx, pPlayer->GetIsDM()); + break; + } + + case PlayersColumns::PlayerDM: + { + sqlite3_result_int(ctx, pPlayer->GetIsPlayerDM()); + break; + } + + default: + LOG_WARNING("Forgot to implement a column?"); + break; + } + } + + return SQLITE_OK; +} + +static int vPlayersRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) +{ + auto *pCursor = (vPlayers_cursor*)cur; + *pRowid = pCursor->currentPlayerIndex; + return SQLITE_OK; +} + +static int vPlayersEOF(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vPlayers_cursor*)cur; + return !vPlayersGetCurrentPlayer(pCursor->currentPlayerIndex); +} + +static int vPlayersFilter(sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) +{ + (void)idxNum; (void)idxStr; (void)argc; (void)argv; + auto *pCursor = (vPlayers_cursor*)cur; + pCursor->currentPlayerIndex = 0; + return SQLITE_OK; +} + +static int vPlayersBestIndex(sqlite3_vtab*, sqlite3_index_info *) +{ + return SQLITE_OK; +} + +static int SetupPlayersVirtualTableModule(sqlite3 *db) +{ + static sqlite3_module vPlayersModule = + { + 0, + nullptr, + vPlayersConnect, + vPlayersBestIndex, + vPlayersDisconnect, + nullptr, + vPlayersOpen, + vPlayersClose, + vPlayersFilter, + vPlayersNext, + vPlayersEOF, + vPlayersColumn, + vPlayersRowid, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }; + + return sqlite3_create_module_v2(db, "players", &vPlayersModule, nullptr, nullptr); +} diff --git a/Plugins/NWSQLiteExtensions/ScriptVarsVirtualTable.cpp b/Plugins/NWSQLiteExtensions/ScriptVarsVirtualTable.cpp new file mode 100644 index 00000000000..7a130a9b7c8 --- /dev/null +++ b/Plugins/NWSQLiteExtensions/ScriptVarsVirtualTable.cpp @@ -0,0 +1,358 @@ +#include "nwnx.hpp" +#include "API/Database.hpp" +#include "API/CNWSObject.hpp" +#include "API/CNWSArea.hpp" +#include "API/CNWSModule.hpp" +#include "API/CNWSScriptVar.hpp" +#include "API/CNWSScriptVarTable.hpp" + +using namespace NWNXLib; +using namespace NWNXLib::API; + +static bool s_ScriptVarsVirtualTableEnabled = false; +static int SetupScriptVarsVirtualTableModule(sqlite3 *db); + +void ScriptVarsVirtualTable() __attribute__((constructor)); +void ScriptVarsVirtualTable() +{ + if ((s_ScriptVarsVirtualTableEnabled = Config::Get("ENABLE_SCRIPTVARS_VIRTUAL_TABLE_MODULE", false))) + { + LOG_INFO("Enabling the ScriptVars Virtual Table Module for the module database."); + static Hooks::Hook s_DatabaseSetupHook = Hooks::HookFunction(&NWSQLite::Database::Setup, + +[](NWSQLite::Database *pThis) -> void + { + s_DatabaseSetupHook->CallOriginal(pThis); + + if (strcmp(pThis->m_label.c_str(), "Module()") == 0) + { + if (SetupScriptVarsVirtualTableModule(pThis->connection().get()) != SQLITE_OK) + { + LOG_ERROR("Failed to setup the ScriptVars Virtual Table Module."); + s_ScriptVarsVirtualTableEnabled = false; + } + } + }, Hooks::Order::Early); + } +} + +typedef struct vScriptVars_tab vScriptVars_tab; +struct vScriptVars_tab +{ + sqlite3_vtab base; +}; + +typedef struct vScriptVars_cursor vScriptVars_cursor; +struct vScriptVars_cursor +{ + sqlite3_vtab_cursor base; + ObjectID oidTarget; + uint32_t currentScriptVarIndex; +}; + +namespace ScriptVarsColumns +{ + enum TYPE + { + VarName = 0, + IntValue, + FloatValue, + StringValue, + ObjectValue, + JsonValue, + LocationValue, + CassowaryValue, + TargetOID, + Num, // Keep Last + }; + constexpr int32_t MAX = 8; + constexpr int32_t NUM = MAX + 1; + static_assert(MAX == TargetOID); + static_assert(NUM == Num); + + constexpr const char* ToColumnWithType(const unsigned value) + { + constexpr const char* TYPE_STRINGS[] = + { + "varname TEXT", + "int INTEGER", + "float REAL", + "string TEXT", + "object INTEGER", + "json TEXT", + "location INTEGER", + "cassowary INTEGER", + "target_oid INTEGER HIDDEN", + }; + static_assert((sizeof(TYPE_STRINGS) / sizeof(TYPE_STRINGS[0])) == NUM); + return (value > MAX) ? "(invalid)" : TYPE_STRINGS[value]; + } + + static CExoString GetSchema() + { + CExoString sSchema = "CREATE TABLE x("; + for (int i = 0; i < NUM; i++) + { + sSchema.Format("%s%s%s", sSchema.CStr(), i ? ", " : "", ToColumnWithType(i)); + } + sSchema.Format("%s);", sSchema.CStr()); + return sSchema; + } +} + +static void vScriptVarsSetErrmsg(vScriptVars_cursor *pCur, const char *zFmt, ...) +{ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + +static int vScriptVarsConnect(sqlite3 *db, void *pAux, int argc, const char* const *argv, sqlite3_vtab **ppVtab, char **pzErr) +{ + (void)pAux; (void)argc; (void)argv; (void)pzErr; + + vScriptVars_tab *pNewVtab = nullptr; + int32_t ret = sqlite3_declare_vtab(db, ScriptVarsColumns::GetSchema().CStr()); + if (ret == SQLITE_OK) + { + pNewVtab = static_cast(sqlite3_malloc(sizeof(vScriptVars_tab))); + if (!pNewVtab) + return SQLITE_NOMEM; + + memset(pNewVtab, 0, sizeof(vScriptVars_tab)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + + *ppVtab = (sqlite3_vtab*)pNewVtab; + return ret; +} + +static int vScriptVarsDisconnect(sqlite3_vtab *pVtab) +{ + auto *p2daVtab = (vScriptVars_tab*)pVtab; + sqlite3_free(p2daVtab); + return SQLITE_OK; +} + +static int vScriptVarsOpen(sqlite3_vtab*, sqlite3_vtab_cursor **ppCursor) +{ + auto *pCursor = static_cast(sqlite3_malloc(sizeof(vScriptVars_cursor))); + if(!pCursor) return SQLITE_NOMEM; + memset(pCursor, 0, sizeof(vScriptVars_cursor)); + pCursor->oidTarget = Constants::OBJECT_INVALID; + *ppCursor = &pCursor->base; + return SQLITE_OK; +} + +static int vScriptVarsClose(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vScriptVars_cursor*)cur; + sqlite3_free(pCursor); + return SQLITE_OK; +} + +static int vScriptVarsNext(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vScriptVars_cursor*)cur; + pCursor->currentScriptVarIndex++; + return SQLITE_OK; +} + +CNWSScriptVarTable* vScriptVarsGetScriptVarTable(ObjectID oidTarget) +{ + if (auto *pGameObject = Utils::GetGameObject(oidTarget)) + return Utils::GetScriptVarTable(pGameObject); + else + return nullptr; +} + +static int vScriptVarsColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int nColumn) +{ + auto *pCursor = (vScriptVars_cursor*)cur; + + if (nColumn == ScriptVarsColumns::TargetOID) + return SQLITE_OK; + + if (auto *pScriptVarTable = vScriptVarsGetScriptVarTable(pCursor->oidTarget)) + { + if (pCursor->currentScriptVarIndex >= pScriptVarTable->m_vars.size()) + { + vScriptVarsSetErrmsg(pCursor, "ScriptVarsVirtualTable: Row Out Of Bounds!"); + return SQLITE_ERROR; + } + + auto it = std::next(pScriptVarTable->m_vars.begin(), pCursor->currentScriptVarIndex); + + switch (nColumn) + { + case ScriptVarsColumns::VarName: + { + sqlite3_result_text(ctx, it->first.CStr(), -1, SQLITE_TRANSIENT); + break; + } + + case ScriptVarsColumns::IntValue: + { + if (it->second.HasInt()) + sqlite3_result_int(ctx, it->second.m_int); + break; + } + + case ScriptVarsColumns::FloatValue: + { + if (it->second.HasFloat()) + sqlite3_result_double(ctx, it->second.m_float); + break; + } + + case ScriptVarsColumns::StringValue: + { + if (it->second.HasString()) + sqlite3_result_text(ctx, it->second.m_string.CStr(), -1, SQLITE_TRANSIENT); + break; + } + + case ScriptVarsColumns::ObjectValue: + { + if (it->second.HasObject()) + sqlite3_result_int(ctx, it->second.m_objectId); + break; + } + + case ScriptVarsColumns::JsonValue: + { + if (it->second.HasJson()) + sqlite3_result_text(ctx, it->second.m_json.m_shared->m_json.dump().c_str(), -1, SQLITE_TRANSIENT); + break; + } + + case ScriptVarsColumns::LocationValue: + { + if (it->second.HasLocation()) + sqlite3_result_int(ctx, 1); + break; + } + + case ScriptVarsColumns::CassowaryValue: + { + if (it->second.HasCswy()) + sqlite3_result_int(ctx, 1); + break; + } + + default: + LOG_WARNING("Forgot to implement a column?"); + break; + } + } + + return SQLITE_OK; +} + +static int vScriptVarsRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) +{ + auto *pCursor = (vScriptVars_cursor*)cur; + *pRowid = pCursor->currentScriptVarIndex; + return SQLITE_OK; +} + +static int vScriptVarsEOF(sqlite3_vtab_cursor *cur) +{ + auto *pCursor = (vScriptVars_cursor*)cur; + auto *pScriptVarTable = vScriptVarsGetScriptVarTable(pCursor->oidTarget); + if (!pScriptVarTable) + return true; + + return pCursor->currentScriptVarIndex >= pScriptVarTable->m_vars.size(); +} + +static int vScriptVarsFilter(sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) +{ + (void)idxStr; + auto *pCursor = (vScriptVars_cursor*)cur; + pCursor->oidTarget = Constants::OBJECT_INVALID; + pCursor->currentScriptVarIndex = 0; + + if ((idxNum & 1) && argc >= 1) + { + if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) + pCursor->oidTarget = sqlite3_value_int(argv[0]); + else if (sqlite3_value_type(argv[0]) == SQLITE_TEXT) + pCursor->oidTarget = Utils::StringToObjectID((const char*)sqlite3_value_text(argv[0])); + else + { + vScriptVarsSetErrmsg(pCursor, "ScriptVarsVirtualTable: Invalid Target Parameter Specified!"); + return SQLITE_ERROR; + } + } + else + { + vScriptVarsSetErrmsg(pCursor, "ScriptVarsVirtualTable: No Target Specified!"); + return SQLITE_ERROR; + } + + auto *pGameObject= Utils::GetGameObject(pCursor->oidTarget); + if (!pGameObject) + { + vScriptVarsSetErrmsg(pCursor, "ScriptVarsVirtualTable: Invalid Target Specified! ObjectID = %s", Utils::ObjectIDToString(pCursor->oidTarget).c_str()); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +static int vScriptVarsBestIndex(sqlite3_vtab*, sqlite3_index_info *pIndexInfo) +{ + int nTargetConstraintIndex = -1; + for (int i = 0; i < pIndexInfo->nConstraint; i++) + { + auto constraint = pIndexInfo->aConstraint[i]; + if (constraint.iColumn == ScriptVarsColumns::TargetOID) + { + if (constraint.usable) + nTargetConstraintIndex = i; + } + } + + if (nTargetConstraintIndex != -1) + { + pIndexInfo->idxNum |= 1; + pIndexInfo->aConstraintUsage[nTargetConstraintIndex].argvIndex = 1; + pIndexInfo->aConstraintUsage[nTargetConstraintIndex].omit = true; + } + + return SQLITE_OK; +} + +static int SetupScriptVarsVirtualTableModule(sqlite3 *db) +{ + static sqlite3_module vScriptVarsModule = + { + 0, + nullptr, + vScriptVarsConnect, + vScriptVarsBestIndex, + vScriptVarsDisconnect, + nullptr, + vScriptVarsOpen, + vScriptVarsClose, + vScriptVarsFilter, + vScriptVarsNext, + vScriptVarsEOF, + vScriptVarsColumn, + vScriptVarsRowid, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }; + + return sqlite3_create_module_v2(db, "scriptvars", &vScriptVarsModule, nullptr, nullptr); +}