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