Inventory system -- attribute of object or separate class?

Isn’t lib_load broke on UPBGE?

So that’s the verdict? attribute of object, is better?

Just remember. As a gamer it’s really important to us, that when you quit the game and shut down the PC.
that the next time we play the game, all the goodies are still there. :wink:

no they fixed it, but they have more issues then you can think of atm

My veredict is dicts are badass. Nothing further!

But seriously now, it’s an attribute AND a class instance, not just a dictionary anymore. If anything, I’m only storing a reference to the object inside another object because it is convenient. Inception.

Here’s code completely out of context.

import shelve
from re import findall
from dsm.g_utils import genItemID
from bge.logic import expandPath
from mathutils import Vector
from dsm.globals import _UI
#------
itemDict = {}
#------    
class game_inventory(dict):
    def __init__(self,master,conts,size="1x1",name="Inventory"):
        self.master = master
        self.name = name
        self.x,self.y = [int(num) for num in size.split("x")]
        self.size = self.x*self.y
        self.genInvSlots()
        self.getConts(conts)
    #------
    def genInvSlots(self):
        inv_slots = {}
        x = y = 0
        #------
        for i in range(0,self.size):
            key = str(x)+","+str(y)
            pos_x,pos_y = (x+1)/10,(-y-1)/10
            #------
            newSlot = {"id":key,"pos":Vector([pos_x,pos_y,0.0]),"occ":False,"ad_occ":[]}
            inv_slots[key] = newSlot
            #------
            x += 2
            if x == self.x*2:
                x = 0
                y += 2
        #------
        self.slots = inv_slots
    #------
    def calcSpace(self,slot,tilesize,jussCheck=False):
        obj_x,obj_y = [int(num) for num in tilesize.split("x")]
        slot_x,slot_y = [int(num) for num in slot.split(",")]
        occSlots = []
        #-------
        for x in range(0,obj_x):
            for y in range(0,obj_y):
                #-------
                adj_key = str(slot_x+(x*2))+","+str(slot_y+(y*2))
                if adj_key in self.slots:
                    adj_slot = self.slots[adj_key]
                    #-------
                    if self.slots[adj_key]["occ"] != False:
                        return False
                    else:
                        occSlots.append(self.slots[adj_key])
                else:
                    return False
        #-------
        if not jussCheck:
            #-------
            id_list = []
            for space in occSlots:
                space["occ"] = True
                id_list.append(space["id"])
            #-------
            self.slots[slot]["ad_occ"] = id_list
        #-------
        return True
    #-------
    def checkSpace(self,tilesize):
        gen = (slot for slot in self.slots if not self.slots[slot]["occ"])
        #-------
        for slot in gen:
            #-------
            willFit = self.calcSpace(slot,tilesize,True)
            if willFit:
                return True
        #-------
        return False
    #-------
    def getConts(self,inv_string,old_id="",transfer=False):
        if inv_string != "":
        #------
            path = expandPath("//"+"data/-4")
            if "," in inv_string:
                obj_list = inv_string.split(",")
            else:
                obj_list = [inv_string]
            #------
            s = shelve.open(path)
            #------
            try:
                #------
                for obj in obj_list:
                    #------
                    try:
                        quant = int(findall('\(([^)]+)',obj)[0])
                    except:
                        quant = 1
                    #------
                    gen = (slot for slot in self.slots if not self.slots[slot]["occ"])
                    for slot in gen:
                        #------
                        obj_name = obj.rstrip("(%d)"%(quant))
                        item = s["Objects"][obj_name]
                        willFit = self.calcSpace(slot,item["tilesize"])
                        if willFit:
                            item_id = old_id
                            #------
                            if old_id == "":
                                item_id = genItemID(itemDict,item["obj_class"])
                                itemDict[item_id] = {"id":item_id,"base_obj":obj_name}
                            #------
                            print(item_id)
                            self[slot] = {"instance":itemDict[item_id],"data":item,"quant":quant}
                            self.slots[slot]["occ"] = True
                            if hasattr(self,"slave"):
                                self.drawIcon(slot)
                            if transfer != False:
                                old_slot,old_inv = transfer[0],transfer[1]
                                old_inv.forgetObject(old_slot)
                            break
            #------
            finally:
                s.close()
    #------
    def drawContainer(self):
        if _UI.overlay_load == 2 and not hasattr(self,"slave"):
            cont_master = _UI.inv_sc.addObject("inv_contMaster")
            new_baseSlot = cont_master.children[0]
            new_baseSlot["master"] = self
            slot_text = new_baseSlot.children[0]
            slot_text.removeParent()
            slot_text.text = self.name
            new_baseSlot.worldScale.xy = [self.x,self.y]
            new_baseSlot.worldPosition.xy = [self.x/10,-self.y/10]
            slot_text.setParent(new_baseSlot)
            setattr(self,"slave",new_baseSlot)
            self.drawAllIcons()
            self.adjustCont(cont_master)
    #------
    def drawIcon(self,key):
        slot = self.slots[key]
        icon_size = self[key]["data"]["tilesize"]
        #------
        obj_name = (
                    self[key]["instance"]["base_obj"]
                    )
        #------
        newIcon = _UI.inv_sc.addObject("inv_icon",self.slave)
        newIcon.setParent(self.slave)
        newIcon.replaceMesh(obj_name+"_ico")
        newIcon.replacePhysicsShape(_UI.inv_sc.objectsInactive["icon_"+icon_size])
        newIcon.worldPosition = slot["pos"]+self.slave.parent.worldPosition
        newIcon.name = key
        newIcon["slot"] = key
    #------
    def drawAllIcons(self):
        #------
        for key in self:
            self.drawIcon(key)
    #------
    def adjustCont(self,cont_master):
        total_conts = _UI.inv_sc.objects.filter("inv_baseSlot","inv_slot")
        gen = (cont for cont in total_conts if cont != cont_master.children[0])
        for cont in gen:
            height = ((cont_master.children[0].worldScale.y*2)/10)+(cont.worldScale.y/10)
            cont_master.applyMovement([0,height*1.125,0])
            cont.parent.worldPosition = [0,0,0]
    #------
    def transferObject(self,slot,target):
        item_id = self[slot]["instance"]["id"]
        obj_string = self.slave.children[slot]["obj_string"]
        #------
        if self != target:
            transfer_x = [slot,self]
            target.getConts(obj_string,item_id,transfer_x)
        else:
            self.occupySlot(slot)
    #------
    def forgetObject(self,slot):
        self.slave.children[slot].endObject()
        del self[slot]
    #------
    def freeSlot(self,slot):
        self.slave.children[slot]["obj_string"] = self.object_toString(slot)
        self.slots[slot]["occ"] = False
        #------
        if self.slots[slot]["ad_occ"] != []:
            for key in self.slots[slot]["ad_occ"]:
                self.slots[key]["occ"] = False
    #------
    def occupySlot(self,slot):
        self.slots[slot]["occ"] = True
        #------
        if self.slots[slot]["ad_occ"] != []:
            for key in self.slots[slot]["ad_occ"]:
                self.slots[key]["occ"] = True
    #------
    def object_toString(self,key):
        id = self[key]["instance"]["base_obj"]
        quant = self[key]["quant"]
        inv_string = id+"(%d)"%(quant)
        #------
        return inv_string
    #------
    def hideSlave(self):
        cont_master = self.slave.parent
        cont_master.endObject()
        del self.slave 

