/// ---------------------------------------------------------------------------- /// @file nui_f_storage.nss /// @author Ed Burke (tinygiant98) /// @brief Persistent Storage formfile /// ---------------------------------------------------------------------------- const string FORM_ID = "persistent_storage"; const string PS_DATABASE = "nui_ps_data"; const string FORM_VERSION = "0.2.2"; const int PS_ACCESS_EXCLUSIVE = 1; const int PS_ACCESS_CONTENTIOUS = 2; const int PS_CONTAINER_PUBLIC = 1; const int PS_CONTAINER_CHARACTER = 2; const int PS_CONTAINER_CDKEY = 3; const int PS_CONTAINER_ITEMS_NONE = 1; const int PS_CONTAINER_ITEMS_ANY = 2; const int PS_UNLIMITED = -1; const int PS_NONE = -2; const int PS_TRUE = 1; const int PS_FALSE = -1; const float PS_UNLIMITED_DISTANCE = -1.0; const string PS_TITLE = "PS_TITLE"; const string PS_FORCE_SEARCH_BUTTON = "PS_FORCE_SEARCH_BUTTON"; const string PS_FORCE_OBJECT_STATE = "PS_FORCE_OBJECT_STATE"; const string PS_STORAGE_LIMIT = "PS_STORAGE_LIMIT"; const string PS_DISTANCE = "PS_DISTANCE"; const string PS_UNIQUE_ID = "PS_UNIQUE_ID"; const string PS_ACCESS_TYPE = "PS_ACCESS_TYPE"; const string PS_CONTAINER_TYPE = "PS_CONTAINER_TYPE"; const string PS_OPEN_INVENTORY = "PS_OPEN_INVENTORY"; const string PS_MAX_GOLD = "PS_MAX_GOLD"; const string PS_MAX_CONTAINER_ITEMS = "PS_MAX_CONTAINER_ITEMS"; const string PS_MAX_CONTAINER_ITEMS_INVENTORY = "PS_MAX_CONTAINER_ITEMS_INVENTORY"; const string PS_ORIGINAL_NAME = "PS_ORIGINAL_NAME"; const string PS_DESTROYED = "PS_DESTROYED"; const string PS_TARGETING_MODE = "PS_TARGETING_MODE"; const string PS_SEARCH_STRING = "PS_SEARCH_STRING"; const string PS_CONTAINER = "PS_CONTAINER"; const string PS_GEOMETRY = "PS_GEOMETRY"; const string PS_UUID_ARRAY = "PS_UUID_ARRAY"; const string PS_USERS = "PS_USERS"; #include "nui_i_library" #include "util_i_strings" #include "nui_c_storage" #include "util_i_varlists" object ps_GetContainer(object oPC) { return GetLocalObject(oPC, PS_CONTAINER); } int ps_GetLocalIntOrDefault(object oPC, string sVarName, int nDefault) { object oContainer = GetIsPC(oPC) ? ps_GetContainer(oPC) : oPC; int n = GetLocalInt(oContainer, sVarName); return n ? n : nDefault; } string ps_GetLocalStringOrDefault(object oPC, string sVarName, string sDefault) { object oContainer = GetIsPC(oPC) ? ps_GetContainer(oPC) : oPC; string s = GetLocalString(oContainer, sVarName); return s != "" ? s : sDefault; } float ps_GetLocalFloatOrDefault(object oPC, string sVarName, float fDefault) { object oContainer = GetIsPC(oPC) ? ps_GetContainer(oPC) : oPC; float f = GetLocalFloat(oContainer, sVarName); return f != 0.0 ? f : fDefault; } string ps_GetContainerID(object oPC) { return ps_GetLocalStringOrDefault(oPC, PS_UNIQUE_ID, GetTag(ps_GetContainer(oPC))); } int ps_GetUseSearchButton(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_FORCE_SEARCH_BUTTON, PS_FORCE_SEARCH_BUTTON_DEFAULT) == PS_TRUE; } int ps_GetSaveObjectState(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_FORCE_OBJECT_STATE, PS_FORCE_OBJECT_STATE_DEFAULT) == PS_TRUE; } int ps_GetContainerType(object oPC) { int nType = GetLocalInt(oPC, PS_CONTAINER_TYPE); object oContainer = ps_GetContainer(oPC); if (!nType && GetObjectType(oContainer) == OBJECT_TYPE_ITEM) return PS_CONTAINER_ITEM_TYPE_DEFAULT; else return PS_CONTAINER_TYPE_DEFAULT; } int ps_GetAccessType(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_ACCESS_TYPE, PS_ACCESS_TYPE_DEFAULT); } int ps_GetMaxItems(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_STORAGE_LIMIT, PS_STORAGE_LIMIT_DEFAULT); } int ps_GetMaxContainerItems(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_MAX_CONTAINER_ITEMS, PS_MAX_CONTAINER_ITEMS_DEFAULT); } int ps_GetMaxContainterItemInventory(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_MAX_CONTAINER_ITEMS_INVENTORY, PS_MAX_CONTAINER_ITEMS_INVENTORY_DEFAULT); } float ps_GetMaxDistance(object oPC) { return ps_GetLocalFloatOrDefault(oPC, PS_DISTANCE, PS_DISTANCE_DEFAULT); } int ps_GetOpenInventory(object oPC) { return ps_GetLocalIntOrDefault(oPC, PS_OPEN_INVENTORY, PS_OPEN_INVENTORY_DEFAULT) == PS_TRUE; } int ps_GetMaxGold(object oPC) { object oContainer = ps_GetContainer(oPC); if (GetObjectType(oContainer) == OBJECT_TYPE_ITEM) return PS_NONE; else return ps_GetLocalIntOrDefault(oPC, PS_MAX_GOLD, PS_MAX_GOLD_DEFAULT); } object ps_GetFirstUser(object oPC) { return GetListObject(ps_GetContainer(oPC), 0, PS_USERS); } int ps_RemoveUser(object oPC) { return RemoveListObject(ps_GetContainer(oPC), oPC, PS_USERS, TRUE); } string ps_GetOwner(object oPC, string sType = "") { if (ps_GetAccessType(oPC) == PS_ACCESS_CONTENTIOUS && ps_GetContainerType(oPC) != PS_CONTAINER_PUBLIC) oPC = ps_GetFirstUser(oPC); if (sType == "") return GetObjectUUID(oPC) + ":" + GetPCPublicCDKey(oPC, TRUE); else if (sType == "uuid") return GetObjectUUID(oPC); else if (sType == "cdkey") return GetPCPublicCDKey(oPC, TRUE); else return ""; } int ps_GetIsColored(string s) { string sPattern = "***"; sqlquery sql = SqlPrepareQueryObject(GetModule(), "SELECT @string GLOB @pattern;"); SqlBindString(sql, "@string", s); SqlBindString(sql, "@pattern", sPattern); return SqlStep(sql) ? SqlGetInt(sql, 0) : FALSE; } void ps_BeginTransaction() { SqlStep(SqlPrepareQueryCampaign(PS_DATABASE, "BEGIN TRANSACTION;")); } void ps_CommitTransaction() { SqlStep(SqlPrepareQueryCampaign(PS_DATABASE, "COMMIT TRANSACTION;")); } sqlquery ps_PrepareQuery(string sQuery) { return SqlPrepareQueryCampaign(PS_DATABASE, sQuery); } string ps_GetTableName(object oPC) { return ps_GetContainerID(oPC); } void ps_InitializeDatabase(object oPC) { string sTable = ps_GetTableName(oPC); string sQuery = "SELECT name FROM sqlite_master WHERE type='table' AND name = @table;"; sqlquery sql = ps_PrepareQuery(sQuery); SqlBindString(sql, "@table", sTable); if (!SqlStep(sql)) { sQuery = "CREATE TABLE IF NOT EXISTS " + sTable + " (" + "owner TEXT NOT NULL DEFAULT '', " + "item_uuid TEXT NOT NULL DEFAULT '', " + "item_name TEXT NOT NULL DEFAULT '', " + "item_baseitem INTEGER NOT NULL DEFAULT '', " + "item_stacksize INTEGER NOT NULL DEFAULT '', " + "item_iconresref TEXT NOT NULL DEFAULT '', " + "item_data TEXT_NOT NULL DEFAULT '', " + "PRIMARY KEY(owner, item_uuid));"; SqlStep(ps_PrepareQuery(sQuery)); } } void ps_EnterDepositMode(object oPC) { SetLocalInt(oPC, PS_TARGETING_MODE, TRUE); EnterTargetingMode(oPC, OBJECT_TYPE_ITEM); } int ps_CountStoredItems(object oPC) { string sAnd, sOwner; int nType = ps_GetContainerType(oPC); if (nType == PS_CONTAINER_CHARACTER || nType == PS_CONTAINER_CDKEY) { sAnd = " AND owner GLOB @owner"; if (nType == PS_CONTAINER_CHARACTER) sOwner = ps_GetOwner(oPC, "uuid") + ":*"; else sOwner = "*:" + ps_GetOwner(oPC, "cdkey"); } string sQuery = "SELECT COUNT(*) FROM " + ps_GetTableName(oPC) + " " + "WHERE item_uuid != 'gold'" + sAnd + ";"; sqlquery sql = ps_PrepareQuery(sQuery); if (sAnd != "") SqlBindString(sql, "@owner", sOwner); return SqlStep(sql) ? SqlGetInt(sql, 0) : 0; } int ps_CountStoredGold(object oPC) { int nType = ps_GetContainerType(oPC); string sWhere = (nType == PS_CONTAINER_PUBLIC ? "" : " AND owner GLOB @owner"); string sQuery = "SELECT SUM(item_stacksize) FROM " + ps_GetTableName(oPC) + " " + "WHERE item_uuid == 'gold'" + sWhere + ";"; sqlquery sql = ps_PrepareQuery(sQuery); if (nType == PS_CONTAINER_CHARACTER) SqlBindString(sql, "@owner", ps_GetOwner(oPC, "uuid") + ":*"); else if (nType == PS_CONTAINER_CDKEY) SqlBindString(sql, "@owner", "*:" + ps_GetOwner(oPC, "cdkey")); return SqlStep(sql) ? SqlGetInt(sql, 0) : 0; } string ps_GetIconResref(object oItem, json jItem, int nBaseItem) { if (nBaseItem == BASE_ITEM_CLOAK) return "iit_cloak"; else if (nBaseItem == BASE_ITEM_SPELLSCROLL || nBaseItem == BASE_ITEM_ENCHANTED_SCROLL) { if (GetItemHasItemProperty(oItem, ITEM_PROPERTY_CAST_SPELL)) { itemproperty ip = GetFirstItemProperty(oItem); while (GetIsItemPropertyValid(ip)) { if (GetItemPropertyType(ip) == ITEM_PROPERTY_CAST_SPELL) return Get2DAString("iprp_spells", "Icon", GetItemPropertySubType(ip)); ip = GetNextItemProperty(oItem); } } } else if (Get2DAString("baseitems", "ModelType", nBaseItem) == "0") { json jSimpleModel = JsonPointer(jItem, "/ModelPart1/value"); if (JsonGetType(jSimpleModel) == JSON_TYPE_INTEGER) { string sSimpleModelId = IntToString(JsonGetInt(jSimpleModel)); while (GetStringLength(sSimpleModelId) < 3) sSimpleModelId = "0" + sSimpleModelId; string sDefaultIcon = Get2DAString("baseitems", "DefaultIcon", nBaseItem); switch (nBaseItem) { case BASE_ITEM_MISCSMALL: case BASE_ITEM_CRAFTMATERIALSML: sDefaultIcon = "iit_smlmisc_" + sSimpleModelId; break; case BASE_ITEM_MISCMEDIUM: case BASE_ITEM_CRAFTMATERIALMED: case 112: sDefaultIcon = "iit_midmisc_" + sSimpleModelId; break; case BASE_ITEM_MISCLARGE: sDefaultIcon = "iit_talmisc_" + sSimpleModelId; break; case BASE_ITEM_MISCTHIN: sDefaultIcon = "iit_thnmisc_" + sSimpleModelId; break; } int nLength = GetStringLength(sDefaultIcon); if (GetSubString(sDefaultIcon, nLength - 4, 1) == "_") sDefaultIcon = GetStringLeft(sDefaultIcon, nLength - 4); string sIcon = sDefaultIcon + "_" + sSimpleModelId; if (ResManGetAliasFor(sIcon, RESTYPE_TGA) != "") return sIcon; } } return Get2DAString("baseitems", "DefaultIcon", nBaseItem); } string ps_FormatGold(int nGold, int nForce = FALSE) { if (nGold < 100000 || nForce) return FormatInt(nGold, "%,d"); else if (nGold >= 1000000) return FormatFloat(nGold / 1000000.0, "%.1fM"); else return FormatFloat(nGold / 1000.0, "%.1fk"); } void ps_UpdateGoldBinds(object oPC, int nToken, int nTotal = -1) { if (nTotal == -1) nTotal = ps_CountStoredGold(oPC); NuiSetBind(oPC, nToken, "gold_stored", JsonInt(nTotal)); NuiSetBind(oPC, nToken, "gold_stored_label", JsonString("Gold: " + ps_FormatGold(nTotal))); if (nTotal > 100000) NuiSetBind(oPC, nToken, "gold_stored_tooltip", JsonString(ps_FormatGold(nTotal, TRUE))); else NuiSetBind(oPC, nToken, "gold_stored_tooltip", JsonString("")); int nGold = GetGold(oPC); NuiSetBind(oPC, nToken, "btn_withdraw_gold", JsonBool(nTotal > 0)); NuiSetBind(oPC, nToken, "btn_deposit_gold", JsonBool(nGold > 0)); int nAmount = StringToInt(JsonGetString(NuiGetBind(oPC, nToken, "gold_amount"))); int nWithdraw = nAmount <= 0 || nAmount > nTotal ? nTotal : nAmount; int nDeposit = nAmount <= 0 || nAmount > nGold ? min(ps_GetMaxGold(oPC) - nTotal, nGold) : nAmount; NuiSetBind(oPC, nToken, "btn_withdraw_tooltip", JsonString("Withdraw " + ps_FormatGold(nWithdraw, TRUE) + " gold")); NuiSetBind(oPC, nToken, "btn_deposit_tooltip", JsonString("Deposit " + ps_FormatGold(nDeposit, TRUE) + " gold")); NuiSetBind(oPC, nToken, "txt_gold_amount", JsonBool(nGold > 0 || nTotal > 0)); } /// @private Updates the amount of gold stored in a container. If the container /// doesn't allow gold, no action is taken. Stored gold will be updated /// *by* nGold, not *to* nGold. /// @param nGold can be positive, negative. `0` is a special case to zero-out /// the gold in the container (withdrawing all gold) for the owning character int ps_UpdateGold(object oPC, int nToken, int nGold) { if (ps_GetMaxGold(oPC) <= PS_NONE) return FALSE; string sGold = (nGold == 0 ? "@nGold" : "item_stacksize + @nGold"); string sQuery = "INSERT INTO " + ps_GetTableName(oPC) + "(owner, item_uuid, item_baseitem, item_stacksize) " + "VALUES (@owner, @item_uuid, @item_baseitem, @item_stacksize) " + "ON CONFLICT (owner, item_uuid) DO UPDATE " + "SET item_stacksize = " + sGold + ";"; sqlquery sql = ps_PrepareQuery(sQuery); SqlBindString(sql, "@owner", ps_GetOwner(oPC)); SqlBindString(sql, "@item_uuid", "gold"); SqlBindInt (sql, "@item_baseitem", BASE_ITEM_GOLD); SqlBindInt (sql, "@item_stacksize", nGold); SqlBindInt (sql, "@nGold", nGold); SqlStep(sql); ps_UpdateGoldBinds(oPC, nToken); return nGold; } int ps_WithdrawGold(object oPC, int nToken, int nGold) { if (ps_GetMaxGold(oPC) <= PS_NONE) return FALSE; if (ps_GetContainerType(oPC) != PS_CONTAINER_PUBLIC) return ps_UpdateGold(oPC, nToken, -nGold); string sTable = ps_GetTableName(oPC); string sQuery = "DELETE FROM " + sTable + " WHERE ROWID IN (SELECT ROWID FROM " + sTable + " t1 " + "WHERE (SELECT SUM(t2.item_stacksize) FROM " + sTable + " t2 WHERE t1.item_stacksize >= " + "t2.item_stacksize) <= @target ORDER BY t1.item_stacksize ASC) AND item_uuid = 'gold' " + "RETURNING item_stacksize;"; sqlquery sql = ps_PrepareQuery(sQuery); SqlBindInt(sql, "@target", nGold); int nRemoved, nRecords; while (SqlStep(sql)) { nRemoved += SqlGetInt(sql, 0); nRecords++; } if (!nRecords || nGold - nRemoved > 0) { sQuery = "UPDATE " + sTable + " SET item_stacksize = item_stacksize - @gold " + "WHERE ROWID IN (SELECT ROWID FROM " + sTable + " WHERE item_stacksize >= " + "@gold AND item_uuid = 'gold' ORDER BY RANDOM() LIMIT 1);"; sql = ps_PrepareQuery(sQuery); SqlBindInt(sql, "@gold", nGold - nRemoved); SqlStep(sql); } return TRUE; } void ps_UpdateItemList(object oPC, int nFlag = FALSE) { /// @note This NUI_DisplaySubform only exists because of an nui issue where shortening a array bound to a listbox /// a sufficient amount while scrolled near the bottom results in an nui error. To fix this issue, the listbox /// is implemented as a subform and reloaded here each time this function is called. It effectively fixes the /// problem, but should be removed when .35 is stable. See additional sections affected by this in DefineForm(). NUI_DisplaySubform(oPC, FORM_ID, "grpItems", "lstItems"); string sAnd, sSearch = GetLocalString(oPC, PS_SEARCH_STRING); int n, nItems, nGold, nType = ps_GetContainerType(oPC); int nToken = NUI_GetFormToken(oPC, FORM_ID); string sWhere = (nType == PS_CONTAINER_PUBLIC ? "" : " AND owner GLOB @owner"); json jWhere = JsonArrayInsert(JsonArray(), JsonString(sWhere)); sWhere += (sSearch == "" ? "" : " AND lower(item_name) GLOB @item"); jWhere = JsonArrayInsert(jWhere, JsonString(sWhere)); string sTable = ps_GetTableName(oPC); string sQuery = "WITH gold AS (SELECT SUM(item_stacksize) pieces FROM " + sTable + " WHERE item_uuid == 'gold'$1 ), " + "items AS (SELECT item_uuid, IIF(item_stacksize > 1, item_name || ' (x' || item_stacksize || ')', item_name) name, " + "item_iconresref, json('false') selected FROM " + sTable + " WHERE item_uuid != 'gold'$2 " + "ORDER BY item_name ASC, item_baseitem ASC) SELECT COUNT(items.item_uuid) items, gold.pieces, " + "IIF(json_group_array(item_uuid) == json_array(null), json_array(), json_group_array(item_uuid)) uuid, " + "IIF(json_group_array(name) == json_array(null), json_array(), json_group_array(name)) name, " + "IIF(json_group_array(item_iconresref) == json_array(null), json_array(), json_group_array(item_iconresref)) resref, " + "IIF(json_group_array(json(selected)) == json_array(null), json_array(), json_group_array(json(selected))) selected " + "FROM gold LEFT JOIN items;"; sqlquery sql = ps_PrepareQuery(SubstituteString(sQuery, jWhere)); if (sSearch != "") SqlBindString(sql, "@item", "*" + GetStringLowerCase(sSearch) + "*"); if (nType == PS_CONTAINER_CHARACTER) SqlBindString(sql, "@owner", ps_GetOwner(oPC, "uuid") + ":*"); else if (nType == PS_CONTAINER_CDKEY) SqlBindString(sql, "@owner", "*:" + ps_GetOwner(oPC, "cdkey")); if (SqlStep(sql)) { nItems = SqlGetInt(sql, 0); nGold = SqlGetInt(sql, 1); SetLocalJson(oPC, PS_UUID_ARRAY, SqlGetJson(sql, 2)); NuiSetBind(oPC, nToken, "names", SqlGetJson(sql, 3)); NuiSetBind(oPC, nToken, "icons", SqlGetJson(sql, 4)); NuiSetBind(oPC, nToken, "selected", SqlGetJson(sql, 5)); } int nMax = ps_GetMaxItems(oPC); if (nMax >= 0) { string sColor; float f = nItems * 1.0 / nMax; if (f > 0.9) sColor = NUI_DefineHexColor(COLOR_RED); else if (f > 0.75) sColor = NUI_DefineHexColor(COLOR_YELLOW); else sColor = NUI_DefineHexColor(COLOR_GREEN); NuiSetBind(oPC, nToken, "progress", JsonFloat(f)); NuiSetBind(oPC, nToken, "progress_color", JsonParse(sColor)); NuiSetBind(oPC, nToken, "progress_tooltip", JsonString(IntToString(nItems) + " of " + IntToString(nMax) + " items stored")); } else { NuiSetBind(oPC, nToken, "progress", JsonFloat(0.0)); NuiSetBind(oPC, nToken, "progress_tooltip", JsonString("This container has unlimited item storage")); } NuiSetBind(oPC, nToken, "btn_withdraw", JsonBool(FALSE)); NuiSetBind(oPC, nToken, "btn_withdraw_all", JsonBool(nItems > 0 && ps_GetAccessType(oPC) != PS_ACCESS_CONTENTIOUS)); NuiSetBind(oPC, nToken, "btn_deposit", JsonBool(nItems < nMax || nMax <= 0)); ps_UpdateGoldBinds(oPC, nToken, nGold); if (!nFlag && ps_GetAccessType(oPC) == PS_ACCESS_CONTENTIOUS) { object oContainer = ps_GetContainer(oPC); int n; for (n; n < CountObjectList(oContainer, PS_USERS); n++) { object oUser = GetListObject(oContainer, n, PS_USERS); if (oUser != oPC && NuiFindWindow(oUser, FORM_ID)) ps_UpdateItemList(oUser, TRUE); } } } int ps_CountInventoryItems(object oContainer) { int n; object oItem = GetFirstItemInInventory(oContainer); while (GetIsObjectValid(oItem)) { n++; oItem = GetNextItemInInventory(oContainer); } return n; } int ps_CountContainerItems(object oPC) { string sQuery = "SELECT COUNT(*) FROM " + ps_GetTableName(oPC) + " " + "WHERE item_data != '' AND json_extract(item_data, '$.ItemList') IS NOT NULL;"; sqlquery sql = ps_PrepareQuery(sQuery); return SqlStep(sql) ? SqlGetInt(sql, 0) : 0; } int ps_DepositContainerItem(object oPC, object oItem) { if (!GetHasInventory(oItem)) return TRUE; int nMaxContainerItems = ps_GetMaxContainerItems(oPC); if (nMaxContainerItems <= PS_NONE) return FALSE; else { if (nMaxContainerItems == PS_UNLIMITED || ps_CountContainerItems(oPC) < nMaxContainerItems) { int nMaxItems = ps_GetMaxContainterItemInventory(oPC); if (nMaxItems == PS_UNLIMITED) return TRUE; else { if (nMaxItems == PS_NONE) return !GetIsObjectValid(GetFirstItemInInventory(oItem)); else return ps_CountInventoryItems(oItem) <= nMaxItems; } } else return FALSE; } } void ps_DepositItem(object oPC, object oItem) { DeleteLocalInt(oPC, PS_TARGETING_MODE); if (!GetIsObjectValid(oItem) || GetLocalInt(oItem, PS_DESTROYED) || GetObjectType(oItem) != OBJECT_TYPE_ITEM) return; if (GetItemCursedFlag(oItem) || GetItemPossessor(oItem) != oPC) return; int nStoredItems = ps_CountStoredItems(oPC); int nMaxItems = ps_GetMaxItems(oPC); if (nMaxItems > 0 && nStoredItems >= nMaxItems) { SendMessageToPC(oPC, "Your storage is full, withdraw an item first."); return; } if (!ps_DepositContainerItem(oPC, oItem)) return; int nItemBaseItem = GetBaseItemType(oItem); string sItemName = GetIdentified(oItem) ? GetName(oItem) : GetStringByStrRef(StringToInt(Get2DAString("baseitems", "Name", nItemBaseItem))) + " (Unidentified)"; json jItemData = ObjectToJson(oItem, ps_GetSaveObjectState(oPC)); if (ps_GetIsColored(sItemName)) { JsonObjectSet(jItemData, PS_ORIGINAL_NAME, JsonString(sItemName)); sItemName = UnColorString(sItemName); } string sQuery = "INSERT INTO " + ps_GetTableName(oPC) + "(owner, item_uuid, item_name, item_baseitem, item_stacksize, item_iconresref, item_data) " + "VALUES(@owner, @item_uuid, @item_name ->> '$', @item_baseitem, @item_stacksize, @item_iconresref, @item_data) " + "RETURNING item_uuid;"; sqlquery sql = ps_PrepareQuery(sQuery); SqlBindString(sql, "@owner", ps_GetOwner(oPC)); SqlBindString(sql, "@item_uuid", GetObjectUUID(oItem)); SqlBindJson (sql, "@item_name", JsonString(sItemName)); SqlBindInt (sql, "@item_baseitem", nItemBaseItem); SqlBindInt (sql, "@item_stacksize", GetItemStackSize(oItem)); SqlBindString(sql, "@item_iconresref", ps_GetIconResref(oItem, jItemData, nItemBaseItem)); SqlBindJson (sql, "@item_data", jItemData); if (SqlStep(sql)) { if (SqlGetString(sql, 0) == "") { NUI_Debug("Error depositing item!", NUI_DEBUG_SEVERITY_ERROR); return; } } SetLocalInt(oItem, PS_DESTROYED, TRUE); DestroyObject(oItem); ps_UpdateItemList(oPC); if (++nStoredItems <= nMaxItems || nMaxItems <= 0) ps_EnterDepositMode(oPC); } /// @private void version of JsonToObject to prevent mass-withdraw overflow errors. void ps_JsonToObject(json jObject, location l, object oOwner, int nObjectState) { object oItem = JsonToObject(jObject, l, oOwner, nObjectState); json jName = JsonObjectGet(jObject, PS_ORIGINAL_NAME); if (jName != JsonNull()) SetName(oItem, JsonGetString(jName)); } void ps_WithdrawItems(object oPC, int nToken, int bForceAll = FALSE) { json jUUIDs = GetLocalJson(oPC, PS_UUID_ARRAY); if (!JsonGetLength(jUUIDs)) return; string sWhere = (bForceAll ? "" : " WHERE truths.value = true"); string sQuery = "WITH truths AS (SELECT ROWID, value FROM json_each(@truths)), " + " uuids AS (SELECT rowid, value FROM json_each(@uuids)) " + "DELETE FROM " + ps_GetTableName(oPC) + " WHERE item_uuid IN " + "(SELECT uuids.value FROM uuids INNER JOIN truths ON uuids.ROWID " + "= truths.ROWID" + sWhere + ") RETURNING item_data;"; sqlquery sql = ps_PrepareQuery(sQuery); SqlBindJson(sql, "@uuids", jUUIDs); SqlBindJson(sql, "@truths", NuiGetBind(oPC, nToken, "selected")); ps_BeginTransaction(); location l = GetLocation(oPC); int nState = ps_GetSaveObjectState(oPC); while (SqlStep(sql)) { json j = SqlGetJson(sql, 0); DelayCommand(0.0, ps_JsonToObject(j, l, oPC, nState)); } ps_UpdateItemList(oPC); ps_CommitTransaction(); } void ps_OnFormOpen() { ps_InitializeDatabase(OBJECT_SELF); object oContainer = ps_GetContainer(OBJECT_SELF); int nAccess = ps_GetAccessType(OBJECT_SELF); int nType = ps_GetContainerType(OBJECT_SELF); NUI_SetBind(OBJECT_SELF, FORM_ID, "title", nuiString(ps_GetFormTitle(oContainer, OBJECT_SELF, nAccess, nType))); if (ps_GetOpenInventory(OBJECT_SELF)) PopUpGUIPanel(OBJECT_SELF, GUI_PANEL_INVENTORY); ps_UpdateItemList(OBJECT_SELF); } void ps_OnFormClose() { if (ps_GetAccessType(OBJECT_SELF) == PS_ACCESS_CONTENTIOUS) ps_RemoveUser(OBJECT_SELF); DeleteLocalInt(OBJECT_SELF, PS_TARGETING_MODE); DeleteLocalString(OBJECT_SELF, PS_SEARCH_STRING); DeleteLocalJson(OBJECT_SELF, PS_UUID_ARRAY); DeleteLocalObject(OBJECT_SELF, PS_CONTAINER); } void BindForm() { json jBinds = NUI_GetOrphanBinds(FORM_ID); int n; for (n; n < JsonGetLength(jBinds); n++) { string sValue, sBind = JsonGetString(JsonArrayGet(jBinds, n)); json jValue = JsonNull(); if (sBind == "search") sValue = nuiString(""); if (sValue != "") NUI_SetBind(OBJECT_SELF, FORM_ID, sBind, sValue); else if (jValue != JsonNull()) NUI_SetBindJ(OBJECT_SELF, FORM_ID, sBind, jValue); } } void DefineForm() { float fRowHeight = 30.0; NUI_CreateForm(FORM_ID, FORM_VERSION); NUI_SetTOCTitle("Peristent Storage"); NUI_SetResizable(FALSE); NUI_SubscribeEvent(EVENT_SCRIPT_MODULE_ON_PLAYER_TARGET); NUI_SubscribeEvent(EVENT_SCRIPT_PLACEABLE_ON_USED); NUI_SubscribeEvent(EVENT_SCRIPT_PLACEABLE_ON_OPEN); NUI_SubscribeEvent(EVENT_SCRIPT_PLACEABLE_ON_CLOSED); NUI_SubscribeEvent(EVENT_SCRIPT_MODULE_ON_ACTIVATE_ITEM); { NUI_AddRow(); NUI_AddColumn(); NUI_SetWidth(350.0); NUI_AddRow(); NUI_SetHeight(20.0); NUI_SetMargin(0.0); NUI_AddProgressBar(); NUI_BindTooltip("progress_tooltip"); NUI_BindValue("progress"); NUI_BindForegroundColor("progress_color"); NUI_CloseRow(); NUI_AddRow(); NUI_SetHeight(fRowHeight); NUI_SetMargin(0.0); NUI_AddTextbox(); NUI_SetPlaceholder("Search..."); NUI_BindValue("search", TRUE); NUI_SetLength(64); NUI_SetMultiline(FALSE); NUI_AddCommandButton("btn_clear"); NUI_SetLabel("X"); NUI_BindEnabled("btn_clear"); NUI_SetTooltip("Clear search criteria"); NUI_SetWidth(35.0); NUI_AddCommandButton("btn_search"); NUI_SetLabel("Search"); NUI_SetWidth(60.0); NUI_BindEnabled("btn_search"); NUI_SetTooltip("Search item list by criteria"); NUI_SetDisabledTooltip("Live search enabled"); NUI_CloseRow(); /// @note Due to the list size issue with NUI in .34, the item listbox had to be /// implemented as a subform. The commented out section immediately below /// this AddRow() block is the original implementation and should be reinstanted /// once (if?) .35 is stable. Additionally, remove the subform definition /// below. NUI_AddRow(); NUI_SetHeight(288.0); NUI_SetMargin(0.0); NUI_AddGroup("grpItems"); NUI_SetBorder(TRUE); NUI_SetScrollbars(NUI_SCROLLBARS_NONE); { NUI_AddSpacer(); } NUI_CloseGroup(); NUI_CloseRow(); /* NUI_AddRow(); NUI_AddListbox(); NUI_BindRowCount("icons"); NUI_SetRowHeight(32.0); { NUI_AddGroup(); NUI_SetBorder(TRUE); NUI_SetScrollbars(NUI_SCROLLBARS_NONE); NUI_SetTemplateWidth(32.0); NUI_SetTemplateVariable(FALSE); { NUI_AddImage(); NUI_BindResref("icons"); NUI_SetAspect(NUI_ASPECT_FIT); NUI_SetHorizontalAlignment(NUI_HALIGN_CENTER); NUI_SetVerticalAlignment(NUI_VALIGN_MIDDLE); } NUI_CloseGroup(); NUI_AddCheckbox(); NUI_BindLabel("names"); NUI_BindValue("selected"); } NUI_CloseListbox(); NUI_CloseRow(); */ NUI_AddRow(); NUI_SetHeight(fRowHeight); NUI_SetMargin(0.0); NUI_AddCommandButton("btn_withdraw"); NUI_SetLabel("Withdraw"); NUI_BindEnabled("btn_withdraw"); NUI_SetTooltip("Withdraw selected items"); NUI_SetDisabledTooltip("No items selected for withdrawal"); NUI_SetWidth(100.0); NUI_AddCommandButton("btn_withdraw_all"); NUI_SetLabel("All"); NUI_BindEnabled("btn_withdraw_all"); NUI_SetTooltip("Withdraw all items"); NUI_SetDisabledTooltip("No items available for withdrawal"); NUI_SetWidth(45.0); NUI_AddSpacer(); NUI_AddCommandButton("btn_deposit"); NUI_SetLabel("Deposit"); NUI_BindEnabled("btn_deposit"); NUI_SetTooltip("Select an item to deposit"); NUI_SetWidth(100.0); NUI_CloseRow(); NUI_AddRow(); NUI_SetHeight(8.0); NUI_CloseRow(); NUI_AddRow(); NUI_SetHeight(fRowHeight); NUI_BindVisible("showGold"); NUI_SetMargin(0.0); NUI_AddGroup(); NUI_SetBorder(TRUE); NUI_SetScrollbars(NUI_SCROLLBARS_NONE); NUI_SetWidth(100.0); NUI_BindTooltip("gold_stored_tooltip"); { NUI_AddLabel(); NUI_BindLabel("gold_stored_label"); NUI_SetForegroundColor(NUI_DefineHexColor(COLOR_GOLD)); NUI_SetHorizontalAlignment(NUI_HALIGN_LEFT); NUI_SetVerticalAlignment(NUI_VALIGN_MIDDLE); } NUI_CloseGroup(); NUI_AddTextbox(); NUI_SetPlaceholder("Amount..."); NUI_BindEnabled("txt_gold_amount"); NUI_BindValue("gold_amount", TRUE); NUI_BindTooltip("gold_amount_tooltip"); NUI_SetLength(64); NUI_SetMultiline(FALSE); NUI_AddCommandButton("btn_withdraw_gold"); NUI_SetLabel("Withdraw"); NUI_BindEnabled("btn_withdraw_gold"); NUI_BindTooltip("btn_withdraw_tooltip"); NUI_SetWidth(75.0); NUI_AddCommandButton("btn_deposit_gold"); NUI_SetLabel("Deposit"); NUI_BindEnabled("btn_deposit_gold"); NUI_BindTooltip("btn_deposit_tooltip"); NUI_SetWidth(75.0); NUI_CloseRow(); NUI_CloseColumn(); NUI_CloseRow(); } /// @note Remove this subform definition when .35 is stable. NUI_CreateSubform("lstItems"); { NUI_AddRow(); NUI_AddListbox(); NUI_BindRowCount("icons"); NUI_SetRowHeight(32.0); NUI_SetBorder(FALSE); { NUI_AddGroup(); NUI_SetBorder(TRUE); NUI_SetScrollbars(NUI_SCROLLBARS_NONE); NUI_SetTemplateWidth(32.0); NUI_SetTemplateVariable(FALSE); { NUI_AddImage(); NUI_BindResref("icons"); NUI_SetAspect(NUI_ASPECT_FIT); NUI_SetHorizontalAlignment(NUI_HALIGN_CENTER); NUI_SetVerticalAlignment(NUI_VALIGN_MIDDLE); } NUI_CloseGroup(); NUI_AddCheckbox(); NUI_BindLabel("names"); NUI_BindValue("selected", TRUE); } NUI_CloseListbox(); NUI_CloseRow(); } NUI_CreateDefaultProfile(); { NUI_SetProfileBind("geometry", NUI_DefineRectangle(360.0, 0.0, 370.0, 470.0)); NUI_SetProfileBind("search", nuiString("")); NUI_SetProfileBind("showGold", nuiBool(TRUE)); NUI_SetProfileBind("showDMPanel", nuiBool(FALSE)); } NUI_CreateProfile("noGold"); { NUI_SetProfileBind("showGold", nuiBool(FALSE)); } } void HandleNUIEvents() { struct NUIEventData ed = NUI_GetEventData(); if (ed.sEvent == "click") { if (ed.sControlID == "btn_withdraw") ps_WithdrawItems(ed.oPC, ed.nToken); else if (ed.sControlID == "btn_withdraw_all") ps_WithdrawItems(ed.oPC, ed.nToken, TRUE); else if (ed.sControlID == "btn_deposit") ps_EnterDepositMode(ed.oPC); else if (ed.sControlID == "btn_deposit_gold") { int nAmount, nGold = GetGold(ed.oPC); string sAmount = JsonGetString(NuiGetBind(ed.oPC, ed.nToken, "gold_amount")); if (sAmount == "") nAmount = nGold; else nAmount = clamp(StringToInt(sAmount), 0, nGold); if ((nGold = ps_GetMaxGold(ed.oPC)) > -2) nAmount = min(nAmount, nGold - JsonGetInt(NuiGetBind(ed.oPC, ed.nToken, "gold_stored"))); if (nAmount <= 0) return; if (ps_UpdateGold(ed.oPC, ed.nToken, nAmount)) AssignCommand(ed.oPC, TakeGoldFromCreature(nAmount, ed.oPC, TRUE)); DelayCommand(0.1, ps_UpdateGoldBinds(ed.oPC, ed.nToken)); } else if (ed.sControlID == "btn_withdraw_gold") { int nAmount, nGold = JsonGetInt(NuiGetBind(ed.oPC, ed.nToken, "gold_stored")); string sAmount = JsonGetString(NuiGetBind(ed.oPC, ed.nToken, "gold_amount")); if (sAmount == "") nAmount = nGold; else nAmount = clamp(StringToInt(sAmount), 0, nGold); if (nAmount <= 0) return; if (ps_WithdrawGold(ed.oPC, ed.nToken, nAmount)) GiveGoldToCreature(ed.oPC, nAmount); ps_UpdateGoldBinds(ed.oPC, ed.nToken); } else if (ed.sControlID == "btn_search") { SetLocalString(ed.oPC, PS_SEARCH_STRING, JsonGetString(NuiGetBind(ed.oPC, ed.nToken, "search"))); ps_UpdateItemList(ed.oPC); } else if (ed.sControlID == "btn_clear") { NuiSetBind(ed.oPC, ed.nToken, "search", JsonString("")); if (ps_GetUseSearchButton(ed.oPC)) { DeleteLocalString(ed.oPC, PS_SEARCH_STRING); ps_UpdateItemList(ed.oPC); } } } else if (ed.sEvent == "watch") { if (ed.sControlID == "geometry") { SetLocalJson(ed.oPC, PS_GEOMETRY, NuiGetBind(ed.oPC, ed.nToken, "geometry")); } else if (ed.sControlID == "selected") { json jSelected = JsonFind(NuiGetBind(ed.oPC, ed.nToken, ed.sControlID), JsonBool(TRUE)); NuiSetBind(ed.oPC, ed.nToken, "btn_withdraw", JsonBool(jSelected != JsonNull())); } else if (ed.sControlID == "search") { string sSearch = JsonGetString(NuiGetBind(ed.oPC, ed.nToken, "search")); int nSearch = GetStringLength(sSearch); NuiSetBind(ed.oPC, ed.nToken, "btn_clear", JsonBool(nSearch > 0)); if (ps_GetUseSearchButton(ed.oPC)) { NuiSetBind(ed.oPC, ed.nToken, "btn_search", JsonBool(nSearch > 0)); if (!nSearch) { DeleteLocalString(ed.oPC, PS_SEARCH_STRING); ps_UpdateItemList(ed.oPC); } } else { NuiSetBind(ed.oPC, ed.nToken, "btn_search", JsonBool(FALSE)); SetLocalString(ed.oPC, PS_SEARCH_STRING, sSearch); ps_UpdateItemList(ed.oPC); } } else if (ed.sControlID == "gold_amount") { string sAmount = JsonGetString(NuiGetBind(ed.oPC, ed.nToken, ed.sControlID)); if (!GetIsNumeric(sAmount)) { NuiSetBind(ed.oPC, ed.nToken, "btn_withdraw_gold", jFalse); NuiSetBind(ed.oPC, ed.nToken, "btn_deposit_gold", jFalse); NuiSetBind(ed.oPC, ed.nToken, "gold_amount_tooltip", JsonString("Error: Only Digits Allowed")); return; } NuiSetBind(ed.oPC, ed.nToken, "gold_amount_tooltip", JsonString("")); int nStored = JsonGetInt(NuiGetBind(ed.oPC, ed.nToken, "gold_stored")); int nAmount = clamp(StringToInt(sAmount), 0, max(nStored, GetGold(ed.oPC))); if (StringToInt(sAmount) > nAmount) NuiSetBind(ed.oPC, ed.nToken, ed.sControlID, JsonString(IntToString(nAmount))); ps_UpdateGoldBinds(ed.oPC, ed.nToken); } } else if (ed.sEvent == "open") ps_OnFormOpen(); else if (ed.sEvent == "close") ps_OnFormClose(); } void ps_CloseContainer(object oPC) { if (NUI_GetFormToken(oPC, FORM_ID)) NUI_CloseForm(oPC, FORM_ID); } /// @private Closes the form if the player moves too far away from the container. void ps_OnPCHeartbeat(object oPC, object oContainer) { object oLastContainer = GetLocalObject(oPC, PS_CONTAINER); if (oLastContainer == OBJECT_INVALID || oLastContainer != oContainer) return; if (GetObjectType(oContainer) == OBJECT_TYPE_ITEM) return; float fMax = ps_GetMaxDistance(oPC); if (fMax < 0.0) return; if (GetDistanceBetween(oLastContainer, oPC) > fMax) ps_CloseContainer(oPC); else AssignCommand(oPC, DelayCommand(2.0, ps_OnPCHeartbeat(oPC, oLastContainer))); } /// @private Closes the form for any using player that moves too far away from the /// container. void ps_OnContainerHeartbeat(object oContainer) { if (GetObjectType(oContainer) == OBJECT_TYPE_ITEM) return; float fMax = ps_GetLocalFloatOrDefault(oContainer, PS_DISTANCE, PS_DISTANCE_DEFAULT); if (fMax < 0.0) return; int nAccess = ps_GetLocalIntOrDefault(oContainer, PS_ACCESS_TYPE, PS_ACCESS_TYPE_DEFAULT); if (nAccess == PS_ACCESS_CONTENTIOUS) { int n; for (n; n < CountObjectList(oContainer, PS_USERS); n++) { object oPC = GetListObject(oContainer, n, PS_USERS); if (GetDistanceBetween(oContainer, oPC) > fMax) ps_CloseContainer(oPC); } } if (CountObjectList(oContainer, PS_USERS)) AssignCommand(oContainer, DelayCommand(2.0, ps_OnContainerHeartbeat(oContainer))); } void ps_OpenContainer(object oPC, object oContainer = OBJECT_INVALID) { //ps_OnFormClose(); if (oContainer == OBJECT_INVALID) oContainer = oPC == OBJECT_SELF ? GetLocalObject(oPC, NUI_OBJECT) : OBJECT_SELF; SetLocalObject(oPC, PS_CONTAINER, oContainer); if (ps_GetAccessType(oPC) == PS_ACCESS_CONTENTIOUS) AddListObject(oContainer, oPC, PS_USERS, TRUE); if (GetObjectType(oContainer) != OBJECT_TYPE_ITEM) { DelayCommand(2.0, ps_OnPCHeartbeat(oPC, oContainer)); DelayCommand(2.0, ps_OnContainerHeartbeat(oContainer)); } NUI_DisplayForm(oPC, FORM_ID, ps_GetMaxGold(oPC) > -2 ? "default" : "noGold"); } void HandleModuleEvents() { object oPC = OBJECT_SELF; switch (GetCurrentlyRunningEvent()) { case EVENT_SCRIPT_MODULE_ON_PLAYER_TARGET: { object oPC = GetLastPlayerToSelectTarget(); if (!GetLocalInt(oPC, PS_TARGETING_MODE) || NuiFindWindow(oPC, FORM_ID) == 0) return; ps_DepositItem(oPC, GetTargetingModeSelectedObject()); } break; case EVENT_SCRIPT_PLACEABLE_ON_USED: if (!GetIsPC(oPC)) oPC = GetLastUsedBy(); case EVENT_SCRIPT_PLACEABLE_ON_OPEN: if (!GetIsPC(oPC)) oPC = GetLastOpenedBy(); ps_OpenContainer(oPC); break; case EVENT_SCRIPT_PLACEABLE_ON_CLOSED: if (!GetIsPC(OBJECT_SELF)) oPC = GetLastClosedBy(); ps_CloseContainer(oPC); break; case EVENT_SCRIPT_MODULE_ON_ACTIVATE_ITEM: ps_OpenContainer(GetItemActivator(), GetItemActivated()); break; default: } }