//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>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); }