375 lines
14 KiB
Plaintext
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);
|
|
}
|