Ren'Py Inventory items stackable

JOJO7777

Newbie
Sep 6, 2017
31
57
Hi guys, I'm trying to make an inventory that shows the available quantities of an owned item(number of quantities in the inventory screen in the bottom corner of the item).

This is the code:

Code:
init python:

    class Item(object):
        def __init__(self, name, cost=0, img="", description="", h_img=""):
            self.name = name
            self.cost = cost
            self.img = img
            self.description = description
            self.h_img = h_img

    class Inventory(object):
        def __init__(self, money=0):
            self.items = []
            self.money = money

        def get_item(self, item):
            self.items.append(item)

        def buy_item(self, item):
            if money >= item.cost:
                self.items.append(item)
                self.money -= item.cost

        def drop(self, item):
            self.items.remove(item)

        def earn_money(self, amount):
            self.money += amount

        def withdraw_money(self, amount):
            self.money -= amount
From this point on I don't know how to move... I've tried to help myself with codes from other users but I can't get to the bottom of it...

My inventory screen:

Code:
screen inventory_window():
    zorder 100
    modal True
    tag gg_inventory_window
    key "mouseup_2" action Hide("nonexistent_screen")
    key "h" action Hide("nonexistent_screen")
    add "gg_dev"
    add "inv_box"
    add "inv_item_desc"

    vpgrid:
        cols 8
        rows 18
        xpos 565 ypos 310 spacing 9
        draggable True
        mousewheel False
        ysize 320
        yinitial 0
        scrollbars "vertical"
        side_spacing 20

        for Item in inventory.items:
            frame:
                style "bagslot"
                imagebutton:
                    idle Item.img
                    hover Item.h_img
                    if hovered_item == False:
                        hovered [SetVariable("hovered_item", True), SetVariable("selected_name", Item.name), SetVariable("selected_description", Item.description)]
                    else:
                        unhovered [SetVariable("hovered_item", False), SetVariable("selected_name", ""), SetVariable("selected_description", "")]
                    action NullAction()

        for i in range(len(inventory.items), 250):
            frame:
                style "bagslot"
Thanks in advance for the help!
 
Last edited:

gojira667

Member
Sep 9, 2019
264
245
Hi guys, I'm trying to make an inventory that shows the available quantities of an owned item(number of quantities in the inventory screen in the bottom corner of the item).
...
From this point on I don't know how to move... I've tried to help myself with codes from other users but I can't get to the bottom of it...
Seeing as I was just playing Adventurer Trainer and it has a working inventory screen with stackable items, it might be worth taking a look at.

I can't speak to their overall design, but a quick look shows that consumables are the stackables. In this instance they are using a dict with the id/name as the key and the value as its count.

Which doesn't seem a half bad way of doing it. Using a dict/list/etc to store the item as one element and its associated total as the other.
 
  • Like
Reactions: Jman9

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,576
2,203
The difficulty isn't just creating the code, but also that you understand it enough to be able to continue to work on it.

I'm going to assume "stackable" just means having more than 1 of the item.

Taking your existing code, you're probably going to need to expand things to include a count of the number of items.

So...

Python:
    class Item():
        def __init__(self, name, cost=0, img="", description="", h_img=""):
            self.name = name
            self.cost = cost
            self.img = img
            self.description = description
            self.h_img = h_img

... becomes...

Python:
    class Item():
        def __init__(self, name, cost=0, itemcount=0, img="", description="", h_img=""):
            self.name = name
            self.cost = cost
            self.itemcount = itemcount
            self.img = img
            self.description = description
            self.h_img = h_img

The class Inventory() takes care of itself, as it is just a list of items plus a money variable/property.

The functions/methods belonging to the Inventory() class will need expanding to allow the game to keep track of the new itemcount, as well as adjust it according to how the item is being picked up, dropped, sold or bought.

So...

Python:
        def get_item(self, item):
            self.items.append(item)

... could become something like...

Python:
        def get_item(self, item, count=1):
        
            match_found = False
        
            for i in self.items:
                if i.name == item.name:
                    i.itemcount += count
                    match_found = True

            if match_found == False:
                self.items.append(item)
                self.items[-1].itemcount += count

In part, a lot of this extra code is to allow for the game to use .get_item() more than once for the same item.
If the item is already in the inventory, the item's itemcount is increased by however many count says (defaulting to 1). If the item wasn't already in the inventory, it is added to the list and the itemcount for the last item in the list (-1 ... ie. the one we just added) is increased.

