It's something that surely lurks in the codearchives, but since there's no search...Here i repost mordy's inventory system:
; MORDYSLOTS
; Baldur's Gate style inventory lib for Blitz Basic
; written by morduun / morduun@... / 10.19.2003
; use freely; credit'd be nice but I'll settle for a copy of yer game :D
;
; ITEM TYPES
;-------------------------------------------------------------------------------
; I use these as ITEM\VARIANT and SLOT\FLAGS
; and check that an AND match between the two returns true
; this allows you to have inventory slots for only certain items
Const ITEM_HEAD = %0000000001 ; generally caps & helms
Const ITEM_NECK = %0000000010 ; generally amulets/necklaces
Const ITEM_BACK = %0000000100 ; generally cloaks
Const ITEM_QUVR = %0000001000
Const ITEM_LHAND = %0000010000 ; generally shields
Const ITEM_RHAND = %0000100000 ; generally weapons
Const ITEM_BHAND = %0001000000 ; generally gauntlets & gloves
Const ITEM_FING = %0010000000 ; for rings
Const ITEM_BODY = %0100000000 ; armor/ clothes
Const ITEM_FEET = %1000000000 ; boots!
Const ITEM_PACK = %1111111111 ; general inventory -- anything goes here
; simple type def for location of sprites onscreen
Type coord
Field x
Field y
End Type
; type def for items as they exist in data. separate from visual definition (chit),
; tho it links to its current chit template.
Type item
Field variant ; what kind of object? weapon, armor, etc.
Field ID$ ; name of object
Field active ; currently wielded or worn
Field loc.coord ; where it is
Field stackable ; is it stackable?
Field quantity ; allows stacking
Field inner.inventory ; allows an object to have an inventory
Field current.inventory ; what inventory is this object part of now?
Field display.chit ; what we look like in inventory
Field tpl.ChitTemplate ; our current display type instance (if any)
Field curslot$ ; slot ID of this item for display
End Type
; type def for inventories as they exist in data. separate from visual representation (slot)
; links to a 'slotmap' that allows the program to create an inventory visually.
Type inventory
Field max ; how many max inventory items in this container
Field parent.item ; if this inventory lies inside an item, which one? (allows sacks)
Field myPack ; reference to the bank that holds all inventory instances
Field SlotMap ; bank of references to slot handles (same order as myPack)
End Type
; type def for an individual slot for an item in an inventory. both data and visual representation.
; links to parent inventory and any currently held chit.
Type Slot
Field ID$ ; for storage/retrieval
Field image ; what image to use
Field frame ; what image frame to use
Field loc.coord ; x/y location
Field holds.chit ; what chit it's holding
Field parent.inventory ; this way we can delete em!
Field visible ; show it or not -- it's a cheat but I like it >:)
Field flags ; these AND the item type must be true
End Type
; type def for an item as it exists on the screen only. connects to its parent item description
; (which in turn links to its template) as well as its current "home" inventory slot (if any)
Type Chit
Field loc.coord ; what its current coord set is
Field home.slot ; where it belongs, currently (if anywhere)
Field linked.item ; what the actual item is
End Type
; type def for an object display item. These are not actually displayed as they exist, but linked to
; from chit object instances in order to display those instances properly.
Type ChitTemplate
Field ID$ ; english name of template type
Field image ; what image to reference
Field frame ; what frame of that image
End Type
; necessary under this methodology, unfortunately. Tells the code what chit, if any, is currently
; being carted around by the mouse.
Global ChitActive.chit
; ---------------------------------------------------------
; CODE SPECIFIC TO THIS DEMO STARTS HERE
; ---------------------------------------------------------
; generic initialization crap
Graphics 640, 480, 0
SetBuffer BackBuffer()
SeedRnd MilliSecs()
; load in the slot graphic
mySlot = LoadImage("slot.png")
MaskImage mySlot, 255, 0, 255
; load in the object graphic
Global myItems = LoadAnimImage("inv.png", 32, 32, 0, 90)
MaskImage myItems, 255, 0, 255
; load in the mouse graphic
Global mouse = LoadImage("mouse.png")
MaskImage mouse, 255, 255, 255
; create two inventories; one represents the ground, one represents the PC's pack
myPC.inventory = CreateInventory(10, Null) ; gives the PC a pack with 10 slots
theGround.inventory = CreateInventory(10, Null) ; gives the ground 10 item slots
; Create 10 slots for each of those inventories.
; this demo doesn't take advantage of the cool slot flags or item variants
; but you could easily tinker with this routine to do so.
For i = 1 To 10
myContainer.slot = CreateSlot("pack", mySlot, i*40, 10, myPC, ITEM_PACK)
myContainer.slot= CreateSlot("ground", mySlot, i*40, 150, theGround, ITEM_PACK)
Next
; Create three chit templates: the sword, bow and arrows.
myTpl1.ChitTemplate = CreateChitTemplate("Hero Sword", myItems, 49)
myTpl2.ChitTemplate = CreateChitTemplate("Hero Bow", myItems, 79)
myTpl3.ChitTemplate = CreateChitTemplate("Arrows", myItems, 17)
; Create three actual items using those templates.
mySword.item = CreateItem(ITEM_RHAND, "Hero Sword", 0, 0)
myBow.item = CreateItem(ITEM_RHAND, "Hero Bow", 0, 0)
treasure.item = CreateItem(ITEM_QUVR, "Arrows", 5, 5, True, 50)
; add those items to their respective inventories
AddItemToInv(mySword, myPC, "pack", False, True)
AddItemToInv(myBow, myPC, "pack", False, True)
AddItemToInv(treasure, theGround, "ground", False, True)
; annnnnd show the inventories to the player (inventories start off hidden)
ShowInventory(theGround)
ShowInventory(myPC)
; generic game loop here
; Calls to DrawSlots and DoChits are required in your main loop
; Also, if you want to draw your mouse pointer properly (ie, with attached inventory)
; you'll need to do the ChitActive check as well
; DispInv is only the text version I hacked for debugging, so you can safely remove it
; ------------------------------------------------------------------------------------
While Not KeyDown(1)
Cls
; do checks on inventory item grabs
; this works for this demo, but the important thing is that the DrawSlots routine
; must know whether a mouse hit has occured lately, and the value you pass that routine
; is true or false depending on that event.
If MouseHit(1)
CheckFlag = True
Else
CheckFlag = False
EndIf
; draw slots & do checks
CheckFlag = DrawSlots(checkflag)
; draw chits & do checks
DoChits(checkflag)
; draw the mouse, if necessary
If ChitActive = Null
DrawImage mouse, MouseX(), MouseY()
EndIf
; display the text version of each inventory
; unnecessary for the pure-graphic version, but this is a nice confirmation
; that the data internally is lining up with the visuals externally
DispInv(myPC, 0, 300)
DispInv(theGround, 320, 300)
; add a random bunch of arrows to the ground
If KeyHit(2)
aTreasure.item = CreateItem(ITEM_QUVR, "Arrows", 5, 5, True, Rand(1, 100))
checkme.chit = AddItemToInv(aTreasure, theGround, "ground", False, True)
EndIf
If KeyHit(57) ; spacebar toggles player inventory
ToggleInventory(myPC)
EndIf
Text 0, 465, "[1] to add arrows to lower inventory; [SPACE] to hide upper inventory"
Flip True
Delay 10
Wend
End
; ---------------------------------------------------------
; CODE SPECIFIC TO THIS DEMO STARTS HERE
; ---------------------------------------------------------
; displays text version of the inventory (for debugging)
Function DispInv(inv.inventory, x, y)
For i = 0 To inv\max - 1
pointer = i * 4
myHandle = PeekInt(inv\myPack, pointer)
If myHandle <> 0
myItem.item = Object.item(myHandle)
myName$ = myItem\ID$
If myItem\stackable = True
myName$ = myName$ + "(" + myItem\quantity + ")"
EndIf
Else
myName$ = ""
EndIf
Text x, y + (i * 12), (i + " - " + myName$)
Next
End Function
; simple routine, just swaps an inventory from visible to hidden or vice versa
; depending on its current state
Function ToggleInventory(my.inventory)
For this.slot = Each slot
If this\parent = my
this\visible = Not this\visible
EndIf
Next
End Function
; shows all slot and chit instances for a specified inventory
Function ShowInventory(my.inventory)
For this.slot = Each slot
If this\parent = my
this\visible = True
EndIf
Next
End Function
; hides all slots and chit instances for a specified inventory
Function HideInventory(my.inventory)
For this.slot = Each slot
If this\parent = my
this\visible = False
EndIf
Next
End Function
; creates an inventory type object with a set number of slots
; including two banks; one for holding item references, one for
; holding references to slots that represent the inventory
Function CreateInventory.inventory(howMany, parent.item)
RVAL.inventory = New inventory
RVAL\max = howMany
RVAL\parent = parent
RVAL\myPack = CreatePack(howMany)
RVAL\SlotMap = CreateSlotMap(howMany)
Return RVAL
End Function
; destroys an inventory and the slots associated with it, not the objects inside!
; it DOES release items to an inventory above the current one, if it exists
; otherwise items get released to nullspace
Function DestroyInventory(where.inventory, safe=True)
; is this inventory located inside another one? if so, record it
If where\parent <> Null
If where\parent\inner <> Null
myNewLocation.inventory = where\parent\inner
EndIf
EndIf
; are there items inside this inventory? if so, let em out.
; use this same loop (conveniently) to destroy all of our slot instances too
For i = 0 To where\max - 1
; both routines can use this pointer
pointer = i * 4
; first, let's grab the item that's here, if any
myHandle = PeekInt(where\myPack, pointer)
If myHandle <> 0
myItem.item = Object.item(myHandle)
myItem\current = myNewLocation ; explodable bags!
EndIf
; now, let's grab the slot that's here
myHandle = PeekInt(where\myPack, slotMap)
If myHandle <> 0
mySlot.slot = Object.slot(myHandle)
DestroySlot(mySlot)
EndIf
Next
FreeBank where\myPack
Delete where
Return True
End Function
; creates a bank to hold handle info for items held within an inventory
Function CreatePack(packitems)
RVAL = CreateBank(packitems * 4)
Return RVAL
End Function
; creates a bank to hold handle info for slots within an inventory
Function CreateSlotMap(Slots)
RVAL = CreateBank(Slots * 4)
Return RVAL
End Function
; Creates a specific inventory slot for an inventory.
; FLAGS here represent the item type flags, and allow you to restrict putting
; certain types of items within certain slots.
Function CreateSlot.slot(ID$, image, x, y, parent.inventory, flags=0)
RVAL.slot = New slot
RVAL\ID = ID
RVAL\image = image
RVAL\loc.coord = New coord
RVAL\loc\x = x
RVAL\loc\y = y
RVAL\parent = parent
RVAL\flags = flags
AddSlotToInv(RVAL, parent)
Return RVAL
End Function
; Destroys a slot. Simple enough.
; Best not done manually -- let DestroyInventory do this for you
Function DestroySlot(mySlot.slot)
Delete mySlot\loc
Delete mySlot
End Function
; Adds a slot to inventory
; Best not done manually -- let CreateSlot do this for you.
Function AddSlotToInv(what.slot, where.inventory)
myHandle = Handle(what)
For i = 0 To where\max - 1
If PeekInt(where\slotmap, i*4) = 0
PokeInt where\slotmap, i*4, myHandle
Return True
EndIf
Next
DebugLog "Unable to add slot to slotmap!"
Return False
End Function
; Given an index number, return the slot instance of an inventory at that index
; I don't recall exactly why I did this and I don't use it for this lib,
; but who knows, it might be useful.
Function GetSlotByIndex.Slot(index)
RVAL.slot = First slot
For i = 1 To index
RVAL = After RVAL
If RVAL = Null Then Return Null
Next
Return RVAL
End Function
; Given a slot, return its index number within the inventory
; I =do= use this one. Must have thought I'd need the other one.
Function GetSlotIndex(this.slot)
For i = 0 To this\parent\max-1
offset = i * 4
CheckVal = PeekInt(this\parent\slotmap, offset)
If CheckVal = Handle(this)
Return i
EndIf
Next
Return -1
End Function
; given the index number and an inventory, return the item type that sits there.
; Another one i don't specifically use in the lib but it should be fairly apparent why this is useful
Function GetInvByIndex.item(where.inventory, index)
If index => where\max Then Return Null
pointer = index * 4
myHandle = PeekInt(where\myPack, pointer)
RVAL.item = Object.item(myHandle)
Return RVAL
End Function
; Creates an item and its corresponding chit instance.
; variant describes the type of item, and is useful for limiting inventory objects to only certain slots
; stackable means that you can stack a number of these objects together.
; quantity is only really useful for stackable objects
; if I remember correctly, slots was meant to be how many this item took up, but I said heck with that :D
Function CreateItem.item(variant, ID$, x, y, stackable=False, quantity=1, slots=0)
RVAL.item = New item
RVAL\variant = variant
RVAL\ID$ = ID$
RVAL\loc = CreateCoord(x, y)
RVAL\stackable = stackable
RVAL\quantity = quantity
If variant = ITEM_CONTAINER
RVAL\inner = CreateInventory(slots, RVAL)
EndIf
RVAL\tpl = GetChitTemplate(RVAL\ID$)
RVAL\display = CreateChit(RVAL)
Return RVAL
End Function
; Destroys an item and its corresponding child objects (coordinates and chit)
Function DestroyItem(which.item)
; this can only be true if this item has been set up as an inventory object
If which\inner <> Null
; ultimately we want the 'exploding bag' thing here ;)
If CountItemsInInv(which\inner) > 0
DebugLog "Cannot destroy full container"
Return False
EndIf
DestroyInventory(which\inner, False)
EndIf
If which\current <> Null
RemoveItemFromInv(which)
EndIf
DestroyChit(which\display)
DestroyCoord(which\loc)
Delete which
Return True
End Function
; adds an item to an inventory indiscriminately -- sort of like a flood fill, objects will just
; go wherever they fit. Good for initializing inventories.
Function AddItemToInv.Chit(which.item, where.inventory, slot$, stack=False, overflow=False)
For i = 0 To where\max - 1 ; iterating through each slot
mySlot.Slot = Object.Slot(PeekInt(where\slotmap, i*4))
If mySlot\ID$ = slot$ ; right kind of slot
myPack = PeekInt(where\myPack, i*4)
If (overflow = True And myPack = 0) Or (overflow = False) ; check to see if we're initializing inv
Return AddItemToSlot(which, mySlot, stack)
EndIf
EndIf
Next
Return Null
End Function
; Removes a specific item from its current inventory
; items carry a link to their current inventories at all time, so all you need
; is to pass the item to this routine
Function RemoveItemFromInv.chit(which.item) ; which\current knows where
If which\current = Null
Return Null
EndIf
myBank = which\current\myPack
myHandle = Handle(which)
For i = 0 To which\current\max - 1
pointer = i * 4
myInt = PeekInt(myBank, Pointer)
If myInt = myHandle
PokeInt myBank, pointer, 0
RVAL.chit = which\display
RVAL\home\holds = Null
RVAL\home = Null
which\current = Null
Return RVAL
EndIf
Next
DebugLog "Tried to remove an item from an inventory that's not its current inventory"
Return Null
End Function
; simple. just counts how many items are currently in an inventory.
Function CountItemsInInv%(where.inventory)
For i = 0 To where\max - 1
pointer = i * 4
If PeekInt(where\myPack, pointer) <> 0
count = count + 1
EndIf
Next
Return count
End Function
; Adds a SPECIFIC item to a SPECIFIC slot in an inventory,
; unlike AddItemToInventory, which just dumps stuff anywhere.
; STACK parameter tells an inventory whether to check if items are identical and stack if they are
Function AddItemToSlot.chit(which.item, mySlot.slot, stack=False)
If Not (which\variant And mySlot\flags) ; wrong kinda slot -- buhbye!
Return which\display
EndIf
myIndex = GetSlotIndex(mySlot)
If myIndex = -1
DebugLog "GetSlotIndex failed miserably."
Return Null ; erm, wtf?
EndIf
where.inventory = mySlot\parent
myPack = PeekInt(mySlot\parent\myPack, myIndex)
If which\current <> Null ; means this object is in another inventory
RemoveItemFromInv(which)
EndIf
; if the slot contains the same stackable item, we actually destroy the
; initiating item and add its value to the item lying there.
If stack = True ; true for the player pack - we stack things there always
myItem.item = CheckStack(which, mySlot) ; so what about THIS item?
If myItem <> Null ; means we can stack on myItem
myItem\quantity = which\quantity + myItem\quantity
DestroyItem(which)
Return Null
EndIf
EndIf
; if the slot holds nothing, drop the item in place
If mySlot\holds = Null ; if nothing in this slot
mySlot\holds = which\display ; adds this chit to the slot
PokeInt where\myPack, myIndex*4, Handle(which); adds this item to inventory
which\current = where ; tell the item where it lives
which\display\home = mySlot ; give a home to the obj
Return Null ; nothing left on the cursor
; otherwise, swap out the currently slotted object with the one we dropped
Else ; if something IS in this slot
tmp.chit = mySlot\holds ; record the chit
mySlot\holds = which\display ; add the new chit to the slot
PokeInt where\myPack, myIndex*4, Handle(which); overwrite existing item in pack
which\current = where ; tell the item where it lives
which\display\home = mySlot ; give a home to the display obj
tmp\home = Null ; remove the home for the displaced obj
Return tmp ; return the displaced object
EndIf
End Function
; Creates and returns a new coord instance
Function CreateCoord.coord(x, y)
RVAL.coord = New coord
RVAL\x = x
RVAL\y = y
Return RVAL
End Function
; destroys a coord instance
Function DestroyCoord(where.coord)
Delete where
Return True
End Function
; checks to see if a given object and any potential items on a given slot
; are stackable -- returns the item that can be stacked on if true
Function CheckStack.item(which.item, mySlot.slot)
If which\stackable = False Then Return Null
If mySlot\holds = Null Then Return Null
RVAL.item = mySlot\holds\linked
If RVAL <> Null
If RVAL\variant = which\variant
If RVAL\ID$ = which\ID$
Return RVAL
EndIf
EndIf
EndIf
Return Null
End Function
; Creates a chit template and returns the instance.
Function CreateChitTemplate.ChitTemplate(ID$, image, frame)
RVAL.ChitTemplate = New ChitTemplate
RVAL\ID$ = ID$
RVAL\image = image
RVAL\frame = frame
Return RVAL
End Function
; get a chit template by its ID
Function GetChitTemplate.ChitTemplate(ID$)
; consider speeding this up with an array of 26 insertion points (A-Z)
For this.ChitTemplate = Each ChitTemplate
If this\ID$ = ID$
Return this
EndIf
Next
Return Null
End Function
; Creates a chit instance, given an item instance.
Function CreateChit.Chit(link.item, x=0, y=0)
RVAL.chit = New chit
RVAL\linked = link
RVAL\loc.coord = New coord
RVAL\loc\x = x
RVAL\loc\y = y
Return RVAL
End Function
; Correctly destroys a chit
Function DestroyChit(this.chit)
Delete this\loc
Delete this
End Function
; Draws slots and does collisions for them with any active chit the player may be holding.
; this routine MUST be told whether a MouseDown event has occured.
Function DrawSlots(checkflag)
For this.slot = Each slot
If checkflag = True
If this\visible = True
If ImagesCollide(mouse, MouseX(), MouseY(), 0, this\image, this\loc\x, this\loc\y, 0)
If ChitActive = Null
If this\holds <> Null
ChitActive = RemoveItemFromInv(this\holds\linked)
EndIf
Else
ChitActive = AddItemToSlot(ChitActive\linked, this, True)
EndIf
CheckFlag = False
EndIf
EndIf
EndIf
If this\visible = True Then DrawImage this\image, this\loc\x, this\loc\y
Next
Return checkflag
End Function
; draws item chits currently visible.
; the commented code here is for using randomly scattered objects in a 2d playfield
; but since I've decided not to go this way I've pretty much ignored it and it's not guaranteed to work
; right. If you DO intend to let objects scatter around, this routine will need to know whether
; a mousedown has recently occured too.
Function DoChits(checkflag)
If ChitActive <> Null
ChitActive\loc\x = MouseX()
ChitActive\loc\y = MouseY()
Insert ChitActive After Last chit
EndIf
For this.chit = Each chit
; If CheckFlag = True
;
; If ChitActive = Null
; If ImagesCollide(this\linked\tpl\image, this\loc\x, this\loc\y, this\linked\tpl\frame, mouse, MouseX(), MouseY(), 0)
; ChitActive = this
; checkflag = False
; EndIf
; Else
; ChitActive = Null
; checkflag = False
; EndIf
; EndIf
; If this\home = null
If this = ChitActive
DrawImage this\linked\tpl\image, this\loc\x, this\loc\y, this\linked\tpl\frame
Else
If this\home <> Null
If this\home\visible = True
DrawImage this\linked\tpl\image, this\home\loc\x, this\home\loc\y, this\linked\tpl\frame
EndIf
; Else
; DrawImage this\linked\tpl\image, this\loc\x, this\loc\y, this\linked\tpl\frame
EndIf
EndIf
Next
End Function