2024-06-20 15:47:42 -04:00

375 lines
14 KiB
Plaintext

//void main(){}
//::///////////////////////////////////////////////
//:: Persistent Inventory Include Script. version 0.6 (Beta)
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
this script helps you to store/retrieve inventory to/from the bioware database
it can serialize several items into a single padded string. you can then retrieve
this string and rebuild those items on any valid object inventory.
persistent containers are only one usage example.
changelog:
- charged items get now handled properly thanks to GetItemCharges/SetItemCharges
- increased item stack size to 5. you can now store any amount of gold.
one small restriction: you can't move more then 50000 gold at once. but you
can store any number of gold stacks. this takes container capacity into account.
e.g. container with max capacity of 10 can hold 500.000 gold (10 stacks of 50.000)
- added blocking
block specifc items by item type or tag filter via PINV_BlockItemByType(),PINV_BlockItemByFilter()
see PINV_open for usage example
- added auto caching.
container will only update to database if the inventory got manipulated.
reading cache will only read the inventory one time at first usage of PINV_RetrieveInventory().
storing of additional items will not issue a new read, just a single db write + readcache update.
this is much faster now...
- added hashing
IDs now get hashed. no more limits on area TAG length while using unique IDs
this also grants additional security.
- secure delimiter added
IDs that contain a secure delimiter character will get rejected
this should prevent any form of hijacking
- container now retains his sorting order through the use of LIFO (last in first out)
for inventory creation/storing.
- a closed chest is ALWAYS completely empty. this preserves lots of RAM on large modules
FAQ (under construction)
When do i use TAG IDs ? (ReflexSafe = 0)
if you have a moving container (like a creature) OR if you want
to share the same inventory between different containers.
When do i use unique IDs ? (ReflexSafe = 1)
use this kind of ID if your container is stationary. you won't
have any tag dependency this way.
When do i use player IDs ? (ReflexSafe = 2)
use this kind of ID if you want a player vault.
a container with this setting will retrieve inventory based
on the using player. this is the classical PC vault thingy...
*/
//:://////////////////////////////////////////////
//:: Created By: Knat
//:: Created On: 28.04.03
//:: Last Change: 14.06.03
//:://////////////////////////////////////////////
// --------------------------- GLOBALS -----------------------------------
//
// feel free to adjust them to fit your needs
// caching increases memory usage..
// if you don't wanna use caching, set this to FALSE
int USE_CACHE = TRUE;
// needed to prevent DB hijacking
// this char is forbidden in PC names
// PCs with this delimiter in their name won't be able
// to manipulate player vaults (ID_FLAG = 2)
string SECURE_DELIMITER = "#";
// everything not in here gets considered an illegal character
// - mixed up for additional security
string HASH_INDEX = "#i!j$k%l{&M/n(o)p=q?r^Xs`Tu'v]AwBxCyDz" +
"E1F2-G3t;4I}5Y:J6_K7+Z[Lm9N\ l0kOjPhQ,gRfSeHdU8cVbWa.";
int HASH_PRIME = 3021377;
// database filename
// change to your own needs
string DB_NAME = "DB_CONTAINER_"+GetTag(GetModule());
// --------------------------- IMPLEMENTATION ----------------------------
// converts item into string (serialize)
// padding gets used to make parsing more easy, should be faster then tokenizing it...
// this also gives us a fixed length. stringsize / 24 = number of items.
// makes cycling over each item easy and very fast. all items start at position (itemindex*20)
// e.g. 5th item in container starts at stringposition 110
// this way, accessing specific items is fast and does not need any searching/looping
string ItemToPaddedString(object oItem);
// converts string to item (de-serialize)
// oTarget = item receiver
object PaddedStringToItem(string sItem, object oTarget = OBJECT_SELF);
// return database ID from oTarget
// nIDType = 0
// non unique container ID. it will just use the TAG as container index
// multiple objects can share the same inventory this way
// nIDType = 1
// unique object ID. area-tag + location used as object index. each object gets
// his own unique inventory space, tag is irrelevant.
// object must be stationary !!!!
// nIDType = 2
// ID based on GetPCPlayerName() and GetName()
// object inventory depends on opener
// this is useful for player vaults
int PINV_GetID(object oTarget, int nIDType = 0);
// destroy persistent inventory
void PINV_DestroyInventory(int nID);
// store inventory to database
// only needs a single database call to store all items
// nID = database ID - you can retrieve that ID from any inventory object via PINV_GetID()
// oTarget = inventory object (e.g.: creature, placeable, merchant, player)
// oFail = items that can't be stored are given back to the oFail object
// (usually a player, but can be anything with an inventory)
// if this object is invalid = item gets destroyed
// nMaxCapacity = maximum inventory capacity
//
// function returns -1 if it can't generate an ID. this usually only happens
// if the target object is a player that has invalid characters in his name
// this should pretty much eliminate any DB hijacking
int PINV_StoreInventory(int nID, object oTarget = OBJECT_SELF, object oFail = OBJECT_INVALID, int nMaxCapacity = 50);
// retrieve inventory from database
// this functions reads an inventory string from the database and re-creates
// items on the specified target.
// only needs a single database call to retrieve all items
//
// nID = database ID - you can retrieve that ID from any inventory object via PINV_GetID()
// oTarget = inventory object (e.g.: creature, placeable, merchant, player)
//
// function returns -1 if it can't generate an ID. this usually only happens
// if the target object is a player that has invalid characters in his name
// this should pretty much eliminate any DB hijacking
int PINV_RetrieveInventory(int nID, object oTarget = OBJECT_SELF);
// forces oTarget to block items of nBaseItemType (BASE_ITEM_*)
void PINV_BlockItemByType(int nBaseItemType, object oTarget = OBJECT_SELF);
// forces oTarget to block items containing sTagFilter in their TAG
// e.g. "**NODROP**" will block items with NODROP anywhere in their tag
void PINV_BlockItemByFilter(string sTagFilter, object oTarget = OBJECT_SELF);
// placeholder
// this function will create an inventory string from a 2DA file
// will be very useful later for dynamic inventory management.
string PINV_GetInventoryFrom2DA(string s2DAFile, int nFirstRow, int nLastRow)
{
return "";
}
/* ----------------------------------------------------------------------- */
// simple hash
// returns -1 if string contains illegal character
int hash(string sData)
{
int nLen = GetStringLength(sData);
int i, nHash, nChar;
for(i=0;i<nLen;i++)
{
nChar = FindSubString(HASH_INDEX, GetSubString(sData,i,1));
if(nChar == -1) return -1;
nHash = ((nHash<<5) ^ (nHash>>27)) ^ nChar;
}
return nHash % HASH_PRIME;
}
// return database ID from oTarget
int PINV_GetID(object oTarget, int nIDType = 0)
{
string sID;
switch(nIDType)
{
case 0 :
sID = "T" + SECURE_DELIMITER + GetTag(oTarget);
break;
case 1 :
sID = "U" + GetTag(GetArea(oTarget)) + SECURE_DELIMITER + IntToString(FloatToInt(GetPosition(oTarget).x * 10)) +
IntToString(FloatToInt(GetPosition(oTarget).y * 10));
break;
case 2 :
// reject player names containing secure delimiter
if(FindSubString(GetPCPlayerName(oTarget),SECURE_DELIMITER) != -1 || FindSubString(GetName(oTarget),SECURE_DELIMITER) != -1)
return -1;
sID = "P" + GetPCPlayerName(oTarget) + SECURE_DELIMITER + GetName(oTarget);
break;
}
return hash(sID);
}
void PINV_DestroyInventory(int nID)
{
DeleteCampaignVariable(DB_NAME,IntToString(nID));
}
// read inventory from database with a single call
int PINV_RetrieveInventory(int nID, object oTarget = OBJECT_SELF)
{
if(nID != -1)
{
string sItems;
// database access gets reduced greatly if using the cache
if(USE_CACHE)
{
sItems = GetLocalString(GetModule(),"DB_INV_CACHE"+IntToString(nID));
// only reload on empty cache
if(sItems == "")
{
sItems = GetCampaignString(DB_NAME,IntToString(nID));
SetLocalString(GetModule(),"DB_INV_CACHE"+IntToString(nID),sItems);
}
}
else
sItems = GetCampaignString(DB_NAME,IntToString(nID));
if(sItems != "")
{
// build inventory
int i, nCount = (GetStringLength(sItems) / 24) - 1;
SendMessageToPC(GetFirstPC(),"id:" + IntToString(nID) + " count: "+IntToString(nCount)+ " items:"+sItems);
for(i=nCount;i>=0;i--) PaddedStringToItem(GetSubString(sItems,i*24,24),oTarget);
}
return 0;
}
else
return -1;
}
void PINV_SendErrorAndReturnItem(string sError, object oItem, object oReceiver, object oInventoryObject=OBJECT_SELF)
{
if(GetIsPC(oReceiver))
SendMessageToPC(oReceiver, GetName(oInventoryObject) + ": " + sError + ". returning item ("+GetName(oItem)+")...");
if(GetIsObjectValid(oReceiver))
AssignCommand(oReceiver, ActionTakeItem(oItem,oInventoryObject) );
else
DestroyObject(oItem);
}
int PINV_StoreInventory(int nID, object oTarget = OBJECT_SELF, object oFail = OBJECT_INVALID, int nMaxCapacity = 50)
{
if(nID != -1)
{
int i = 0;
string sItems, sDummy;
object oItem = GetFirstItemInInventory(OBJECT_SELF);
while(oItem != OBJECT_INVALID)
{
if(i < nMaxCapacity) // enough space left ?
{
// check blocking filters
if( GetLocalInt(oTarget,"BLOCK_TYPE#"+IntToString(GetBaseItemType(oItem))) ||
TestStringAgainstPattern( GetLocalString(oTarget,"BLOCK_TAG"), GetTag(oItem)))
{
PINV_SendErrorAndReturnItem("It's not allowed to store this", oItem, oFail);
}
else
{
// check for large stacks
if(GetNumStackedItems(oItem) > 50000)
{
PINV_SendErrorAndReturnItem("It's not allowed to store stacks of gold > 50000. Store your gold in several stacks of 50000 or less", oItem, oFail);
}
else
{
// serialize item
sDummy = ItemToPaddedString(oItem);
if(sDummy == "")
{
// invalid item
PINV_SendErrorAndReturnItem("Can't store this item because of empty tag AND resref", oItem, oFail);
}
else
{
// item passed all checks and gets stored
// add item to string, inc item counter and destroy item
i++; sItems += sDummy; DestroyObject(oItem);
}
}
}
}
else
{
// maximum capacity reached
PINV_SendErrorAndReturnItem("Container maximum capacity exceeded", oItem, oFail);
}
oItem = GetNextItemInInventory(oTarget);
}
// write inventory to database with a single call
// only update if inventory got changed
if(USE_CACHE)
{
if(GetLocalString(GetModule(),"DB_INV_CACHE"+IntToString(nID)) != sItems)
{
SetCampaignString(DB_NAME,IntToString(nID), sItems);
SetLocalString(GetModule(),"DB_INV_CACHE"+IntToString(nID),sItems);
}
}
else
SetCampaignString("DB_CONTAINER_"+GetTag(GetModule()),IntToString(nID), sItems);
return 0;
}
else
return -1;
}
// converts item into string (serialize)
// resref + stack + identified flag
string ItemToPaddedString(object oItem)
{
string sItem = GetResRef(oItem);
// blank resref ? probably merchant-bug.. , try tag instead (item resref/tag must match otherwise it won't work)
sItem = (sItem == "") ? GetStringLowerCase(GetTag(oItem)) : sItem;
if(sItem == "") return "";
// pad to 16 & add stack
sItem = (GetStringLength(sItem) < 16) ? sItem + GetStringLeft(" ",16 - GetStringLength(sItem)) : GetStringLeft(sItem,16);
sItem += IntToString(GetNumStackedItems(oItem));
// pad to 21 & add number of charges
sItem = (GetStringLength(sItem) < 21) ? sItem + GetStringLeft(" ",21 - GetStringLength(sItem)) : GetStringLeft(sItem,21);
sItem += IntToString(GetItemCharges(oItem));
// pad to 23 & add identified flag
if (GetStringLength(sItem) < 23) sItem += " ";
return sItem + IntToString(GetIdentified(oItem));
}
// converts string to item (de-serialize)
object PaddedStringToItem(string sItem, object oTarget = OBJECT_SELF)
{
SendMessageToPC(GetFirstPC(),"Item: "+sItem);
// check for and remove any padding
// create item with nStackSize from DB
int nPad = FindSubString(GetStringLeft(sItem,16)," ");
string sRef = (nPad != -1) ? GetStringLeft(sItem,nPad) : GetStringLeft(sItem,16);
int nStack = StringToInt(GetSubString(sItem, 16,5));
int nCharges = StringToInt(GetSubString(sItem, 21,2));
SendMessageToPC(GetFirstPC(),"Charges: "+IntToString(nCharges));
object oItem = CreateItemOnObject(sRef, oTarget, nStack);
// set identified from DB
if(oItem != OBJECT_INVALID) SetIdentified(oItem, StringToInt(GetStringRight(sItem,1)));
// set charges
if(nCharges > 0) SetItemCharges(oItem, nCharges);
return oItem;
}
void PINV_BlockItemByType(int nBaseItemType, object oTarget = OBJECT_SELF)
{
SetLocalInt(oTarget,"BLOCK_TYPE#"+IntToString(nBaseItemType),TRUE);
}
void PINV_BlockItemByFilter(string sTagFilter, object oTarget = OBJECT_SELF)
{
SetLocalString(oTarget,"BLOCK_TAG",sTagFilter);
}