Similarly, buy_item() would change from...

Python:
        def buy_item(self, item):
            if money >= item.cost:
                self.items.append(item)
                self.money -= item.cost

... to...

Python:
        def buy_item(self, item, count=1):

            if money >= (item.cost * count):
                match_found = False
            
                for i in self.items:
                    if i.name == item.name:
                        i.itemcount += count
                        match_found = True

                if match_found == False:
                    self.items.append(item)
                    self.items[-1].itemcount += count
                
                self.money -= (item.cost * count)

Personally, I'm not a fan of that code... since it doesn't offer any feedback if the player doesn't have enough money. I presume the screen takes care to make sure the player can't buy stuff they can't afford.

Next, the code allows the player to drop an item. But perhaps we're carrying 10. Or perhaps the player wants to drop 5, but we're only carrying 2. Again, we'll assume the screen takes care of that kind of shit...

Python:
        def drop(self, item):
            self.items.remove(item)

... might become...

Python:
        def drop(self, item, count):

            for i in self.items:
                if i.name == item.name:
                    i.itemcount -= count

                    if i.itemcount <= 0:
                        self.items.remove(item)

In this case, we're just changing things so that the item is only removed from the list if the itemcount drops to zero (or below zero). Otherwise, we just reduce itemcount by the relevant amount.

Beyond this, it would just be a case of changing the screen to somehow show the itemcount property for each item shown in the inventory. I haven't even looked at the screen code.

I hope you can see that adding a sell_item() could be relatively easy. Though that might not be needed, depending upon your game. I haven't added it here, as I figure it could be a good test of whether you've understood the concepts here if you could figure it out yourself.
 
Last edited:

Jman9

Engaged Member
Jul 17, 2019
2,295
957
There are actually at least two ways to go about stackable items in inventories: making items stackable on their own and adding a stack of items to the inventory, or stacking them within the inventory. IMO, the latter is more general, because then different inventories can do their own, possibly different stacking (like not allowing certain items in (specialty shops), having individual stacking limits (carrying capacity), etc).

You can do this by e.g. having the contents in an an inventory come as a dictionary of (item, stack size) pairs, as pointed out before. Dictionaries have the advantage of being faster than both lists and the 'iterate over everything' approach above. Probably won't matter if you're not going to allow a great variety of items, e.g. there being 200 types of flowers and the player being able to amass whole mountains of them.
 
Last edited:
  • Like
Reactions: gojira667

JOJO7777

Newbie
Sep 6, 2017
31
57
The difficulty isn't just creating the code, but also that you understand it enough to be able to continue to work on it.

I'm going to assume "stackable" just means having more than 1 of the item.

Taking your existing code, you're probably going to need to expand things to include a count of the number of items.

So...

Python:
    class Item():
        def __init__(self, name, cost=0, img="", description="", h_img=""):
            self.name = name
            self.cost = cost
            self.img = img
            self.description = description
            self.h_img = h_img

... becomes...

Python:
    class Item():
        def __init__(self, name, cost=0, itemcount=0, img="", description="", h_img=""):
            self.name = name
            self.cost = cost
            self.itemcount = itemcount
            self.img = img
            self.description = description
            self.h_img = h_img

The class Inventory() takes care of itself, as it is just a list of items plus a money variable/property.

The functions/methods belonging to the Inventory() class will need expanding to allow the game to keep track of the new itemcount, as well as adjust it according to how the item is being picked up, dropped, sold or bought.

So...

Python:
        def get_item(self, item):
            self.items.append(item)

... could become something like...

Python:
        def get_item(self, item, count=1):
       
            match_found = False
       
            for i in self.items:
                if i.name == item.name:
                    i.itemcount += count
                    match_found = True

            if match_found == False:
                self.items.append(item)
                self.items[-1].itemcount += count

In part, a lot of this extra code is to allow for the game to use .get_item() more than once for the same item.
If the item is already in the inventory, the item's itemcount is increased by however many count says (defaulting to 1). If the item wasn't already in the inventory, it is added to the list and the itemcount for the last item in the list (-1 ... ie. the one we just added) is increased.

Similarly, buy_item() would change from...

Python:
        def buy_item(self, item):
            if money >= item.cost:
                self.items.append(item)
                self.money -= item.cost

... to...