Your API looks decent :slight_smile:

Why the #------ everywhere though :cry:

Whitespace does the trick too…

Well, uh… I like to cut up the code into blocks like that, it’s easier for me to read. If I don’t put in those separators I get confussed quickly when writing anything longer than a couple dozen lines. Also helps differentiate sections of a program at a glance, which is nice.

Side note: I might make this project open source at some point.

1 Like

Side note: No open source project does that, at least yet :stuck_out_tongue:

But the move is appreciated! Thanks!

nice class you got there, a lot is going on.
I don’t put the code for drawing to the screen inside my abstract classes my self and another thing is the code that store the inventory can be done simpler.

here is a complete example that stores a monster including it’s loot (inventory) to disk and restores it (scroll to the end for the important bits.)

import json
import shelve

import hashlib
import random
import time


def genkey(data=None):
    if data:
        data = str(data)
    else:
        data = str( time.time() ) + str( random.randrange(1,10000) )
    generated_id = hashlib.md5(data.encode('utf-8'))
    return generated_id.hexdigest()

#-------------------------------------------------
class Base:
    """
    Base Class
    """
    def __init__(self,**kw):
        super().__init__()
        
    def __getitem__(self, key):
        return getattr(self, key)
    
    def __setitem__(self, key, value):
        setattr(self, key, value)

    def __repr__(self):
        return "{} {}".format(type(self),vars(self))
    
    def update(self,**kw):
        for arg in kw:
            if arg in vars(self):
                if type(self[arg]) == dict:
                    self[arg].update(kw[arg])
                else:
                    self[arg] = kw[arg] 
            else:
                print("Error: Attribute [ ",arg," ] does not exists!")


