Start making an inventory accessed by keys

Dear friends.
The simple adventure game inventory (see https://blenderartists.org/forum/showthread.php?412199-How-to-make-a-simple-adventure-game-inventory&p=3129824&viewfull=1#post3129824) I made so far has the disadvantage that it needs for each kind of “inventory item” an own keyboard key, which limits the amount of them in the game.
I changed this issue using slots accessed by keyboard keys, i.e. each slot has an own key, and the item in each slot is accessed with the key of the slot. So the amount of “inventory items” in the game can be unlimited and only the amount of slots for an inventory is limited by the amount of used keyboard keys for them.

Features / characteristics :

  • Items can be taken from the scene and can be put back to certain places,

  • stackable items in the inventories,

  • The inventories are dictionaries. The dictionary key is the slot number and the dictionary value is a list of two items, which are used for the game item name and the game item amount.

  • The content of the inventories is printed as text to the screen, sorted by the key numbers.

  • Each inventory slot correspond to a keyboard key. The different inventories can use the same keys for their slots, p.e. NUM keys 1 to 5 for the slots 1 to 5.

  • interaction of player inventory with container inventory by keyboard keys in both directions. Each key press substrates one item from one inventory and adds it to the other. The output on screen of the content of the implicated inventories is refreshed and shown after each action.

  • certain object names are shown if the mouse is over them.

    Disadvantages :

  • The maximum amount of slots of an individual inventory is limited by the amount of used keyboard keys for the slots. But all inventories can use the same keys for the slots.

  • The code results to be long if we integrate interaction with containers, because then:

  • for each container we need an interaction code which consists of code blocks of ca. forty lines for each slot of both interacting inventories, i.e. the container and the player inventory.

  • for each kind of ‘get- and dropable item’ we need an own code block of fifteen lines per possible position in the player inventory to drop them back to the scene. The others items will not cause this increase.

Credits :
Thanks to all the visitors of my threads, who’s thought and wishes related with them have pushed me so far.

Contents
:
How to make inventory slots accessed by keyboard events -> please scroll down a little bitChanging some keys to Python keyboard ‘all keys’ events

How to use Python Keyboard ‘All keys’ events with character motion

How to use Python Keyboard ‘All keys’ events with the AND operator

How to use Python Keyboard ‘All keys’ events with the OR operator

How to add an item to an inventory slot -> 3139634How to drop an item back to scene from an inventory slot

How to make items stackable in inventory slots -> 3139644Changing the structure of the inventory dictionary

Adding stackable items to the inventory

Dropping stackable items to the scene

The container slots -> 3140049The consequences caused by them
How to add a slot to an existing inventory

[INDENT=2]Comparing two interacting inventories
[/INDENT]
How to add a new item to the game

How to interact with Box_01 -> 3140050How to add items from the player inventory to Box_01

How to add items from Box_01 to the player inventory

I will start with the explanations in the next post.
Greetings
achisp


Dear friends.
Let’s start with the description of the inventory code.

### Changing some keys to Python keyboard ‘all keys’ events ###
To don’t have so much Keyboard sensors, we replace in SimpleInventory_Demo_061.blend for the player object all keyboard sensors for the NUM keys and for the W, A, D movement keys with one of them. This one we name AllKeys_01 and set it to All keys .
In the Text editor we delete the code lines for definition of the keyboard sensors for the items, p.e. item_04 = cont.sensors[“Item_04”] and replace them one time for each module functions with allKeys = cont.sensors[‘AllKeys_01’] . Furthermore we adapt in the following code lines the sensor calls for the items, p.e. we change if item_04.positve: to for key,status in allKeys.events: ; if status == bl.KX_INPUT_JUST_ACTIVATED: ; if key == bge.events.FOURKEY: (the later code will be better on three lines with the correct indentation).

### How to use Python Keyboard ‘All keys’ events with character motion ###
In the Logic editor we connect the keyboard sensor AllKeys_01 with a new Python controller, set to Module mode with the module function SKeyInv.motion to run.
We delete the three AND controller connected with the three motion actuators and connect the later three with the Python controller.
In the Text editor we add the module function def motion(cont): .

Part of the next code is taken from: https://www.blender.org/api/blender_python_api_2_78a_release/bge.events.html #intro / Blender documentation team, thank you.

def motion(cont):
    ...
    allKeys = cont.sensors['AllKeys_01']
    ...
    for key,status in allKeys.events:
        if status == bl.KX_INPUT_JUST_ACTIVATED:
            if key == bge.events.WKEY:
                cont.activate("Motion_forward")
            if key ...
        if status == bl.KX_INPUT_JUST_RELEASED:
            if key == bge.events.WKEY:
                cont.deactivate("Motion_forward")
            if key ...

We use bl.KX_INPUT_JUST_ACTIVATED to activate with cont.activate(“Motion_forward”) the Motion actuator if the key is pressed/hold down, and we use bl.KX_INPUT_JUST_RELEASED to deactivate it with cont.deactivate(“Motion_forward”) if the key is released.

### How to use Python Keyboard ‘All keys’ events with the AND operator ###
Example :
if key == bge.events.ONEKEY and key == bge.events.IKEY: or if key == bge.events.ONEKEY and bge.events.IKEY:
I have had problems with both codes and for that reason I don’t use two or more of the Keyboard ‘All keys’ events for the combination of keys. If I need a combination with them, I use only one ‘All key’ event and for the other key(s) a ‘One key’ keyboard sensor(s).

### How to use Python Keyboard ‘All keys’ events with the OR operator ###
Example :
if inv.positive and key == bge.events.ONEKEY or key == bge.events.ZEROKEY:
It works correct.
if inv.positive and key == bge.events.ONEKEY or bge.events.ZEROKEY:
It doesn’t work correct.

In the next post we will use the inventory slots.
Greetings
achisp

Example :

def get_apple(cont):
    ...
        ...:
            for value in Invent_01:            # we loop through the inventory
                if Invent_01[value] == '':     # searching for an empty slot
                    Invent_01[value] = 'Apple' # replacing value '' with 'Apple'
                    ...
                    break # if found '' and replaced with 'Apple', we leave the loop

We loop through the inventory searching for an empty slot, if found, we replace ‘’ with ‘Apple’ and leave the loop with the break statement to don’t use more than one empty slot.The next two paragraphs are taken from: https://www.programiz.com/python-programming/for-loop #syntax_of_for_loop / Programiz, thank you.

for value in sequence:
    Body_of_for_loop

Here, value is the variable that takes the value of the item inside the sequence on each iteration. Loop continues until we reach the last item in the sequence. The body of for loop is separated from the rest of the code using indentation.

The next two paragraphs are taken from: https://www.programiz.com/python-programming/break-continue #example / Programiz, thank you.

>>> for val in "string":
...    if val == "i":
...        break
...    print(val)
...
s
t
r

The break statement terminates the loop containing it. Control of the program flows to the statement immediately after the body of the loop. If it is inside a nested loop (loop inside another loop), break will terminate the innermost loop.

NOTE : If we have more than one if statement in the body of the for loop, like p.e. in def interBox_01(cont): , the normal position of the break statement might be two indentations more than the for statement. But you can also experiment a little bit with it to see what happens. It could break out at different moments of the loop if set to different position.

### How to drop an item back to scene from an inventory slot ### Example :
def drop_apple(cont):
    ...
    # if the player tries to drop the item near to its origin
    ...
        ...
            for item in Invent_01.items():
                if item == (1, 'Apple'):
                    for key,status in allKeys.events:
                        if status == bl.KX_INPUT_JUST_ACTIVATED:
                            if key == bge.events.ONEKEY and inv.positive:
                                ...
                                Invent_01[1] = ''
                                ...
                                break


The next code is taken from (and adapted): https://www.programiz.com/python-programming/dictionary #python_dictionary_methods / Programiz, thank you.

for item in Invent_01.items(): and if item == (1, ‘Apple’):
We need to find the whole item to know the key which indicates which keyboard key / inventory slot corresponds to the searched value, here ‘Apple’ . To do so we use the items() function, which returns an items view in form of dict_items([(0, ‘Credit card’), (1, ‘Apple’), (2, ‘’), …]).
Then we ask if item == (1, ‘Apple’): . If this is true, i.e. the apple is in slot 1, the interpreter continues executing. If not, it leaves the greater if statement to go to the following statement at the same indentation level as the left one.

if key == bge.events.ONEKEY and inv.positive:
If the player has pressed the key combination I + 1 , which correspond to slot 1, he drops the apple.

Invent_01[1] = ‘’
We write ‘’ to the dictionary value to have an empty slot where the apple has been.

break
If the apple is dropped back we leave the loop with the break statement.

I deleted for this .blend from SimpleInventory_Demo_061.blend all code and Logic bricks related with stackable items and interaction with a container. I will add them in the next posts adapted for the slots. In the next post we start with making the items stackable in the inventory slots.
Greetings,
achisp

Attachments

SKeyInventory_Demo_01.blend (591 KB)

### Changing the structure of the inventory dictionary ###
Dear friends.
First we change in SKeyInventory_Demo_01.blend the structure of the dictionary Invent_01 .

Invent_01 = {0 : ['Credit card', 1], 1 : ['', 0], 2 : ['', 0], 3 : ['', 0], 4 : ['', 0],\
5 : ['', 0]}

We replace the string values of Invent_01 with lists. Each list contains the replaced string value and additional a integer value to count the string value, i.e. the item in the slot. If the slot is empty, we set the integer to 0 .
Now we can count the amount of each item in Invent_01 , and we can access them with the Subscription operator [] , using the keys of the dictionary and the indices of the list:

# syntax:
Invent_01[dict_key]
[list_index]

# examples:
Invent_01 = {0 : ['Credit card', 1], 1 : ['Apple', 3], 2 : ['', 0]}

Invent_01[1][1] # this is the position of the amount of apples, here 3
Invent_01[2][0] # this is the position of the empty slot ''

With the Subscription operator [] we can access the indices * of sequences **.NOTE * : The next paragraph is taken from the Python 2.5 tutorial: http://infohost.nmt.edu/tcc/help/pubs/lang/pytut/seq-index.html / New Mexico Tech, thank you.
Indexing the positions in a sequence : Positions are numbered from left to right starting at 0.

NOTE ** : The next paragraph is taken from: https://en.wikibooks.org/wiki/Python_Programming/Sequences / Wikibooks team, thank you.
Sequences allow you to store multiple values in an organized and efficient fashion. There are three kinds of sequences in Python: strings, lists and tuples. Dictionaries and sets are containers for sequential data.

Adding stackable items to the inventory ###

def get_apple(cont):
    ...
        ...
        if hitNameMouse == "Apple" and hitNameNear == "Near_apple_01":
            for value in Invent_01:
                if Invent_01[value][0] == "Apple": # this is a new block
                    Invent_01[value][1] += 1
                    ...
                    break

                if Invent_01[value] == ["", 0]: # this is changed
                    Invent_01[value] = ["Apple", 1] # this is changed
                    ...
                    break

if Invent_01[value][0] == “Apple”: and Invent_01[value][1] += 1 and break
If a list value on the first position (asked with the Subscription operator [] set to 0 ) is the string Apple, we add 1 to its amount and leave the loop with the break statement.NOTE : With our dictionary structure (see at the top of the previous chapter) Invent_01[value] accesses the value, which is a list, Invent_01[value][0] accesses the first position in that list and Invent_01[value][1] accesses the second position. So if we ask for ‘Apple’ , we need to indicate [value][0] . If we ask for [“Apple”, 1] we have to use [value] , and if we ask for the amount [value][1] .

if Invent_01[value] == ["", 0]: and Invent_01[value] = ["Apple", 1] and break If before 'Apple' isn't found in a list, we replace the (first) list ["", 0] with the list ["Apple", 1] . We leave the loop with the break statement.NOTE : The commentaries in the code tag above and in the following code tag are related with the changes from SKeyInventory_Demo_01.blend to SKeyInventory_Demo_02.blend.

Dropping stackable items to the scene ###

def drop_apple(cont):
    ...
    # if the player tries to drop the item near to its origin
    if Empty_apple_01['apple_01'] == False and nearAny.positive:
        ...
            if Invent_01[1][0] == 'Apple': # this is changed
                for key,status in allKeys.events:
                    if status == bl.KX_INPUT_JUST_ACTIVATED:
                        if key == bge.events.ONEKEY and inv.positive:
                            if Invent_01[1][1] >= 1: # this is changed and moved
                                ...
                                if Invent_01[1][1] >= 2: # this is new
                                    Invent_01[1][1] -= 1 # this is changed
                                else: # this is new
                                    Invent_01[1][1] -= 1 # this is new
                                    Invent_01[1][0] = '' # this is new
                                ...

if Invent_01[1][0] == ‘Apple’:
We ask for dictionary key 1 if ‘Apple’ is at the first place of its list. If true, we have related slot_01 with the Apple item.NOTE : we will need to ask also for the other slots, if there are more ‘get- and dropable items’ in game. This will happen in the next post.

if Invent_01[1][1] >= 2: and Invent_01[1][1] -= 1 and else: and Invent_01[1][1] -= 1 and Invent_01[1][0] = '' We subtract 1 from the amount, and if the amount has been 1 before the subtraction, we replace additional 'Apple' with ''.

In the next post we will start with the second inventory and its consequences.
Greetings
achisp

Attachments

SKeyInventory_Demo_02.blend (590 KB)

Dear friends.
First we add an inventory for the container Box_01 .

Invent_02 = {0 : ['Sandwich', 3], 1 : ['', 0], 2 : ['', 0]}

### The consequences caused by them ###
For get- and dropable items
Example def drop_apple(cont) :
In the previous .blend the apple could be only on one position in the inventory, but now with the interaction with a container all the items can be at every place in the inventories. So we have to ask for the apple at every possible slot in the player inventory and that causes fifteen lines extra code per possible slot.NOTE : If we have less items in the level than player inventory slots, we need only extra code equal to the amount of items.

Using here a for loop and conditional statements will not result shorter than using only conditional statements (so far I can see right now), because we have to relate the pressed key with the item in the slot to evaluate True only if the apple is in it and not another item.

### How to add a slot to an existing inventory ###
If we want to add an additional slot to an inventory, we can copy the last slot of that inventory, open a new line beneath and paste it there. Then we change in the pasted block the dictionary key numbers of the changed inventory (but not of the other inventory there) to the new slot number and the name of the keyboard key to the new key name to have a new usable slot.
Furthermore we have to add to every slot of all the inventories to interact with this new slot (that means to the inventory where we add the slot we don’t add the expression) in def interBox the expression
Syntax :

and Invent_with_new_slot[new_slot_key_number][0] !=\
Invent_where_you_add_expression[slot_key_number][0]

at the end of the line under the statement line if Invent_with_new_slot[val][0] not in items: .
Don’t add more or less of this expressions than slots in the correspondent inventory, that means we need one expression for each slot of the inventory to interact with.NOTE : For further information see beneath ### Comparing two interacting inventories ###.

### Comparing two interacting inventories ### Example :
def interBox_01(cont):
    ... ...
            """ container ZEROKEY """
            ...
                if Invent_01[val][0] not in items:
                    if Invent_01[0][0] != Invent_02[0][0] and\
                    Invent_01[1][0] != Invent_02[0][0] and Invent_01[2][0] != Invent_02[0][0]\
                    and Invent_01[3][0] != Invent_02[0][0]:
                        ...

if Invent_01[0][0] != Invent_02[0][0] and Invent_01[1][0] != Invent_02[0][0] and …:
Here we compare the item name in each slot of Invent_01 one by one with the item name of the zero slot of Invent_02 to know if none of the items of Invent_01 is identical with the one of the zero slot of Invent_02 . If True, ie. they are not identical, we go on in this body. If False, we abort. We ask this to don’t insert while looping an item in an empty slot if that item already occupies a slot in that inventory.
This statement is one time in each ‘key/slot code’ of def interBox , exact after asking for an empty slot in the inventory to interact with (in this example: if Invent_01[val][0] not in items: ).NOTE 1 : The next paragraph is taken from: http://openbookproject.net/thinkcs/python/english3e/variables_expressions_statements.html Variables, expressions and statements#evaluating_expressions / ‘How to Think Like a Computer Scientist’ team, thank you.
An expression is a combination of values, variables, operators, and calls to functions. If you type an expression at the Python prompt, the interpreter evaluates it and displays the result.

>>> 5 != 7
True

NOTE 2 : != is the ‘not equal to’ operator. An expression containing it evualates to True if the operands are not equal.
NOTE 3 : The next paragraph is taken from: https://python.swaroopch.com/basics.html A byte of Python - Basics#logical_and_physical_line / Mr. Swaroop, thank you.
If you have a long line of code, you can break it into multiple physical lines by using the backslash. This is referred to as explicit line joining.
The backslash </b> breaks a code line to continue the code on the next line, but the interpreter reads and executes this backslash separated lines as one. They have to be all on the same indentation level. We use it to make the code more readable if a line is to long to fit on the text editor screen. So the entire line is easier to read than using the scroll border of the editor or the arrow key for the cursor to move.

### How to add a new item to the game ### We have three kinds of "inventory items": The getable item is spawned to the scene and the player can put it in his inventory by clicking on it. It stays in the inventories and doesn't go back to the scene. The get- and dropable item is also spawned to the scene and can also be putted it in the inventory by clicking on it. Additional it can be dropped back to the scene at certain points. The internal item has never been in the scene and will never be there. It exists only in the inventory.
  • If we add any of them as new item to the game, we have to add its name to the list of the variable items in def interBox , which should contain all items in game.
  • If we add an internal item, we have to create additional to the point mentioned before a new slot to put the new item in, if there isn’t an empty slot to hold it. To see how to add a slot see above ### How to add a slot to an existing inventory ###.
  • If we add a getable or a get- and dropable item, we need to create additional to the first point mentioned above its def item_add(cont): , its def drop_item(cont): (only for dropable items) , its empty_item to spawn it to (it runs the SKeyInv.item_add function), its object on the second layer to send its copies to the scene and the near_item (only for dropable items) object to be detected by the near sensor.

NOTE 1 : I have written in “How to make a simple Adventure game inventory” about the get- and dropable item. See there https://blenderartists.org/forum/showthread.php?412199-How-to-make-a-simple-adventure-game-inventory&p=3130146#post3130146 ### Foreword to the get object from the scene to the inventory procedure ###, ### Adding an object to the scene from layer 2 ###, ### Dropping an object from the inventory back to its origin ###, ### Dropping an item of the inventory to more than one place ### and ### How to get a certain “added object”, part 1 + 2 ###.
NOTE 2 : For near_item we use a plane and enable in the Property editor -> Physics tab the Actor and Collision bounds to make it detectable by the Near sensor. And we enable Invisible to restrict that it is rendered in the game.

NOTE : If you have add an item using the hints above and it don’t works (properly), verify if you have not added a new slot incorrectly. See chapter ### How to add a slot to an existing inventory ###. The amount of slots have to be synchronous with the existing code, that means here that every slot needs to be covered individually by certain statements of the code.

In the next post we will see how to add items to Box_01.
Greetings,
achisp

def interBox_01(cont):
    ...
    items = ['Credit card', 'Apple', 'Sandwich']
    ...

First we add a new variable named items to hold all the ‘inventory items’ in game. We will use it in this function (see beneath the next code tag, in if Invent_01[0][0] in items: ).
We have to actualize it if we introduce new item(s) in the game.

### How to add items from the player inventory to Box_01 ###

def interBox_01(cont):
    ...
        ...
            """ inventory ZEROKEY """   # we have to do it for every slot. →
                                        # Inventory ZEROKEY = inventory slot 0
            for val in Invent_02:
#                print('inv 0-0- ' + str(val)) # uncomment it to see the iteration

                if Invent_02[val][0] == Invent_01[0][0]:

                    if Invent_01[0][0] in items:
                        for key,status in allKeys.events:
                            if status == bl.KX_INPUT_JUST_ACTIVATED:
                                if key == bge.events.ZEROKEY:
                                    Invent_02[val][1] += 1
                                    ... # the same code to actualize the inventories →
                                        # as in def drop_apple(cont) | see previous post
                                    HUD_text_1['Text'] = textwrap.fill("Box_01: " + str(sorted(Invent_02.items(),\
                                    key=lambda t: t[0])), 75) # prints the actual inventory to screen
                    break
        ... # continuation beneath

Please read also the commentaries in the code tag from above.
→ means the commentary goes on on the next line.

if Invent_02[val][0] == Invent_01[0][0]:
We ask if an item name ( [val][0] ) of the inventory of Box_01 ( Invent_02 ) is the same as the one of player inventory slot 0 ( Invent_01[0][0] ). If True, the loop continues, if false, it leaves this body and the execution goes on at the beginning of the next loop statement, where the asked item name from above is inserted in the inventory of Box_01 and the correspondent values will be actualized, if there is an empty slot. For the code of the last mentioned loop see beneath the next code tag.

if Invent_01[0][0] in items:
If not, the player will use the key of an empty slot and nothing will happens. We have defined items before (see above ### How to interact with Box_01 ###) and it should contain all items of the game.
If we don’t ask this, and the player presses a key of an empty slot of the player inventory, the loop will continue (with this if statement it won’t) and if there is also an empty slot in Box_01, the loop will add one to the amount of the first empty Box_01 slot (in the loop), but we don’t want this.

Invent_02[val][1] += 1
The loop will add one to the amount of the Box_01 slot which holds the found item name.

break
If the item name is found and the loop has reached the end of its body (and with this the break statement), it will leave instead of continue looping. So we will add only one to the amount of the found item, and not more, one in each following loop.
Note that the break statement is two indents higher than the for statement. So it breaks the first if statement.

def interBox_01(cont):
                    ... # continuation from above
            for val in Invent_02:

                if Invent_02[val][0] not in items:
                    if Invent_02[0][0] != Invent_01[0][0] and\
                    Invent_02[1][0] != Invent_01[0][0] and Invent_02[2][0] != Invent_01[0][0]:

                        if Invent_01[0][0] in items:
                            for key,status in allKeys.events:
                                if status == bl.KX_INPUT_JUST_ACTIVATED:
                                    if key == bge.events.ZEROKEY:
                                        Invent_02[val][0] = Invent_01[0][0]
                                        Invent_02[val][1] = 1
                                        ... # essentially the same as in the previous code tag
                    break

            """ inventory ONEKEY """ # the next inventory slot
            ...

if Invent_02[val][0] not in items:
Here we ask for an empty slot in Box_01.

if Invent_02[0][0] != Invent_01[0][0] and Invent_02[1][0] != Invent_01[0][0] and …:
And we ask if none of the items of Box_01 is the one of the zero slot of the player inventory. If True, ie. it is not present, we go on in this body. If we have inserted it in the previous loop or it has been there before, we abort.
We ask this to don’t insert while looping an item in an empty slot if that item already occupies a slot in that inventory.

if Invent_01[0][0] in items:
If the zero slot of the player inventory isn’t empty. We ask it to do not add an empty slot and increase its amount.

Invent_02[val][0] = Invent_01[0][0] and Invent_02[val][1] = 1
We write in the (first) empty Box_01 slot the name of the item from the zero slot of the player inventory and we overwrite 0 with 1 in the (previous) empty Box_01 slot. Now we have one item from the player inventory zero slot in Box_01.

if Invent_01[1][1] >= 2: and Invent_01[1][1] -= 1 and else: and Invent_01[1][1] -= 1 and Invent_01[1][0] = ‘’
The interpreter goes on and subtract 1 from the amount in the player inventory, and if the amount has been 1 before the subtraction, we replace additional the item name with ‘’ to have an empty slot.

break
If the item is transferred to Box_01 and the loop has reached the end of its body (and with this the break statement), it will leave instead of continue looping. So we will add only one item, and not more in the following loop(s).

### How to add items from Box_01 to the player inventory ###
The principle is the same as for adding items from the player inventory to Box_01 and the code is essentially the same, but the two inventories are in a reverse succession.

Greetings
achisp

Revision history:
2017-01-13.: SKeyInventory_Demo_031.blend

  • (1.) Correction of counting the items in def drop_apple in both if Invent_01[2][0] == ‘Apple’: statements, i.e. for Empty_apple_01 and for Empty_apple_02 , (2.) Synchronization of the amount of slots which the existing code, (3.) Added one item to Box_01 and the correspondent code to the module file.

    2017-01-12.: SKeyInventory_Demo_03.blend

Attachments

SKeyInventory_Demo_031.blend (648 KB)