Inventory code went wrong!

Simplified .blend:
http://www.pasteall.org/blend/36547

Transfering the properties of the inventoery slots to overlay scene was working fine when suddenly something went wrong. I don’t remember what I changed, but now it is not working. Who could tell me reason? Pay attention at Inventory.setGUI and Inventory.getGUI scripts.


Python script error - object 'Player', controller 'setGUI':
Traceback (most recent call last):
  File "/home/user/Adriana_Faili/Games/In-Dome/GameCompletionFolder/Blends/Part1.blend/Inventory.py", line 64, in setGUI
IndexError: list index out of range

What does this mean?

It means you’re trying to access a part of a list that doesn’t exist. For example list[2], the number in between the [] is called an index.

The way accessing lists by use of index starts from 0, so to get the first item in the list would be list[0]. The index of a list will be 1 less than the length of the list. So if list = [slot1, slot2, slot3]. The length of list is 3 since it has 3 objects in it. But since indexes start counting from 0, the last item is list[2].

Now lets use the list from above to give you an idea why the error popped up, if you tried list[3] with the list above you’d get the same error, the list is only 3 long but you’re trying to look at the 4th item, which in this case doesn’t exist and it warns you. The same as your problem.

Now I looked at your game a little, you’re having trouble with actually appending objects to your list. The list always seems empty to me. That’s why things aren’t showing up on the gui, I’m not sure why its doing it though since I don’t really mess with blender or programming really. but it might just be that the list gets reset constantly, I don’t know how the module controller works, but with all your always sensors, it might constantly run the beginning of your script which will always set the list to [].

If that’s the case maybe try making the list in a separate script or its own function to see if that helps.

Yes, you have a common problem, you are using module mode, that means all your code should be contained in functions or classes. There shouldn’t be any code at the root level, except import BGE.

#define
cont = logic.getCurrentController()
own = cont.owner
scene = logic.getCurrentScene()
objects = scene.objects
sens = cont.sensors
col = sens["collide"]
space = sens["space"]
slotsList = []

Here’s you problem area. Each time your script runs, it is setting slots list to [].

You should define your variables only in the module you are running:

def items(cont):
    own = cont.owner
    scene = logic.getCurrentScene()
    objects = scene.objects
    sens = cont.sensors

Notice that the active controller is passed automatically as an argument of the function.

You need to make slotsList either a property of own (own[‘slotslist’]) or a part of the globaldict (GlobalDict[‘slotsList’])

If you want to define something once, try writing an initiation section for your script:

if "ini" not in own:
    own['slotsList'] = []
    own['ini'] = True

This will run only once, since you add the “ini” property to your own object during this chunk of code.

The initiation code is a good place to define default properties or to generate walkmeshes, place enemies, swap skins, activate 2d filters etc… as you can be sure it will be the first thing the object does and it won’t do it again.

When you uses classes in python they already have an initiation function, but if not this is a good workaround.

You can even make it in to a function:


def player_ini(own):
    if "ini" not in own:
        own['slotsList'] = []
        own['ini'] = True

def player(cont):
    own = cont.owner
    player_ini(own)

to keep your code separate.


import bge
from bge import logic


#define
cont = logic.getCurrentController()
own = cont.owner
scene = logic.getCurrentScene()
objects = scene.objects
sens = cont.sensors
col = sens["collide"]
space = sens["space"]
if "ini" not in own:
    own['slotsList'] = []
    own['ini'] = True


#execute slots
def slots():
    #get slots
    for object in objects:
        if "slot" in object:
            own['slotsList'].append(object)
            #print(object) #for control
    #tasks with slots
    for slot in own['slotsList']:
        if slot["amount"] < 1:
            slot["amount"] = 0
            slot["slot"] = 0
            slot["aviable"] = True
        if slot["slot"] < 1:
            slot["slot"] = 0
            slot["amount"] = 0
            slot["aviable"] = True
        if slot["amount"] > 0:
            slot["aviable"] = False
                


