RoT2_PRC8/_module/nss/nui_f_storage.nss
Jaysyn904 379c5dce03 Added persistent player storage
Added persistent player storage.  Fixed store items.  Full compile.  Updated release archive.
2025-03-09 20:14:36 -04:00

1148 lines
41 KiB
Plaintext

/// ----------------------------------------------------------------------------
/// @file nui_f_storage.nss
/// @author Ed Burke (tinygiant98) <af.hog.pilot@gmail.com>
/// @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 = "*<c???>*</c>*";
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:
}
}