Python:
        def buy_item(self, item, count=1):

            if money >= (item.cost * count):
                match_found = False
           
                for i in self.items:
                    if i.name == item.name:
                        i.itemcount += count
                        match_found = True

                if match_found == False:
                    self.items.append(item)
                    self.items[-1].itemcount += count
               
                self.money -= (item.cost * count)

Personally, I'm not a fan of that code... since it doesn't offer any feedback if the player doesn't have enough money. I presume the screen takes care to make sure the player can't buy stuff they can't afford.

Next, the code allows the player to drop an item. But perhaps we're carrying 10. Or perhaps the player wants to drop 5, but we're only carrying 2. Again, we'll assume the screen takes care of that kind of shit...

Python:
        def drop(self, item):
            self.items.remove(item)

... might become...

Python:
        def drop(self, item, count):

            for i in self.items:
                if i.name == item.name:
                    i.itemcount -= count

                    if i.itemcount <= 0:
                        self.items.remove(item)

In this case, we're just changing things so that the item is only removed from the list if the itemcount drops to zero (or below zero). Otherwise, we just reduce itemcount by the relevant amount.

Beyond this, it would just be a case of changing the screen to somehow show the itemcount property for each item shown in the inventory. I haven't even looked at the screen code.

I hope you can see that adding a sell_item() could be relatively easy. Though that might not be needed, depending upon your game. I haven't added it here, as I figure it could be a good test of whether you've understood the concepts here if you could figure it out yourself.
Sorry for the delay in responding... I thank you all for the help! With your code everything works perfectly!!
 
Aug 10, 2018
2
0
Ok, since there's already a post about stackable items, I'd like to ask for help. I was looking for an RPG inventory code and I've found this. This code is perfect for what I want to do, and I was already able to make some changes to adapt it to my purpose. Problem is, despite my attempts, I couldn't make the consumable items stackable. Someone could give me an hand please?
Here's the original code:




Python:
init python:

    class Player:
        def __init__(self, hp, mp, atk, defense, mdef,level=1):
            self.hp = hp
            self.mp = mp
            self.max_hp = hp
            self.max_mp = mp
            self.atk = atk
            self.defense =defense
            self.mdef =mdef
            self.level = level
            self.weapon = None
            self.armor = {"head": None, "chest": None, "acc" : None,"shield":None}


        def addHP(self,amount):
            self.hp += amount
            if self.hp > self.max_hp:
                self.hp = self.max_hp



        def addMP(self,amount):
            self.mp += amount
            if self.mp > self.max_mp:
                self.mp = self.max_mp

        def equip_weapon(self,weapon):
            if self.weapon != None:
                self.unequip_weapon()

            self.weapon = weapon
            self.atk += weapon.atk

        def unequip_weapon(self):
            if self.weapon != None:
                self.atk -= self.weapon.atk
                self.weapon = None


        def equip_armor(self,armor, slot):
            if self.armor[slot] != None:
                self.unequip_armor(slot)
            self.armor[slot] = armor
            self.defense += armor.defense
            self.mdef += armor.mdef


        def unequip_armor(self,slot):
            if self.armor[slot] != None:
                self.defense -= self.armor[slot].defense
                self.mdef -= self.armor[slot].mdef
                self.armor[slot] = None



Inventory:


Python:
init python:

    class InventoryItem:
        def __init__(self, img, value):
            self.img = img
            self.value = value


    class Consumable(InventoryItem):
        def __init__(self,img,value,hp_gain,mp_gain):
            InventoryItem.__init__(self,img,value)
            self.hp_gain = hp_gain
            self.mp_gain = mp_gain

        def use(self, target):
            inventory.remove(self)
            target.addHP(self.hp_gain)
            target.addMP(self.mp_gain)
            global selected_item
            selected_item = None


    class Equipable(InventoryItem):
        def __init__(self, img, value):
            InventoryItem.__init__(self, img, value)
            self.is_equipped = False
            self.equipped_to = None


        def equip(self, target):
            self.is_equipped = True
            self.equipped_to = target

        def unequip(self):
            self.is_equipped = False
            self.equipped_to = None



    class Weapon(Equipable):
        def __init__(self, img, value, atk, wpn_type):
            Equipable.__init__(self, img, value)
            self.atk = atk
            self.wpn_type = wpn_type


        def equip(self,target):
            Equipable.equip(self, target)
            target.equip_weapon(self)

        def unequip(self):
            self.equipped_to.unequip_weapon()
            Equipable.unequip(self)


    class Armor(Equipable):
        def __init__(self, img, value, defense, mdef,slot):
            Equipable.__init__(self, img, value)
            self.defense = defense
            self.mdef =mdef
            self.slot = slot


        def equip(self,target):
            Equipable.equip(self, target)
            target.equip_armor(self, self.slot)

        def unequip(self):
            self.equipped_to.unequip_armor(self.slot)
            Equipable.unequip(self)


    class KeyItem(InventoryItem):
        def __init__(self, img):
            InventoryItem.__init__(self, img, 0)

