ff_u_store_inf
Destroy items sold to a store that are cursed, or that it already has in infinite quantity or in several copies.
When a player sells an item to a store, the item is destroyed once the sale is done if:
the store already holds an item with the infinite flag set, the same tag and the same permanent item properties. Nothing else is compared (local variables, stack size, cost, plot flag, …);
the item has the “Cursed” additional item property or the cursed flag;
the store already holds at least
MAX_COPIES(3) other items with the same tag and the same permanent item properties.
Variables
ENABLE__FF_U_STORE_INF: (string) (opt-out) Set to "N" on module to disable the unit.
Source code
// @code
#include "ff_i_core"
const string ENABLE__FF_U_STORE_INF = "ENABLE__FF_U_STORE_INF";
// A sold item is destroyed if the store already holds this many copies of it.
const int MAX_COPIES = 3;
// -----------------------------------------------------------------------------
//! @brief Return TRUE if oItem is cursed: either the "Cursed" additional item
//! property, or the cursed flag set in the toolset.
//! @param oItem An item.
int GetIsCursed(object oItem);
int GetIsCursed(object oItem)
{
if (GetItemCursedFlag(oItem))
return TRUE;
itemproperty ip = GetFirstItemProperty(oItem);
while (GetIsItemPropertyValid(ip))
{
// The "Cursed" value is stored in the subtype or the cost table value,
// depending on how the property was created.
if (GetItemPropertyType(ip) == ITEM_PROPERTY_ADDITIONAL
&& (GetItemPropertySubType(ip) == IP_CONST_ADDITIONAL_CURSED
|| GetItemPropertyCostTableValue(ip) == IP_CONST_ADDITIONAL_CURSED))
return TRUE;
ip = GetNextItemProperty(oItem);
}
return FALSE;
}
// -----------------------------------------------------------------------------
//! @brief Return the permanent item properties of oItem, one string per property.
//! @param oItem An item.
//! @return JSON array of "type:subtype:costvalue:param1value" strings.
json GetItemPropertyList(object oItem);
json GetItemPropertyList(object oItem)
{
json jList = JsonArray();
itemproperty ip = GetFirstItemProperty(oItem);
while (GetIsItemPropertyValid(ip))
{
if (GetItemPropertyDurationType(ip) == DURATION_TYPE_PERMANENT)
{
string sProperty = IntToString(GetItemPropertyType(ip))
+ ":" + IntToString(GetItemPropertySubType(ip))
+ ":" + IntToString(GetItemPropertyCostTableValue(ip))
+ ":" + IntToString(GetItemPropertyParam1Value(ip));
jList = JsonArrayInsert(jList, JsonString(sProperty));
}
ip = GetNextItemProperty(oItem);
}
return jList;
}
// -----------------------------------------------------------------------------
//! @brief Return TRUE if jA and jB hold the same item properties, in any order.
//! @param jA A list from GetItemPropertyList().
//! @param jB A list from GetItemPropertyList().
int GetIsSamePropertyList(json jA, json jB);
int GetIsSamePropertyList(json jA, json jB)
{
int nLength = JsonGetLength(jA);
if (nLength != JsonGetLength(jB))
return FALSE;
int i;
for (i = 0; i < nLength; i++)
{
json jIndex = JsonFind(jB, JsonArrayGet(jA, i));
if (JsonGetType(jIndex) != JSON_TYPE_INTEGER)
return FALSE;
// Remove the match so a duplicated property must be matched twice
jB = JsonArrayDel(jB, JsonGetInt(jIndex));
}
return TRUE;
}
// -----------------------------------------------------------------------------
//! @brief Return TRUE if oStore holds an infinite item with the tag and the
//! permanent item properties of oItem.
//! @param oStore A store.
//! @param oItem The item sold to oStore.
int GetHasInfiniteCopy(object oStore, object oItem);
int GetHasInfiniteCopy(object oStore, object oItem)
{
string sTag = GetTag(oItem);
json jProperties = JsonNull();
object oOther = GetFirstItemInInventory(oStore);
while (GetIsObjectValid(oOther))
{
// Cheap check first, the properties are only listed when the tag matches
if (oOther != oItem && GetInfiniteFlag(oOther) && GetTag(oOther) == sTag)
{
if (JsonGetType(jProperties) == JSON_TYPE_NULL)
jProperties = GetItemPropertyList(oItem);
if (GetIsSamePropertyList(jProperties, GetItemPropertyList(oOther)))
return TRUE;
}
oOther = GetNextItemInInventory(oStore);
}
return FALSE;
}
// -----------------------------------------------------------------------------
//! @brief Return the number of items in oStore, other than oItem, with the tag
//! and the permanent item properties of oItem. Counting stops at nMax.
//! @param oStore A store.
//! @param oItem The item sold to oStore.
//! @param nMax Count at which to stop.
int CountCopies(object oStore, object oItem, int nMax);
int CountCopies(object oStore, object oItem, int nMax)
{
string sTag = GetTag(oItem);
json jProperties = JsonNull();
int nCount = 0;
object oOther = GetFirstItemInInventory(oStore);
while (GetIsObjectValid(oOther) && nCount < nMax)
{
if (oOther != oItem && GetTag(oOther) == sTag)
{
if (JsonGetType(jProperties) == JSON_TYPE_NULL)
jProperties = GetItemPropertyList(oItem);
if (GetIsSamePropertyList(jProperties, GetItemPropertyList(oOther)))
nCount++;
}
oOther = GetNextItemInInventory(oStore);
}
return nCount;
}
// -----------------------------------------------------------------------------
//! @brief Destroy oItem, just sold to oStore, if it is cursed, or if oStore
//! already has it in infinite quantity or MAX_COPIES times.
//! @param oStore The store.
//! @param oItem The sold item.
void OnItemSold(object oStore, object oItem);
void OnItemSold(object oStore, object oItem)
{
if (!GetIsObjectValid(oStore) || !GetIsObjectValid(oItem))
return;
string sReason = "";
if (GetIsCursed(oItem))
sReason = "cursed";
else if (GetHasInfiniteCopy(oStore, oItem))
sReason = "already infinite";
else if (CountCopies(oStore, oItem, MAX_COPIES) >= MAX_COPIES)
sReason = "already " + IntToString(MAX_COPIES) + " copies";
else
return;
LogInfo(__FILE__ + ": Removal of " + GetObjectInfo(oItem) + " sold to " + GetTag(oStore) + " (" + sReason + ")");
SetPlotFlag(oItem, FALSE);
DestroyObject(oItem);
}
// -----------------------------------------------------------------------------
void main()
{
string sEvent = NWNX_Events_GetCurrentEvent();
if (sEvent == ON_REGISTER)
{
if (!GetModuleFlag(ENABLE__FF_U_STORE_INF, TRUE))
return;
SubscribeToEvent(NWNX_ON_STORE_REQUEST_SELL_AFTER, __FILE__);
}
else if (sEvent == NWNX_ON_STORE_REQUEST_SELL_AFTER)
{
if (!StringToInt(NWNX_Events_GetEventData("RESULT")))
return;
object oStore = StringToObject(NWNX_Events_GetEventData("STORE"));
object oItem = StringToObject(NWNX_Events_GetEventData("ITEM"));
OnItemSold(oStore, oItem);
}
}