Initial commit
Adding all of the current content for Anphillia Unlimited.
This commit is contained in:
427
_module/nss/pinv_inc.nss
Normal file
427
_module/nss/pinv_inc.nss
Normal file
@@ -0,0 +1,427 @@
|
||||
//void main(){}
|
||||
//::///////////////////////////////////////////////
|
||||
//:: Persistent Inventory Include Script. version 1.0
|
||||
//:: 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.
|
||||
|
||||
notes:
|
||||
|
||||
you can also add full "containers" into a chest. But this will cause a small hassle
|
||||
caused by the engine's way of handling inventories. Once you placed your container and
|
||||
close the chest, then its inventory will swap over to the chest (you will not loose anything).
|
||||
after you re-open, all the items from the container will now be part of the chests inventory and
|
||||
the container will be empty. it's like you would pour the container content into the chest...
|
||||
|
||||
i could do a workaround, but it would involve some complex scripting and a more complex database
|
||||
structure. of course, it would be slower... so i think i won't do it because it's not worth the hassle
|
||||
and no big deal.
|
||||
|
||||
btw, it is now possible to store ANY item, no more restrictions thanks to v1.30 patch
|
||||
|
||||
"locking" a chest the way i do it disables the "close" button. you can still easily close
|
||||
the container just by walking a bit away from it. this is a small hassle, but a container can't
|
||||
be opened by two people at once now.
|
||||
|
||||
--- exploit fix ----
|
||||
|
||||
action-spam exploit which works like this:
|
||||
|
||||
PC moves some meters away from the chest and starts left-clicking it in a rapid manner while the PC
|
||||
starts to move towards it. After the arrival, his action queue will spam the chest:
|
||||
|
||||
OnUse/OnOpen/OnClose fire in a very rapid manner and the chest will get "stuck open" most of the time,
|
||||
not firing OnClose/OnOpen anymore.
|
||||
|
||||
this will not only spork many scripts, it will also render this chest unusable. this can lead to bad stuff.
|
||||
|
||||
i worked heavily on a fix and got something ready. it's not a clever one, it's a brute force one.
|
||||
but it works 100% of the time.
|
||||
|
||||
one small hassle:
|
||||
your chest/placeable needs to be present on the palette.
|
||||
you have to create it beforehand with all scripts and stuff, then dropping it to your map.
|
||||
it won't work if you modified your chest after you dropped it to the map.
|
||||
|
||||
|
||||
changelog:
|
||||
|
||||
v 1.1
|
||||
|
||||
- added dynamic watchdog to scan for "Stuck Open" chests (applied to pcnt_open)
|
||||
- added various exploit fixes (applied to pcnt_open, pcnt_close)
|
||||
- fixed two bugs (thanks for benton for the headsup):
|
||||
1. DestroyInventory() did not delete cache
|
||||
2. forgot to use correct database name in case cache was deactivated
|
||||
|
||||
v 1.0 (first final)
|
||||
|
||||
- added a floating text message "Please walk away to close container" while the player
|
||||
opens a persistent container.
|
||||
|
||||
- fixed container "lock" mechanism. it should now correctly stop other players
|
||||
from opening an already busy container.
|
||||
|
||||
v 0.6
|
||||
|
||||
- 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
|
||||
const 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)
|
||||
const string SECURE_DELIMITER = "#";
|
||||
|
||||
// everything not in here gets considered an illegal character
|
||||
// - mixed up for additional security
|
||||
const string HASH_INDEX = "#i!j$k%l{&M/n(o)p=q?r^Xs`Tu'v]AwBxCyDzE1F2-G3t;4I}5Y:J6_K7+Z[Lm9N\ l0kOjPhQ,gRfSeHdU8cVbWa.";
|
||||
|
||||
const int HASH_PRIME = 3021377;
|
||||
|
||||
// database filename
|
||||
// change to your own needs
|
||||
const string DB_NAME = "DB_CONTAINER";
|
||||
|
||||
// --------------------------- 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)
|
||||
{
|
||||
if (USE_CACHE) DeleteLocalString(GetModule(),"DB_INV_CACHE"+IntToString(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;
|
||||
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_NAME,IntToString(nID), sItems);
|
||||
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
// converts item into string (serialize)
|
||||
// resref + stack + charges + 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)
|
||||
{
|
||||
// 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));
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user