#execute collect items
def items():
    #define sens
    col = sens["collide"]
    space = sens["space"]
    #tasks with items
    if col.positive:
        obj = col.hitObject
        if space.status == 1:
            collectItem(obj["item"], obj["amount"])
            obj.endObject()
                       
#collect item
def collectItem(itemID, itemAmount):
    for slot in own['slotsList']:
        if slot["slot"] == itemID:
            slot["amount"] += itemAmount
            itemAmount = 0
            break
        elif slot["aviable"] == True:
            slot["slot"] = itemID
            slot["amount"] = itemAmount
            break
        else:
            continue
     
#transfer to GUI
def setGUI():
    for n in (0, (len(own['slotsList']) - 1)):
        slot = own['slotsList'][n]
        logic.globalDict["slot" + str(n)] = slot["slot"]
        logic.globalDict["amount" + str(n)] = slot["amount"]
        
def getGUI():
    objectsL = logic.getCurrentScene().objects
    if "ini" not in own:
        own['srList'] = []
        own['ini'] = True
    for obj in objectsL:
        if "slot" in obj:
            own['srList'].append(obj)
    for n in (0, (len(own['srList']) - 1)):
        slotR = own['srList'][n]
        slotID = logic.globalDict.get("slot" + str(n))
        slotA = logic.globalDict.get("amount" + str(n))
        if slotID is None:
            return
        slotR["slot"] = slotID
        slotR["amount"] = slotA

Something like htis should work?

No, only if you are using script mode. With module mode you need everything to be in the called function. Nothing can be at the root level (no indent) except import bge.

You need to move all the definitions to the items() function, and repeat them in any script which isn’t connected to items().

If you create a new function, that function can get access to own by including it in the function’s arguments:

import bge
def new_function(own):
    print (own["foo"])

def main(cont):
    own = cont.owner
    own["foo"] = "bar"
    new_function(own)

This is the only safe way to pass variables from one function to another.

consider the following;

import bge

own = {}
own["foo"] = None

def new_function(own):
    print (own["foo"])

def main(cont):
    own = cont.owner
    own["foo"] = "bar"
    new_function(own)

What would be printed in this case?


import bge
from bge import logic


if "ini" not in own:
    own['slotsList'] = []
    own['ini'] = True


#execute slots
def slots():
    cont = logic.getCurrentController()
    own = cont.owner
    scene = logic.getCurrentScene()
    objects = scene.objects
    #get slots
    for object in objects:
        if "slot" in object:
            own['slotsList'].append(object)
            #print(object) #for control
    #tasks with slots
    for slot in own['slotsList']:
        if slot["amount"] < 1:
            slot["amount"] = 0
            slot["slot"] = 0
            slot["aviable"] = True
        if slot["slot"] < 1:
            slot["slot"] = 0
            slot["amount"] = 0
            slot["aviable"] = True
        if slot["amount"] > 0:
            slot["aviable"] = False
                


#execute collect items
def items():
    cont = logic.getCurrentController()
    own = cont.owner
    scene = logic.getCurrentScene()
    objects = scene.objects
    sens = cont.sensors
    #define sens
    col = sens["collide"]
    space = sens["space"]
    #tasks with items
    if col.positive:
        obj = col.hitObject
        if space.status == 1:
            collectItem(obj["item"], obj["amount"])
            obj.endObject()
                       
#collect item
def collectItem(itemID, itemAmount):
    cont = logic.getCurrentController()
    own = cont.owner
    scene = logic.getCurrentScene()
    objects = scene.objects
    for slot in own['slotsList']:
        if slot["slot"] == itemID:
            slot["amount"] += itemAmount
            itemAmount = 0
            break
        elif slot["aviable"] == True:
            slot["slot"] = itemID
            slot["amount"] = itemAmount
            break
        else:
            continue
     