Inventory Screen:


Python:
 style inventory_label:
        xalign 0.2

    style slot:
        background Frame("square",0,0)
        minimum(80,80)
        maximum(80,80)
        xalign 0.5

    screen inventory_screen:
        style_prefix "inventory"



        add "white"
        hbox:


            vbox:
                xmaximum 300
                spacing 10
                label "Level [pc.level]"
                label "Character Details" xalign 0.5
                label "HP: [pc.hp]/[pc.max_hp]"
                label "MP: [pc.mp]/[pc.max_mp]"
                label "Attack: [pc.atk]"
                label "Defense: [pc.defense]"
                label "MDefense: [pc.mdef]"


                frame:
                    style "slot"
                    if pc.weapon != None:
                        add pc.weapon.img
                    else:
                        label "weapon" xalign 0.5 yalign 0.5 text_size 15

                frame:
                    style "slot"
                    if pc.armor["head"] != None:
                        add pc.armor["head"].img
                    else:
                        label "head" xalign 0.5 yalign 0.5 text_size 15

                frame:
                    style "slot"
                    if pc.armor["chest"] != None:
                        add pc.armor["chest"].img
                    else:
                        label "chest" xalign 0.5 yalign 0.5 text_size 15

                frame:
                    style "slot"
                    if pc.armor["acc"] != None:
                        add pc.armor["acc"].img
                    else:
                        label "accessory" xalign 0.5 yalign 0.5 text_size 15


                frame:
                    style "slot"
                    if pc.armor["shield"] != None:
                        add pc.armor["shield"].img
                    else:
                        label "shield" xalign 0.5 yalign 0.5 text_size 15


            grid 10 7:
                yalign 0.2
                spacing 5
                for item in inventory:
                    frame:
                        style "slot"
                        if isinstance(item, KeyItem):
                            add "bg keyitem"
                        imagebutton idle item.img action SetVariable("selected_item", item)


                for i in range(len(inventory),70):
                    frame:
                        style "slot"

            vbox:
                spacing 10
                label "Current Item" xalign 0.5
                if selected_item != None:
                    frame:
                        style "slot"
                        if isinstance(selected_item, KeyItem):
                            add "bg keyitem"
                        add selected_item.img

                    label "Value: [selected_item.value]"

                    if isinstance(selected_item, Consumable):
                        textbutton "Use" action Function(selected_item.use, pc)
                    if isinstance(selected_item, Equipable):
                        if selected_item.is_equipped:
                            textbutton "Unequip" action Function(selected_item.unequip)
                        else:
                            textbutton "Equip" action Function(selected_item.equip,pc)
                    if not isinstance(selected_item, KeyItem):
                        textbutton "Discard" action RemoveFromSet(inventory, selected_item), SetVariable("selected_item", None)



        textbutton "Return":
            action Return()
            xalign 0.5
            yalign 0.95
and script file:


Python:
define player = Character("Rufus",color="#c8c8ff")



default inventory = []
default selected_item = None
default pc = Player(15,10,5,3,2,1)
default sword_item = Weapon("sword", 10, 5, "sword")
default burger =Consumable("burger",50,100,0)
default cauldron_item =KeyItem("cauldron")
default shield_item = Armor("armor shield",15,5,4,"shield")
default chest_item = Armor("armor chest", 10,4, 5, "chest")
default helm_item = Armor("armor helm", 10,3, 5, "head")



# The game starts here.

label start:

$inventory.append(sword_item)
$inventory.append(burger)
$inventory.append(cauldron_item)
$inventory.append(shield_item)
$inventory.append(chest_item)
$inventory.append(helm_item)
call screen inventory_screen



return
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,171
Problem is, despite my attempts, I couldn't make the consumable items stackable.
Because there's no inventory in all this code. Just a chest (the "inventory" list) where you throw the owned items.

79flavors post is where the answer to you problem lie. Use/adapt his inventory and you'll have stackable items.