openclonk/planet/Objects.ocd/Libraries.ocd/Inventory.ocd/Script.c

564 lines
15 KiB
C

/**
Inventory
Functions to handle a multi-slot, multi-hand inventory.
*/
/*
The inventory management:
The objects in the inventory are saved (parallel to Contents()) in the
array 'inventory'. They are accessed via GetItem(i) and GetItemPos(obj).
Other properties are MaxContentsCount (defines the maximum number of
contents)
Furthermore the clonk has a defined amount of "hands", defined by local HandObjects.
The array 'use_objects' is a mapping of "hands" onto the inventory-slots.
The functions GetHandItem(i) returns the object in the "i"th hand.
used properties:
this.inventory.objects: items in the inventory, array
this.inventory.hand_objects: items in the hands, array
this.inventory.disableautosort: used to get default-Collection-behaviour (see Collection2)
this.inventory.force_collection: used to pick stuff up, even though the hand-slots are all full (see RejectCollect + Collect with CON_Collect)
*/
/* Item limit */
local MaxContentsCount = 10; // Size of the inventory
local HandObjects = 1; // Amount of hands to select items
func Construction()
{
if(this.inventory == nil)
this.inventory = {};
this.inventory.objects = [];
this.inventory.disableautosort = false;
this.inventory.hand_objects = [];
this.inventory.force_collection = false;
for(var i=0; i < HandObjects; i++)
this.inventory.hand_objects[i] = i;
return _inherited(...);
}
/** Get the 'i'th item in the inventory */
public func GetItem(int i)
{
if (i >= GetLength(this.inventory.objects))
return nil;
if (i < 0) return nil;
return this.inventory.objects[i];
}
/** Returns all items in the inventory */
public func GetItems()
{
var inv = this.inventory.objects[:];
RemoveHoles(inv);
return inv;
}
/** Returns how many items are in the clonks inventory
Does not have to be the same as ContentCounts() because of objects with special handling, like CarryHeavy */
public func GetItemCount()
{
var count = 0;
for(var i=0; i < GetLength(this.inventory.objects); i++)
if(this.inventory.objects[i])
count++;
return count;
}
// This function was useful back when the clonk had two usable hand items.
// Feel free to uncomment whenever this obsolete control style is reinvented.
/** Get the 'i'th item in hands.
These are the items that will be used with use-commands. (Left mouse click, etc...) */
/*public func GetHandItem(int i)
{
// i is valid range
if (i >= GetLength(this.inventory.hand_objects))
return nil;
if (i < 0) return nil;
return GetItem(this.inventory.hand_objects[i]);
}*/
// 0: returns currently selected inventory item
// 1: returns item in quick slot
public func GetHandItem(int i)
{
// Range exceeded
if (i < 0 || i >= 2) return nil;
if (i == 0)
return GetItem(this.inventory.hand_objects[0]);
if (i == 1)
{
var slot = this->~GetQuickSwitchSlot();
if (slot < 0 || slot >= this.MaxContentsCount) return nil;
// Don't show the same item twice
if (slot == this.inventory.hand_objects[0]) return nil;
return GetItem(slot);
}
// no more than 2 hands
return nil;
}
/** Set the 'hand'th use-item to the 'inv'th slot */
public func SetHandItemPos(int hand, int inv)
{
// indices are in range?
if(hand >= HandObjects || inv >= MaxContentsCount)
return nil;
if(hand < 0 || inv < 0) return nil;
// no slot change?
if (inv == GetHandItemPos(hand)) return nil;
// the current hand object needs to be both notified and asked
var old_object = GetHandItem(hand);
if (old_object && old_object->~QueryRejectDeselection(this, hand))
return nil;
// when we want to swap placed with another hand, the other object also has to be asked
var hand2 = GetHandPosByItemPos(inv);
var old_hand2_object = nil;
if (hand2 != nil)
{
old_hand2_object = GetHandItem(hand2);
if (old_hand2_object && old_hand2_object->~QueryRejectDeselection(this, hand2)) return nil;
}
// changing slots cancels using, if the slot with the used object is contained
if(this.control.current_object) // declared in ClonkControl.ocd
{
var used_slot = GetItemPos(this.control.current_object);
if(used_slot != nil)
if(used_slot == GetHandItemPos(hand) || used_slot == inv)
this->~CancelUseControl(0,0);
}
// If the item is already selected, we can't hold it in another one too.
if(hand2 != nil)
{
// switch places
this.inventory.hand_objects[hand2] = this.inventory.hand_objects[hand];
this.inventory.hand_objects[hand] = inv;
// additional callbacks
if (old_hand2_object)
old_hand2_object->~Deselection(this, hand2);
if (old_object)
old_object->~Deselection(this, hand);
// notify new hand2 item (which should be old hand-item) that it has been selected
var hand_item;
if (hand_item = GetHandItem(hand2))
{
this->~OnSlotFull(hand2);
// OnSlotFull might have done something to the item
if(GetHandItem(hand2) == hand_item)
hand_item->~Selection(this, hand2);
}
else
this->~OnSlotEmpty(hand2);
}
else
{
this.inventory.hand_objects[hand] = inv;
// notify the old object that it was already deselected
if (old_object)
old_object->~Deselection(this, hand);
}
// notify the new item that it was selected
var item;
if(item = GetItem(inv))
{
this->~OnSlotFull(hand);
// OnSlotFull might have done something to the item
if(GetItem(inv) == item)
GetItem(inv)->~Selection(this, hand);
}
else
{
this->~OnSlotEmpty(hand);
}
}
/** Returns the position in the inventory of the 'i'th use item */
public func GetHandItemPos(int i)
{
if (i >= GetLength(this.inventory.hand_objects))
return nil;
if (i < 0) return nil;
return this.inventory.hand_objects[i];
}
/** Returns in which hand-slot the inventory-slot is */
private func GetHandPosByItemPos(int o) // sorry for the horribly long name --boni
{
for(var i=0; i < GetLength(this.inventory.hand_objects); i++)
if(this.inventory.hand_objects[i] == o)
return i;
return nil;
}
/** Drops the item in the inventory slot, if any */
public func DropInventoryItem(int slot)
{
var obj = GetItem(slot);
if(!obj || obj->~QueryRejectDeparture(this))
return nil;
// Notify other libraries of deliberate drop.
this->~OnDropped(obj);
// And make the engine drop the object.
this->AddCommand("Drop",obj);
}
/** Search for the index of an item */
public func GetItemPos(object item)
{
if (item)
if (item->Contained() == this)
{
var i = 0;
for(var obj in this.inventory.objects)
{
if (obj == item) return i;
++i;
}
}
return nil;
}
/** Switch two items in the clonk's inventory */
public func Switch2Items(int one, int two)
{
// no valid inventory index: cancel
if (!Inside(one,0,MaxContentsCount-1)) return;
if (!Inside(two,0,MaxContentsCount-1)) return;
// switch them around
var temp = this.inventory.objects[one];
this.inventory.objects[one] = this.inventory.objects[two];
this.inventory.objects[two] = temp;
// callbacks: cancel use, variable declared in ClonkControl.ocd
if (this.control.current_object == this.inventory.objects[one] || this.control.current_object == this.inventory.objects[two])
this->~CancelUse();
var handone, handtwo;
handone = GetHandPosByItemPos(one);
handtwo = GetHandPosByItemPos(two);
// callbacks: (de)selection
if (handone != nil)
if (this.inventory.objects[two]) this.inventory.objects[two]->~Deselection(this,one);
if (handtwo != nil)
if (this.inventory.objects[one]) this.inventory.objects[one]->~Deselection(this,two);
if (handone != nil)
if (this.inventory.objects[one]) this.inventory.objects[one]->~Selection(this,one);
if (handtwo != nil)
if (this.inventory.objects[two]) this.inventory.objects[two]->~Selection(this,two);
// callbacks: to self, for HUD
if (handone != nil)
{
if (this.inventory.objects[one])
this->~OnSlotFull(handone);
else
this->~OnSlotEmpty(handone);
}
if (handtwo != nil)
{
if (this.inventory.objects[two])
this->~OnSlotFull(handtwo);
else
this->~OnSlotEmpty(handtwo);
}
this->~OnInventoryChange(one, two);
this->~UpdateAttach();
}
/* Overload of Collect function
Allows inventory/hands-Handling with forced-collection
*/
public func Collect(object item, bool ignoreOCF, int pos, bool force)
{
// Whenever a script calls the Collect function manually, an intended force is assumed.
// That means, items will usually be collected with Collect() even if the current hand-slot is not free.
force = force ?? true;
this.inventory.force_collection = force;
var success = false;
if (pos == nil || item->~IsCarryHeavy())
{
success = _inherited(item,ignoreOCF);
this.inventory.force_collection = false;
return success;
}
// fail if the specified slot is full
if (GetItem(pos) == nil && pos >= 0 && pos < MaxContentsCount)
{
if (item)
{
this.inventory.disableautosort = true;
// collect but do not sort in_
// Collection2 will be called which attempts to automatically sort in
// the collected item into the next free inventory slot. Since 'pos'
// is given as a parameter, we don't want that to happen and sort it
// in manually afterwards
var success = _inherited(item);
this.inventory.disableautosort = false;
if (success)
{
this.inventory.objects[pos] = item;
var handpos = GetHandPosByItemPos(pos);
// if the slot was a selected hand slot -> update it
if(handpos != nil)
{
this->~OnSlotFull(handpos);
}
}
}
}
this.inventory.force_collection = false;
return success;
}
protected func Collection2(object obj)
{
var sel = 0;
// See Collect()
if (this.inventory.disableautosort) return _inherited(obj,...);
var success = false;
var i;
// sort into selected hands if empty
for(i = 0; i < HandObjects; i++)
if(!GetHandItem(i))
{
sel = GetHandItemPos(i);
this.inventory.objects[sel] = obj;
success = true;
break;
}
// otherwise, first empty slot
if(!success)
{
for(var i = 0; i < MaxContentsCount; ++i)
{
if (!GetItem(i))
{
sel = i;
this.inventory.objects[sel] = obj;
success = true;
break;
}
}
}
// callbacks
if (success)
{
var handpos = GetHandPosByItemPos(sel);
// if the slot was a selected hand slot -> update it
if(handpos != nil)
{
this->~OnSlotFull(handpos);
// OnSlotFull might have done something to obj
if(GetHandItem(handpos) == obj)
obj->~Selection(this, handpos);
}
}
return _inherited(obj,...);
}
func Ejection(object obj)
{
// if an object leaves this object
// find obj in array and delete (cancel using too)
var i = 0;
var success = false;
for(var item in this.inventory.objects)
{
if (obj == item)
{
this.inventory.objects[i] = nil;
success = true;
break;
}
++i;
}
// variable declared in ClonkControl.ocd
if (this.control.current_object == obj) this->~CancelUse();
// callbacks
if (success)
{
var handpos = GetHandPosByItemPos(i);
// if the slot was a selected hand slot -> update it
if(handpos != nil)
{
this->~OnSlotEmpty(handpos);
obj->~Deselection(this, handpos);
}
this->~UpdateAttach();
}
// we have over-weight? Put the next unindexed object inside that slot
// this happens if the clonk is stuffed full with items he can not
// carry via Enter, CreateContents etc.
var inventory_count = 0;
for(var io in this.inventory.objects)
if(io != nil)
inventory_count++;
if (ContentsCount() > inventory_count && !GetItem(i))
{
for(var c = 0; c < ContentsCount(); ++c)
{
var o = Contents(c);
if (!o) continue; // safety in case callbacks delete some objects
if(o->~IsCarryHeavy())
continue;
if (GetItemPos(o) == nil)
{
// found it! Collect it properly
this.inventory.objects[i] = o;
var handpos = GetHandPosByItemPos(i);
// if the slot was a selected hand slot -> update it
if(handpos != nil)
{
this->~OnSlotFull(handpos);
// OnSlotFull might have done something to o
if(GetHandItem(handpos) == o)
o->~Selection(this, handpos);
}
break;
}
}
}
_inherited(obj,...);
}
func ContentsDestruction(object obj)
{
// tell the Hud that something changed
this->~OnInventoryChange();
_inherited(obj, ...);
}
protected func RejectCollect(id objid, object obj)
{
// collection of that object magically disabled?
if(GetEffect("NoCollection", obj)) return true;
// Only handle extra-slot objects if the object was not dropped on purpose.
if (this.inventory.force_collection)
{
// try to stuff obj into an object with an extra slot
for(var i=0; Contents(i); ++i)
if (Contents(i)->~HasExtraSlot())
if (!(Contents(i)->Contents(0)))
if (Contents(i)->Collect(obj,true))
return true;
// try to stuff an object in clonk into obj if it has an extra slot
if (obj->~HasExtraSlot())
if (!(obj->Contents(0)))
for(var i=0; Contents(i); ++i)
if (obj->Collect(Contents(i),true))
return false;
}
// Can't carry bucket material with bare hands.
if (obj->~IsBucketMaterial()) return true;
// check max contents. But do not count CarryHeavy towards contents.
var contents_count = this->ContentsCount();
var carry_heavy_obj = this->GetCarryHeavy();
if (carry_heavy_obj && carry_heavy_obj->Contained() == this) --contents_count;
if (contents_count >= MaxContentsCount) return true;
return _inherited(objid,obj,...);
}
public func GrabContents(object source, ...)
{
// Try to put grabbed items into same slot (for respawn)
if (source)
{
var i = source->ContentsCount();
while (--i >= 0)
{
var item = source->Contents(i);
if (item)
{
var item_pos = source->GetItemPos(item);
// Collect this into same slot index if it's a valid, free slot for this object
if (GetType(item_pos) && item_pos >=0 && item_pos < MaxContentsCount && !GetItem(item_pos))
{
Collect(item, true, item_pos);
}
}
}
}
// Grab remaining items
return inherited(source, ...);
}
/* Updates the maximum number of carryable items by this clonk. */
public func SetMaxContentsCount(int new_count)
{
// Update limit
MaxContentsCount = new_count;
// Make sure hands aren't beyond inventory
if (MaxContentsCount)
{
for(var i=0; i < HandObjects; i++)
{
if (GetHandItemPos(i) >= MaxContentsCount)
{
var new_slot = 0;
while (GetIndexOf(this.inventory.hand_objects, new_slot) >= 0) ++new_slot;
if (new_slot >= MaxContentsCount) new_slot = 0;
SetHandItemPos(i, new_slot);
}
}
}
// Update GUI
this->~OnInventoryChange();
return true;
}
/* Objects with inventory (i.e. clonks) do not allow taking out items by
* hostile clonks. Actively controlled Clonks never allow taking out items.
* Also do not allow for neutral, because that will affect NPCs.
*/
public func RejectInteractionMenu(object clonk, ...)
{
var msg = _inherited(clonk, ...);
if (clonk != this && !msg)
{
if (GetOwner() == NO_OWNER || Hostile(GetOwner(), clonk->GetOwner()) || GetCursor(GetOwner()) == this)
return Format("$MsgNoInteraction$", GetName());
}
return msg;
}