#transfer to GUI
def setGUI():
    cont = logic.getCurrentController()
    own = cont.owner
    scene = logic.getCurrentScene()
    objects = scene.objects
    for n in (0, (len(own['slotsList']) - 1)):
        slot = own['slotsList'][n]
        logic.globalDict["slot" + str(n)] = slot["slot"]
        logic.globalDict["amount" + str(n)] = slot["amount"]
        
def getGUI():
    cont = logic.getCurrentController()
    own = cont.owner
    scene = logic.getCurrentScene()
    objects = scene.objects
    objectsL = logic.getCurrentScene().objects
    if "ini" not in own:
        own['srList'] = []
        own['ini'] = True
    for obj in objectsL:
        if "slot" in obj:
            own['srList'].append(obj)
    for n in (0, (len(own['srList']) - 1)):
        slotR = own['srList'][n]
        slotID = logic.globalDict.get("slot" + str(n))
        slotA = logic.globalDict.get("amount" + str(n))
        if slotID is None:
            return
        slotR["slot"] = slotID
        slotR["amount"] = slotA

This still doesn’t work. Could you fix this, please?

You need to move the initiation function in to the items function too.

BTW; you only need to define scene and objects in funtions that will actually use those things.

Thank oyu very much, smoking mirror. Now it is fixed. I will obiouvsly credit you and youle for final help in the script.

To clarify what Smoking_mirror is talking about, there are two forms of script execution - script mode and module mode.

Script mode treats the text block like a single block of code, that is run every time the controller is triggered.
Module mode treats the text block like a Python module - it is executed once and the module is called by the Python Controller.
This has the side-effect of binding the script to a single object, if you place initialisation code directly in the module body (rather than inside the module functions).

The point is, define your code in functions. You could place it all in a single function, if you wanted, but that wouldn’t be good practice. Avoid getCurrentController and getCurrentScene, now that you can get the scene from the game object, and the controller is passed as an argument in module mode.
I give this advice because such functions allow global state - getting the current controller anywhere. If you never use such functions, you’ll have to get the controller from a useful context (a module function) e.g


def do_something(cont):
    scene = cont.owner.scene

Yes

scene =own.scene

Is one of the most useful bits of code there is.

Or just use own.scene.objects

OK! Now I found out that it is not working for other slots, just the first one in GUI. Here is .blend:
http://www.pasteall.org/blend/36562

What could fix this?

Hello? Anyone? I really need this for my proejcts.

@Smoking_mirror: Hi! I don’t understand why you said this:

Yes, you have a common problem, you are using module mode, that means all your code should be contained in functions or classes. There shouldn’t be any code at the root level, except import BGE.

Code:
#define
cont = logic.getCurrentController()
own = cont.owner
scene = logic.getCurrentScene()
objects = scene.objects
sens = cont.sensors
col = sens[“collide”]
space = sens[“space”]
slotsList = []
Here’s you problem area. Each time your script runs, it is setting slots list to [].

Why can’t we use root level to define global variables? The beginning code (the code outside functions) is run only once. Even if you run 2 functions with 2 python controllers linked to an always sensor in pulse mode:

http://www.pasteall.org/blend/36570

In this file for example, “ok” is printed only one time, no? And myList is correctly appended… There’s something I don’t understand.

You need to provide better example files, which have the required objects, and explain which buttons do what.

Here’s a slightly improved script which will still need further changes. (GetGUI didn’t seem valid)


#########################################################
#       Inventory Script by adriansnetlis               #
# Thanks to:                                            #
#-Smoking_Mirror                                        #
#-youle                                                 #
#########################################################


#########################################################
#                       items                           #
#
#  0 empty
#  1 9mm ammo
#                                                       #
#########################################################


import bge
from bge import logic