#-------------------------------------------------   
class GameObject(Base):
    
    def __init__(self,**kw):
        self.Id=0
        self.name = "GameObject"
        self.worldPosition = None
        self.worldOrientation = None
        self.worldScale = [1.0,1.0,1.0]
        
        super().__init__()
    
        self.update(**kw)
    
    def __getitem__(self, key):
        return getattr(self, key)
    
    
    def __setitem__(self, key, value):
        setattr(self, key, value)

    def __repr__(self):
        return "{} {}".format(type(self),vars(self))
    
    
#-------------------------------------------------   
class DynamicGameObject(GameObject):
    
    def __init__(self,**kw):
        super().__init__()
        self.stacksize = 1
        self.count = 1
        self.objsize = 100
        
        self.update(**kw)
        
        if self.count < 1:
            self.count = 1
            
        if self.count > self.stacksize:
            self.stacksize = self.count
                
        
            
    def addItemToStack(self,amount = 1):
        if self.count < self.stacksize:
            self.count += amount
            
            if self.count > self.stacksize:
                tmp = self.count - self.stacksize
                self.count = self.stacksize
                return tmp
            
        return 0
    
    
    def removeItemFromStack(self,amount = 1):
        if self.count > amount:
            self.count -= amount
            return False
        else:
            self.count = 0
            return True
        
        
    def getallattr(self):
        return vars(self)
        #return  self.name, self.objsize, self.worldPosition, self.worldOrientation, self.count, self.stacksize
        

    
         
#-------------------------------------------------
class basicContainer(Base):
    
    def __init__(self,**kw):
        self.Id = 0 #deprecated don't use.
        self.key = "defaultkey"
        self.name = "container"
        
              
        self.items = {}
        
        super().__init__()
        self.update(**kw)
    
    def __len__(self):
        return len(self.items)
    
    def __iter__(self):
        for n in self.items:
            yield self.items[n]
        return self
    
    def __next__(self):
        return self
    
    
    def add(self,item):
        if hasattr(item,"key"):
            self.items[item["key"]] = item
        else:
            self.items[genkey()] = item

    def remove(self,item):
        if hasattr(item,"key"):
            if item["key"] in self.items:
                outobj = self.items[item["key"]]
                del self.items[item["key"]]
                return outobj
        else:
            for key in self.items.keys():
                if item == self.items[key]:
                    outobj = self.items[key]
                    del self.items[key]
                    return outobj
                
        if hasattr(item,"name"):
            print("ERROR :",item.name,"Not in",self.name)
        else:
            print("ERROR : Item Not in",self.name)
            
        return None
    
#-------------------------------------------------
class Container(basicContainer):
    
    def __init__(self,**kw):
        self.maxitems = 10
        self.maxitemsize = 0
        self.active = True
        self.containertype = ""
        self.freeslots = []
                
        super().__init__()
        self.update(**kw)
        
        if "items" in kw:
            self.items = {}
            for itm in kw["items"]:
                obj,slot = kw["items"][itm]
                self.items[itm] = [DynamicGameObject(**obj),slot]
        
        if not self.freeslots:
            self.freeslots = [i for i in range(0,self.maxitems) ]

        
    def getallattr(self):
        self.dumps()
        
    def dumps(self):
        allitems = {}
        
        for itm in self.items:
            tmp = self.items[itm]
                  
            allitems[itm] = [ tmp[0].getallattr(),tmp[1] ]
            
        return {"name":self.name, "maxitems":self.maxitems, "maxitemsize":self.maxitemsize, "freeslots":self.freeslots, "active":self.active, "containertype":self.containertype, "items":allitems}
    
    def swap(self,slot_a,slot_b): # slot_a = from , slot_b = to
        
        if slot_b in self.freeslots:
            for key in self.items:
                itma,slota = self.items[key]
                if slota==slot_a:
                    self.items[key][1]=slot_b
                    self.freeslots.append(slota)
                    self.freeslots.remove(slot_b)
                    self.freeslots.sort()
        else:
            tmp1=None
            tmp2=None
            
            for key in self.items:
                itma,slota = self.items[key]
                if slota==slot_a:
                    tmp1=key
                    
            for key in self.items:
                itmb,slotb = self.items[key]
                if slotb==slot_b:
                    tmp2=key
                
            tmp = self.items[tmp1][1]
            self.items[tmp1][1] = self.items[tmp2][1]
            self.items[tmp2][1] = tmp
           
    def findObjectsByName(self,name=""):
        tmp = None
        for item in self.items:
            itm,slot = self.items[item]

            if name == itm.name:
                if tmp:
                    tmp.append(itm)
                else:
                    tmp = [itm]
                
        return tmp
    

    def add(self,item,slot=None):
            
        if self.maxitemsize !=0: # maxitemsize = 0 = no limit
            if hasattr(item,"objsize"):
                if item["objsize"]>self.maxitemsize:
                    print("Error : can't add ",item.name,"to",self.name,": object to big")
                    return False
                
        if len(self.freeslots)>0:
            if item["Id"] in self.items:
                print("Error : can't add ",item.name,"to",self.name,": can't add the same object twice")
                return False
            else:
                if slot:
                    if slot in self.freeslots:
                        index = self.freeslots.index(slot)
                        self.items[item["Id"]] = [item,self.freeslots[index]]
                        del self.freeslots[index]
                    else:
                        self.items[item["Id"]] = [item,self.freeslots[0]]
                        del self.freeslots[0]
                else:
                    self.items[item["Id"]] = [item,self.freeslots[0]]
                    del self.freeslots[0]
                    
                return True
        else:
            print("Error : cant add ",item.name,": Container",self.name,"is Full")
            return False

    def delObjectfromslot(self,fromslot):
        for item in self.items:
            itm,slot = self.items[item]
            if slot == fromslot:
                out = self.remove(itm)
                return out
        return None
            
    def remove(self,item):
        if item["Id"] in self.items:
            itm ,slot = self.items[item["Id"]]
            self.freeslots.append(slot)
            self.freeslots.sort()
            #outobj = copy.copy(self.items[item["Id"]])
            outobj = self.items[item["Id"]]
            del self.items[item["Id"]]
            return outobj[0]
        else:
            print("ERROR :",item.name,"Not in",self.name)
            return False

    def getfreeslots(self):
        return self.freeslots
    
               