#execute slots
def slots(cont):
    own = cont.owner
    scene = own.scene
    objects = scene.objects
    
    try: 
        slots = own["slotsList"]
        
    except KeyError:
        slots = own["slotsList"] = []
                
    #get slots
    for object in objects:
        if "slot" in object:
            slots.append(object)
            
    #tasks with slots
    for slot in slots:
        if slot["amount"] < 1:
            slot["amount"] = 0
            slot["slot"] = 0
            slot["available"] = True
            
        if slot["slot"] < 1:
            slot["slot"] = 0
            slot["amount"] = 0
            slot["available"] = True
            
        if slot["amount"] > 0:
            slot["available"] = False
                


#execute collect items
def items(cont):
    own = cont.owner
    sens = cont.sensors
    
    #define sens
    col = sens["collide"]
    space = sens["space"]
    
    slots = own["slotsList"]
    
    #tasks with items
    if not col.positive:
        return
    
    obj = col.hitObject
    
    if space.status != logic.KX_INPUT_JUST_ACTIVATED:
        return


    collectItem(slots, obj["item"], obj["amount"])
    obj.endObject()
                   
#collect item
def collectItem(slots, itemID, itemAmount):    
    for slot in slots:
        if slot["slot"] == itemID:
            slot["amount"] += itemAmount
            itemAmount = 0
            return
    
    # Otherwise find available slot
    for slot in slots:
        if slot["available"] == True:
            slot["slot"] = itemID
            slot["amount"] = itemAmount
            return
     
#transfer to GUI
def setGUI(cont):
    own = cont.owner
    
    for n, slot in enumerate(own['slotsList']):
        logic.globalDict["slot{}".format(n)] = slot["slot"]
        logic.globalDict["amount{}".format(n)] = slot["amount"]
        
def getGUI(cont):
    own = cont.owner
    scene = own.scene
    objectsL = scene.objects
    
    try: 
        slots = own["slotsList"]
        
    except KeyError:
        slots = own["slotsList"] = []
        
    for obj in objectsL:
        if "slot" in obj:
            slots.append(obj)
            
    for n, slotR in enumerate(slots):
        slotID = logic.globalDict.get("slot{}".format(n))
        
        if slotID is None:
            return
        
        slotA = logic.globalDict.get("amount{}".format(n))
        
        slotR["slot"] = slotID
        slotR["amount"] = slotA
        
def visualiseItem(cont):
    own = cont.owner
    parent = own.parent
    slot_id = parent["slot"]


   # own.replaceMesh("Item{}".format(slot_id), True, False)

Why own.replaceMesh(…) makes it not work? What actually is wrong with it?

You don’t have objects with that mesh name in the scene

Well that global variable will apply to every object using that script. If you are sure which variable belongs in which script you can do it that way, but there are better ways of handling your globals IMHO. Apart from imports, I can’t think of any variables I would like all my objects to have.

So at my root level I usually import bge mathutils and other modules I need but not other things such as scene definitions or object lists.

I guess you could use it for several useful things but I would expect it to go wrong sometimes.

for example try adding another object with this function:

def function3(cont):    
    myList.append("x")     
    myList = []  

When myList is rebound as a local variable, access to the old global myList is lost from function3. But, the myList global used by the other two functions seems to still exist.

It’s just my opinion, but it becomes harder to manage the code, and harder for other people to understand my code if I use module mode like that. Also somethings become more difficult. What if I want to clear or rebind mylist as a global?

It even made whole code not work also when all the item IDs I had in scene was 0 and 1, and there are “Item0” and “Item1” meshes in scene. Maybe you could tell me how to do a check: if “ItemID”(was syntax to add int in it something like `“Item{}”. …) mesh in scene than replace the mesh, else just skip it?

@Smoking_mirror: Thanks for your answer. I understand know. But what if you use bge.logic.myList = [] at the root level. And for the sensors, actuators (EDIT: Ah no, that won’t work if the script is used by another object), things that won’t change after, we could declare it at the root level, or there is a problem to do like that too?