#-------------------------------------------------
class Loot:
    """
    there be loot.
    """
    def __init__(self):
        super().__init__()
        self.loot = Container(maxitems = 20)
        
    def addLoot(self,data):
        self.loot.add(data)

        
#-------------------------------------------------
class Dummy:
    """
    Dummy Class
    """
    def __init__(self):
        super().__init__()

        
#-------------------------------------------------       
class AI(Dummy):
    """
    artificial intelligence (more or less)
    """
    def __init__(self):
        super().__init__()
        self.state = None


#-------------------------------------------------        
class Monster(Base,AI,Loot):
    """
    There be Monsters.
    """
    def __init__(self,**kw):
        super().__init__()
        self.name = "snake"
        self.key = "defaultkey" # override this with unique key.
        self.stats = {"LVL":1,"HP":100,"Mana":10,"XP":20}
        
        self.update(**kw)
        
        if "loot" in kw:
            self.loot = Container(**kw["loot"])

        
    def dumps(self):
        return {"name":self.name,"key":self.key,"stats":self.stats,"loot":self.loot.dumps(),"state":self.state}
    

#------------------------------------------------- 
def save(filename="defaultshelve",key="defaultkey",data=""):
    """
    Save to Disc
    """
    try:
        with shelve.open(filename) as s:
            s[key] = data
    except:
        print('ERROR: {}'.format(key))


#------------------------------------------------- 
def load(filename="defaultshelve",key="defaultkey"):
    """
    Load from Disc
    """
    try:
        with shelve.open(filename) as s:
            return s[key]
    except:
        print('ERROR: {}'.format(key))
        return json.dumps({})

    
#------- testing goes here --------

stats = {"stats":{"LVL":10,"HP":300}}

giraffe = Monster(key="awesomekey",name="giraffe",**stats)

giraffe.update(**stats)
giraffe.addLoot(DynamicGameObject(**{"Id":genkey(),"name":"grass","count":10}))
giraffe.addLoot(DynamicGameObject(**{"Id":genkey(),"name":"water","count":5,"stacksize":20}))
save( data = json.dumps(giraffe.dumps()) )

tmp = json.loads(load())

cat = Monster(**tmp)

cat.update(**{"key":"123","state":"sleeping","name":"cat"})
print("\n--print object--------------------------------------\n")
print(giraffe)
print("---")
print(cat)

print("\n--print dump from object--------------------------------------\n")
print(giraffe.dumps())
print("---")
print(cat.dumps())

Hey, thanks for the example, I’ll take a close look at it c:

I have the functions for drawing in there because inventory.draw() makes more sense to me than draw(inventory). They’re nearly the same thing though, and as a matter of fact, it’s because I chose to do the drawing and grid spatial searches within the inventory class, that it turned out kind of complex. Upside is I can do most things in one go.

EDIT: also I can move stuff around like so

My 5 cents:

You already discovered it is an “inventory system”. That means it is a bit more complex than “just” a game object and/or a class.

I suggest to use the MVC approach to design your inventory system. This way you can design the core functionality into a “model” that can completely exist as Python object (containing other Python objects indeed).

The presentation to the user can be performed via a “view”. As this is very visual it can consist of different game objects. The view can make use of Python too, but should not be mixed with the model (as this would create a dependency from model to the view). Remember the inventory (model) should still be present, even when the view does not show anything.

To interact with the game, a controller can communicate with view and model. This can consist of one or more game objects using Python and takes care of the (user) input.

This structure allows to even use different implemnetations of the view and controller without the need to change the model.

Example: You have one view/controller to use the inventory in game, you can have another one that allows you to use the inventory in a shop system, and another one to configure the inventory in a “briefing scene” (e.g. what equipment to start with). They all can look different and can have different features (e.g. in the shop you can only change items by transferring currency), but the inventory is still the same.

Just something to think about.

1 Like