Doubt with renpy inventory system

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
Hello, we have still trouble with this nice and near 100% working and customize able inventory system over here on "lemmasoft.renai.us": and need the help of you profesionals from "f95zone". The problem that is not solved yet is:"...gained items in game will vanish after load a savegame to continue play. The demo by "Leon" who made this code has: "init -1 python:" and after the "label start:" the " python:" and no "inventory = Inventory()" and save/load is working. Also has starting items. Strange, what we have done wrong? " And:"...how to get the Items stay in the inventory after starting the game new/and load a save game? I still not could find out, it has something to do with "Rollback" function. Do I need to shut off Rollback during the scene when a item is supposed to be used? Did your items persist? " And so on. Who can help with this? What exactly has to be add to make ,(and how to write this) the items stay in the inventory after reload the game. And if doing "Rollback"?
Er... It will be difficult to answer with just this, because it say nothing about what you're effectively doing, and I'm not really sure to understand if there's another problem than the save/load one. There's the code you posted on the Ren'py's official forum, but without indentation it's really hard to understand it.
Still one thing is obvious :
Code:
init -1 python:
    inventory = Inventory()
    startingitem = Item("Space suit", keyitem="Ssuit_Elie", image="gui/inv_Ssuit_Elie.png")
    chocolate = Item("Chocolate", image="gui/inv_chocolate.png")
    banana = Item("Banana", image="gui/inv_banana.png") 
    specialoil = Item("Special Oil", image="gui/inv_Special_Oil.png")
While it doesn't matter for the items, it's normal that you loose the inventory by doing this.
Like said in the documentation, object created in an init block are not saved ; and if they aren't saved, they obviously can't be loaded.
What you need is to do this :
Code:
init -1 python:
    startingitem = Item("Space suit", keyitem="Ssuit_Elie", image="gui/inv_Ssuit_Elie.png")
    chocolate = Item("Chocolate", image="gui/inv_chocolate.png")
    banana = Item("Banana", image="gui/inv_banana.png") 
    specialoil = Item("Special Oil", image="gui/inv_Special_Oil.png") 

default inventory = Inventory()
Then, the inventory will be saved.
 
  • Like
Reactions: M-77 and Epadder

M-77

Newbie
Sep 18, 2017
82
22
Thanks for the quick response. now I get this error:
You don't have permission to view the spoiler content. Log in or register now.
Line 185 is [masteryet=False]
and 186 is [for m,n in self.sort_layer:] is code for a Paralax effect and nothing about the inventory:
You don't have permission to view the spoiler content. Log in or register now.
Maybe the code to show the Inventory button is misleading now, because it has "Inventory" in it?:
You don't have permission to view the spoiler content. Log in or register now.
And I also not defined the items inside the "classes" section. Because I use hardcoding. Because I have no clue how this working. because I not understand, because the new RenPy wiki is not understandable for me in this case.
You don't have permission to view the spoiler content. Log in or register now.
But I like to learn and find out why this is not working. If not I can think out/use a pseudo inventory using different pictures showing up. Depending on what items are in possesion. And what if the "init -1" stuff is removed? Then the "add/remove" with the "$" Phyton symbol will not work with the inventory?
 
Last edited:

M-77

Newbie
Sep 18, 2017
82
22
Thank you this worked! I changed the code for the Inventory icon display from: "Inventory = 0" to "Inventar = 0" and now the items stay after quit game and loading a savegame. Only the "Rollback" gives me still this error:
You don't have permission to view the spoiler content. Log in or register now.
Logicaly, because the item was removed during gameplay event. How to prevent this bug to occur? Block "Rolback" with "NoRollback" command in the script, depending on the event? Or there was some variable to made RenPy to remember (and this way to save the items in memory) what was removed? To be able made them pop up back again if rolling the mousewheel back. I will be fine with only stop the "Rollback" for this part in my script. And how to activate it backafter this? It should stop rolling back after the event when the item was used (removed from inventory). This is interesting and confusing. Always much to try out.
 

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
Since I started this thread, although there are still some details to review, I would like to leave here the code I finally used:

Code:
init -1 python:
    from operator import attrgetter

    inv_page = 0
    item = None
    class Player(renpy.store.object):
        def __init__(self, name, element=None):
            self.name=name
            self.element=element
    player = Player("Tony")
    
    class Item(store.object):
        selected = False
        def __init__(self, name, player=None, element="", image=""):
            self.name = name
            self.player=player
            self.element=element
            self.image=image
        def use(self):
            player.element=self.element

    class Inventory(store.object):
        def __init__(self):
            self.currentItem = None
            self.items = []
        def add(self, item):
            self.items.append(item)
        def drop(self, item):
            self.items.remove(item)
    
    class selectItem(Action):
        
        def __init__(self, object):
            self.object = object
        
        def __call__(self):
            new_value = not self.object.selected
            for item in inventory.items:
                setattr(item, "selected", False)
            store.active_item = None
            setattr(self.object, "selected", new_value)
            if (new_value):
                store.active_item = self.object
            renpy.restart_interaction()
        
        def get_selected(self):
            return self.object.selected
    
    def item_use():
        item.use()

    #Tooltips:
    style.tips_top = Style(style.default)
    #style.title.font="gui/arial.ttf"
    style.tips_top.size=14
    style.tips_top.color="fff"
    style.tips_top.outlines=[(3, "6b7eef", 0,0)]
    style.tips_top.kerning = 5

    style.tips_bottom = Style(style.tips_top)
    style.tips_top.size=20
    style.tips_bottom.outlines=[(0, "6b7eef", 1, 1), (0, "6b7eef", 2, 2)]
    style.tips_bottom.kerning = 2
    
    style.button.background=Frame("gui/frame2.png",25,25)
    style.button.yminimum=52
    style.button.xminimum=52
    style.button_text.color="000"

            
screen inventory_screen:    
    add "gui/inventory.png"
    modal True
    imagebutton auto "nav/exit_inv_%s.webp" xpos 930 ypos 10 xalign 1.0 focus_mask True action [ Hide("inventory_screen")]


    $ x = 515
    $ y = 0
    $ i = 0
    $ sorted_items = sorted(inventory.items)
    $ next_inv_page = inv_page + 1            
    if next_inv_page > int(len(inventory.items)/9):
        $ next_inv_page = 0
    for item in sorted_items:
        if i+1 <= (inv_page+1)*9 and i+1>inv_page*9:
            $ x += 270
            if i%3==0:
                $ y += 235
                $ x = 515
            $ pic = item.image
            imagebutton idle pic hover pic xpos x+300 ypos y+110 action [Hide("gui_tooltip"), SetVariable("item", item), selectItem(item)] hovered [ Play ("sound", "sfx/click.wav") ] unhovered [Hide("gui_tooltip")] at inv_eff
            if item.selected:
                add "gui/selected.png" xpos x+200 ypos y+70 anchor(.5,.5)
                $ selitem = (item_use)
            
        $ i += 1
        if len(inventory.items)>9:
            textbutton _("Next Page") action [SetVariable('inv_page', next_inv_page), Show("inventory_screen")] xpos .475 ypos .83

screen gui_tooltip (my_picture="", my_tt_xpos=58, my_tt_ypos=687):
    add my_picture xpos my_tt_xpos ypos my_tt_ypos


init -1:
    
    transform inv_eff:
        zoom 1.0 xanchor 1.0 yanchor 1.0
        on idle:
            linear 0.2 alpha 0.5
        on hover:
            linear 0.2 alpha 2.5
Code:
label start:
    
    default inventory = Inventory()
    
    default rosa = Item("Rosa", element="rosa", image="images/shop/items/Rosa_idle.png")
    default camerareflex = Item("camerareflex", element="camerareflex", image="shop/items/CameraReflex_idle.png")
    #etc...
To add the object purchased/obtained to the inventory use:
Code:
$inventory.add(rosa)
To remove an object from inventory use:
Code:
$inventory.drop(rosa)
To check if an object of the inventory is selected and to be able to use it, for example, in a conditional command, I use:
Code:
if rosa.selected:
M-77, I remember having the same problem that you describe using the base code, the purchased objects were not saved in the save files; with the code I have written above this was resolved, I think what solved it was to move the line "default inventory = Inventory()".
Keep in mind, however, that the code I've written is adapted to my game, and I removed some unnecessary things from the example base code.


NOTE: The code is functional and serves me for the purpose of the game, but surely it can be done better or with more functionalities (I still find it hard to understand how phyton works... but I try :p)
 

the66

beware, the germans are cumming
Modder
Respected User
Donor
Jan 27, 2017
7,581
23,477
snippet from

Variables that have their value set in an init python block are not saved, loaded, and do not participate in rollback. Therefore, these variables should not be changed after init is over.

or simplified:
declared at init level or per define -> use it for constants, it won't get saved​
declared within a label or per default -> use it for variables, the object will be saved and participates in rollback/forward​
 
  • Like
Reactions: M-77 and Porcus Dev

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
NOTE: The code is functional and serves me for the purpose of the game, but surely it can be done better or with more functionalities (I still find it hard to understand how phyton works... but I try :p)
It's a good code. You could have put everything (except the items) in a single object, but what matter is that you understand the code you use, not that it's a pure and beautiful code.
 

M-77

Newbie
Sep 18, 2017
82
22
It is working now. I have changed the list, adding the "default" and removing the "init -1" stuff. After the "label start:".
 

Acers0

New Member
Jan 3, 2018
13
4
Hi. I have a question if someone is kind enough to help me solve this problem:

So, I am trying to implement the inventory system and everything was working correctly until I tried to make the same items stack on each other instead of taking multiple spaces in the inventory and now I am getting this Error and I can't fix it for days. If anyone has any clue I will be very glad.


This is the error:
Python:
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 146, in script call
    "Look! An inventory button!"
  File "game/script.rpy", line 294, in script
    call screen inventory_screen
  File "renpy/common/000statements.rpy", line 531, in execute_call_screen
    store._return = renpy.call_screen(name, *args, **kwargs)
  File "game/inventory.rpy", line 224, in execute
    screen inventory_screen:
  File "game/inventory.rpy", line 224, in execute
    screen inventory_screen:
  File "game/inventory.rpy", line 246, in execute
    window:
  File "game/inventory.rpy", line 261, in execute
    $ sorted_items = sorted(inventory.items, key=attrgetter('element'), reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
  File "game/inventory.rpy", line 261, in <module>
    $ sorted_items = sorted(inventory.items, key=attrgetter('element'), reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
AttributeError: 'RevertableList' object has no attribute 'element'
And this is the whole inventory code:
Code:
init -1 python:
    import renpy.store as store
    import renpy.exports as renpy # we need this so Ren'Py properly handles rollback with classes
    from operator import attrgetter # we need this for sorting items

    inv_page = 0 # initial page of the inventory screen
    item = None
    class Player(renpy.store.object):
        def __init__(self, name, mc_max_hp=0, mc_max_mp=0, element=None):
            self.name=name
            self.mc_max_hp=mc_max_hp
            self.mc_hp=mc_max_hp
            self.mc_max_mp=mc_max_mp
            self.mp=mc_max_mp
            self.element=element
    player = Player("[sylv]", 100, 50)

    class Item(store.object):
        def __init__(self, name, player=None, mc_hp=0, mc_mp=0, image="", element="", cost=0):
            global cookbook
            self.name = name
            self.player=player # which character can use this item?
            self.mc_hp = mc_hp # does this item restore hp?
            self.mc_mp = mc_mp # does this item restore mp?
            self.element=element # does this item change elemental damage?
            self.image=image # image file to use for this item
            self.cost=cost # how much does it cost in shops?

            #self.desc = desc
            #self.icon = icon
            #self.act = act # screen action
            #self.type = type # type of item
            #self.recipe = recipe # nested list of [ingredient, qty]
            #if recipe:
            #    cookbook.append(self)
            #    cookbook.sort(key=lambda i: i.name) #alpha order

        #def change(self, name, desc=False, value=False, act=False, recipe=False):
            #self.name = name
            #if desc:
            #    self.desc = desc
            #if value:
            #    self.value = value
            #if act:
            #    self.act = act
            #if recipe:
            #    self.recipe = recipe

#        def output(self):
#            return str(self.amt)
#
#        def add_stack(self, amt):
#            self. amt += 1
#
#    class invitem(object):
#        def __init__(self, item, amount):
#            self.item = item
#            self.amount = amount

        def use(self): #here we define what should happen when we use the item
            if self.mc_hp>0: #healing item
                player.mc_hp = player.mc_hp+self.mc_hp
                if player.mc_hp > player.mc_max_hp: # can't heal beyond max HP
                    player.mc_hp = player.mc_max_hp
                inventory.drop(self) # consumable item - drop after use
            elif self.mc_mp>0: #mp restore item
                player.mc_mp = player.mc_mp+self.mc_mp
                if player.mc_mp > player.mc_max_mp: # can't increase MP beyond max MP
                    player.mc_mp = player.mc_max_mp
                inventory.drop(self) # consumable item - drop after use
            else:
                player.element=self.element #item to change elemental damage; we don't drop it, since it's not a consumable item

    class Inventory(store.object):
        def __init__(self, money=0):
            self.currentItem = None
            self.money = money
            self.items = [] # items stored in nested list [item object, qty]
            #self.barter = barter #percentage of value paid for items
            #self.sort_by = self.sort_name
            #self.sort_order = True #ascending, descending
            #self.grid_view = True

        def take(self, item, qty=1):
            if self.qty(item):
                item_location = self.check(item)
                self.items[item_location][1] += qty
            else:
                self.items.append([item,qty])

            #if len(inventory.items) > 54: #<---
            #    renpy.say(None, "Inventory is full.{w=0.5}{nw}") # or use any other way to show it to player
            #else:
            #    self.items.append[(item,qty)]

        def check(self, item):
            if self.qty(item):
                for i in self.items:
                    if i[0] == item:
                        return self.items.index(i) # returns item index (location)

        def drop(self, item, qty=1):
            if self.qty(item):
                item_location = self.check(item)
                if self.items[item_location][1] > qty:
                    self.items[item_location][1] -= qty
                else:
                    del self.items[item_location]
        #def drop(self, item):
        #    self.items.remove(item)
        def buy(self, item):
            if self.money >= item.cost:
                if len(inventory.items) > 54:
                    renpy.say(None, "Inventory is full.{w=0.5}{nw}")
                    return False
                else:
                    self.items.append[(item,qty)]
                    self.money -= item.cost #bonus code to next # line
                    return True
            else:
                renpy.say(None, "Not enough money.{w=0.5}{nw}")
                return False
        def earn(self, amount):
            self.money += amount

        def qty(self, item):
            for i in self.items:
                if i[0] == item:
                    return i[1] # returns quantity

        def sort_qty(self):
            self.items.sort(key=lambda i: i[1], reverse=self.sort_order)

        #def take(self, item, qty=1):
        #    if self.qty(item):
        #        item_location = self.check(item)
        #        self.items[item_location][1] += qty
        #    else:
        #        self.items.append([item,qty])

        def has_item(self, item):
                if item in self.items:
                    return True
                else:
                    return False #here ends bonus code
        def updateSelected(self, item=None):
            if item is not None:
                self.currentItem = item.name
            else:
                self.currentItem = None

        def sell(self, item, price):
            self.withdraw(price)
            self.drop(item[0])

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

        def deposit(self, amount):
            self.money -= amount

    def calculate_price(item, buyer):
        if buyer:
            price = item[0].cost * (buyer.barter * 0.01)
            return int(price)

    def item_use():
        item.use()

    def money_transfer(depositor, withdrawer, amount):
        if depositor.money >= amount:
            depositor.deposit(amount)
            withdrawer.withdraw(amount)
        else:
            message = "Sorry, %s doesn't have %d!" % (buyer.name, amount)
            renpy.show_screen("inventory_popup", message=message)

    def transaction(seller, buyer, item):
        price = calculate_price(item, buyer)
        if buyer.money >= price:
            seller.sell(item, price)
            buyer.buy(item, price)
        else:
            message = "Sorry, %s doesn't have enough money!" % (buyer.name)
            renpy.show_screen("inventory_popup", message=message)

    transfer_amount = 0

    #Tooltips:
    style.tips_top = Style(style.default)
    #style.title.font="gui/arial.ttf"
    style.tips_top.size=14
    style.tips_top.color="fff"
    style.tips_top.outlines=[(3, "6b7eef", 0,0)]
    style.tips_top.kerning = 5

    style.tips_bottom = Style(style.tips_top)
    style.tips_top.size=20
    style.tips_bottom.outlines=[(0, "6b7eef", 1, 1), (0, "6b7eef", 2, 2)]
    style.tips_bottom.kerning = 2

    style.button.background=Frame("gui/frame.png",25,25)
    style.button.yminimum=52
    style.button.xminimum=52
    style.button_text.color="000"


    showitems = True #turn True to debug the inventory
    # def display_items_overlay():
        # if showitems:
            # inventory_show = "Money:" + str(inventory.money) + " HP: " + str(player.hp) + " bullets: " + str(player.mp) + " element: " + str(player.element) + "\nInventory: "
            # for i in range(0, len(inventory.items)):
                # item_name = inventory.items[i].name
                # if i > 0:
                    # inventory_show += ", "
                # inventory_show += item_name

            # ui.frame()
            # ui.text(inventory_show, color="#000")
    # config.overlay_functions.append(display_items_overlay)


screen inventory_button:
    textbutton "Show Inventory" action [Hide("inventory_button"), Call("inv")] align (.95,.04)

screen inventory_screen:
    add "gui/inventory.png"
    modal True  #prevent clicking on other stuff when inventory is shown


    imagebutton auto "nav/exit_inv_%s.png" xpos 930 ypos 10 xalign 1.0 focus_mask True action [ Hide("inventory_screen")]

    #use battle_frame(char=player, position=(.97,.20)) # we show characters stats (mp, hp) on the inv. screen
    #use battle_frame(char=dog, position=(.97,.50))
    window:
        add "gui/inventory.png" # the background
        xysize(1920, 1080)
        align(0.0, 0.0)
        imagebutton auto "gui/inv_bag_%s.png" xpos 156 ypos 397 focus_mask None action [Show("inventory_button"), Call("return_screen")]#, Show("gui_tooltip", my_picture=my_tooltip, my_tt_xpos=1715, my_tt_ypos=990) ] unhovered [Hide("gui_tooltip"), Function(set_cursor_default)] at main_eff1
        hbox align (.95,.04) spacing 20:
            textbutton "Close Inventory" action [Show("inventory_button"), Call("return_screen")]

        for item in inventory.items:
            $ cost = item[0].cost
            $ qty = str(item[1])

        $ x = 515 # coordinates of the top left item position
        $ y = 25
        $ i = 0
        $ sorted_items = sorted(inventory.items, key=attrgetter('element'), reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
        $ next_inv_page = inv_page + 1
        $ prev_inv_page = inv_page - 1 #my code

        $ max_inv_page = 5 # count the max page number
        if inv_page > max_inv_page: # if items in inventory were reduced and amount of pages were decreased too
            $ inv_page = max_inv_page

        if next_inv_page > int(len(inventory.items)/9):
            $ next_inv_page = 0
        if prev_inv_page < int(len(inventory.items)/54): #my code
            $ prev_inv_page = 0

        for item in sorted_items:
            if i+1 <= (inv_page+1)*9 and i+1>inv_page*9:
                $ x += 190
                if i%3==0:
                    $ y += 170
                    $ x = 515
                $ pic = item.image
                $ my_tooltip = "tooltip_inventory_" + pic.replace("gui/inv_", "").replace(".png", "") # we use tooltips to describe what the item does.
                imagebutton idle pic hover pic xpos x ypos y action [Hide("gui_tooltip"), SetVariable("item", item), Function(renpy.invoke_in_new_context, inventory.updateSelected, item), item_use, Function(renpy.restart_interaction)] hovered [ Play ("sound", "sfx/click.wav"), Show("gui_tooltip", my_picture=my_tooltip, my_tt_ypos=693) ] unhovered [Hide("gui_tooltip")] at inv_eff
                if player.element and (player.element==item.element): #indicate the selected gun
                    add "gui/selected.png" xpos x ypos y anchor(.5,.5)

            $ i += 1
            if len(inventory.items)>9:
                textbutton _("Next Page") action [ If(inv_page < max_inv_page, SetVariable('inv_page', inv_page + 1), SetVariable('inv_page', 0)), Show("inventory_screen") ] xpos .475 ypos .83 # if inv_page is less than max_inv_page increase the inv_page number, otherwise set the inv_page to 0
                #textbutton _("Next Page") action [SetVariable('inv_page', next_inv_page), Show("inventory_screen")] xpos .475 ypos .83
                textbutton _("Previous Page") action [SetVariable('inv_page', prev_inv_page), Show("inventory_screen")] xpos .575 ypos .83

screen inventory_popup(message):
    zorder 100
    frame:
        style_group "invstyle"
        hbox:
            text message
    timer 1.5 action Hide("inventory_popup")


screen gui_tooltip (my_picture="", my_tt_xpos=58, my_tt_ypos=687):
    add my_picture xpos my_tt_xpos ypos my_tt_ypos

init -1:
    transform inv_eff: # too lazy to make another version of each item, we just use ATL to make hovered items super bright
        zoom 0.5 xanchor 0.5 yanchor 0.5
        on idle:
            linear 0.2 alpha 1.0
        on hover:
            linear 0.2 alpha 2.5

    image information = Text("INFORMATION", style="tips_top")
    #Tooltips-inventory:
    image tooltip_inventory_chocolate=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("Generic chocolate to heal\n40 points of health.", style="tips_bottom"))
    image tooltip_inventory_banana=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("A healthy banana full of potassium! You can also use it as ammo for your guns! O.O Recharges 20 bullets.", style="tips_bottom"))
    image tooltip_inventory_gun=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("An gun that looks like something a cop would\ncarry around. Most effective on humans.", style="tips_bottom"))
    image tooltip_inventory_laser=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("An energy gun that shoots photon beams.\nMost effective on aliens.", style="tips_bottom"))
    image tooltip_inventory_bag_idle=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("Open bacpack.", style="tips_bottom"))

    image sidewalk = "Gallery_bg.png"
If I change init -1 python: to just init python: it gives a different error that $ pic = item.image is not a RevertableList object instead. I don't know. I am defining these before, so why doesn't it work?

Here's the code in my script.rpy:

Python:
default inventory = Inventory()
default chocolate = Item("chocolate", mc_hp=40, image="gui/inv_chocolate.png", element="chocolate", cost=1 * copper_per_gold)
default gun = Item("Gun", element="bullets", image="gui/inv_gun.png", cost=1 * copper_per_gold) #costs 1 gold
default laser = Item("Laser Gun", element="laser", image="gui/inv_laser.png")

label script:
    show screen inventory_button

    $ inventory.take(chocolate, 2)
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
Python:
    $ sorted_items = sorted(inventory.items, key=attrgetter('element'), reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
AttributeError: 'RevertableList' object has no attribute 'element'
Well, for once the error is explicit. inventory.items is a list of list, and the element attribute is not part of the second list.


Code:
init -1 python:
    import renpy.store as store
    import renpy.exports as renpy # we need this so Ren'Py properly handles rollback with classes
    from operator import attrgetter # we need this for sorting items
The two implicit imports are useless, you're already in Ren'py space, so you already have direct access to the store, and the renpy module is already available. Those imports are needed only if the code is part of an external Python file (.py).
As for the import of attrgetter, it can be replaced by a lambda ; in fact in this particular case, it NEED to be replaced by a lambda.


Python:
    class Inventory(store.object):
        def __init__(self, money=0):
[...]
            self.items = [] # items stored in nested list [item object, qty]
And this is the origin of the error.
The code is originally wrote to works with inventory.items hosting object, and you changed it to host lists, which lead to the error reported by the traceback.
It imply that you can't anymore directly access to the element attribute when you sort inventory.items, because it's not anymore an attribute of the entry contained by the list ; you ask Ren'py to use [item object, quantity].element as key, which is wrong.

Try with $ sorte_items = sorted( inventory.items, key=lambda x:x[0].element, reverse=True), it should works.
 

Acers0

New Member
Jan 3, 2018
13
4
Thanks for replying!
I tried with what you suggested, but now I am getting other errors:
First this:
Python:
[code]
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 144, in script call
    "Me" "Lalala~"
  File "game/script.rpy", line 294, in script
    call screen inventory_screen
  File "renpy/common/000statements.rpy", line 531, in execute_call_screen
    store._return = renpy.call_screen(name, *args, **kwargs)
  File "game/inventory.rpy", line 226, in execute
    screen inventory_screen:
  File "game/inventory.rpy", line 226, in execute
    screen inventory_screen:
  File "game/inventory.rpy", line 248, in execute
    window:
  File "game/inventory.rpy", line 256, in execute
    for item in inventory.items:
  File "game/inventory.rpy", line 257, in execute
    $ cost = item[0].cost
  File "game/inventory.rpy", line 257, in <module>
    $ cost = item[0].cost
AttributeError: 'unicode' object has no attribute 'cost'

Then if I try to remove the problematic code above with # before it (just to test if it works like that) and launch the game a different error pops up:
Python:
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 140, in script call
    "Not enough money."
  File "game/script.rpy", line 294, in script
    call screen inventory_screen
  File "renpy/common/000statements.rpy", line 531, in execute_call_screen
    store._return = renpy.call_screen(name, *args, **kwargs)
  File "game/inventory.rpy", line 226, in execute
    screen inventory_screen:
  File "game/inventory.rpy", line 226, in execute
    screen inventory_screen:
  File "game/inventory.rpy", line 248, in execute
    window:
  File "game/inventory.rpy", line 263, in execute
    $ sorted_items = sorted( inventory.items, key=lambda x:x[0].element, reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
  File "game/inventory.rpy", line 263, in <module>
    $ sorted_items = sorted( inventory.items, key=lambda x:x[0].element, reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
  File "game/inventory.rpy", line 263, in <lambda>
    $ sorted_items = sorted( inventory.items, key=lambda x:x[0].element, reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
AttributeError: 'unicode' object has no attribute 'element'
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
Code:
  File "game/inventory.rpy", line 257, in <module>
    $ cost = item[0].cost
AttributeError: 'unicode' object has no attribute 'cost'
Looks like, at one time, you changed the value of inventory.items. Or, more precisely, looks like there's still at least one line that fill the list directly with objects and not with the [object, quantity] list.

This said, looking again at the code, there's this :
Python:
   class Item(store.object):
[...]
#        def add_stack(self, amt):
#            self. amt += 1
Why not follow this approach instead ? Letting the object deal itself with the quantity seem a better approach.


Then if I try to remove the problematic code above with # before it (just to test if it works like that) and launch the game a different error pops up:
Python:
  File "game/inventory.rpy", line 263, in <lambda>
    $ sorted_items = sorted( inventory.items, key=lambda x:x[0].element, reverse=True) # we sort the items, so non-consumable items that change elemental damage (guns) are listed first
AttributeError: 'unicode' object has no attribute 'element'
This one is normal. It's the exact opposite of the error you initially get.
If you remove what made the correction needed, you also need to remove the said correction.
 

Acers0

New Member
Jan 3, 2018
13
4
I don't know. I don't quite understand what I should do here? This code seems way too advance for me. How do I follow the 'amt' approach? And you said the error is normal what do you mean? Didn't understand that last part of what you said.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
How do I follow the 'amt' approach?
By using items.amt in place of the "quantity" in your [item, quantity].


And you said the error is normal what do you mean? Didn't understand that last part of what you said.
You returned to inventory.items containing only the item, therefore the fix I gave for the code to works with inventory.items containing [item, quantity] can not works anymore.
 

Acers0

New Member
Jan 3, 2018
13
4
Okay, so... I went and looked at another code from a person who was trying to do the same thing and added my own twist to it and the code on its own works, BUT... When I try to implement the features from my previous inventory code, like HP regeneration potion and object class of the Player and having items sorted by "consumables" and "permanent" attributes, the code just refuses to work and looking at the code I really don't understand why. I thought it was perfect.

So, here's the code that "works" and stacks items on top of each other. But it's bare bones without the features that I need:
Python:
label start:
    show screen inventory_button
    "adding another item"
    $ backpack.add_item(popcorn, 5)
    $ item.add_stack(popcorn, 1)
    "lalalala"

    "removing item now."

    $ backpack.remove_item(popcorn, 5)
    $ item.remove_stack(popcorn, 1)
    "Now going to add again."


    $ backpack.remove_item(popcorn, 5)
    $ item.remove_stack(popcorn, 1)


    $ backpack.add_item(popcorn, 5)
    $ item.add_stack(popcorn, 1)
    $ backpack.add_item(popcorn, 5)
    $ item.add_stack(popcorn, 1)
    $ backpack.add_item(popcorn, 5)
    $ item.add_stack(popcorn, 1)

    "And done."
    jump start

return



default popcorn = item("popcorn", "inventory/ragepop_idle.png", "inventory/ragepop_hover.png", 0)
default money = 50
default backpack = container()
default inv = []

label return_screen:
    $ quick_menu = True
    #hide screen money_screen
    pause(0.2)
    return

label inv:
    $ renpy.retain_after_load()
    $ quick_menu = False
    call screen inventory

screen inventory_button:
    textbutton "Show Inventory" action [Hide("inventory_button"), Call("inv")] align (.95,.04)

######screen
screen inventory():
    zorder 2
    add "images/inventory/inventorybg.png"
    modal True
    text "Money: [money]0" xpos 310 ypos 60 color("#FFFFFF")
    hbox align (.95,.04) spacing 20:
        textbutton "Close Inventory" action [Show("inventory_button"), Call("return_screen")]
    hbox:
        xpos 298 ypos 113
        spacing 7
        xmaximum 695
        box_wrap True
        for thing in backpack.inv:
            imagebutton idle thing.item.img hover thing.item.himg action NullAction()
            text "{b}[thing.item.amt]{/b}" size 20 color("#FFFFFF") xoffset -30 yoffset 5


    imagebutton:
        idle "images/inventory/invpois.png"
        hover "images/inventory/invpois2.png"
        focus_mask True
        action Hide("inventory")

init python:
    class item(object):
        def __init__(self, name, img, himg, amt):
            self.name = name
            self.img = img
            self.himg = himg
            self.amt = amt
        def output(self):
            return str(self.amt)

        def add_stack(self, amt):
            self.amt += 1
            return("success")

        def remove_stack(self, amt):
            if self.amt <= 0:
                self.amt = 0
                return("gone")
            else:
                self.amt -= 1
                return("not found")

    class invitem(object):
        def __init__(self, item, amount):
            self.item = item
            self.amount = amount


    class container(object):
        def __init__(self):
            self.inv = []

        def add_item(self, item, amount=1):
            if self.has_item(item):
                self.finditem(item).amount += amount
                #if self.finditem(item).amount >= 99:
                #    self.inv.pop(self.inv.index(self.finditem(item)))
                #    return ("gone")
                #else:
                #    return ("more left")
            else:
                self.inv.append(invitem(item, amount))
                return("success")

        def has_item(self, item, amount=1):
            if item in [i.item for i in self.inv]:
                if self.finditem(item).amount >= amount:
                    return(self.finditem(item).amount)
                else: return(False)
            else:
                return(False)

        def finditem(self, item):
            return(self.inv[[i.item for i in self.inv].index(item)])

        def remove_item(self, item, amount=1):
            if self.has_item(item):
                self.finditem(item).amount -= amount
                if self.finditem(item).amount <= 0:
                    self.inv.pop(self.inv.index(self.finditem(item)))
                    return ("gone")
                else:
                    return ("more left")
            else:
                return ("not found")

And here is that same code with me implementing the features that I need from the code above.
Python:
init python:
    style.money_gold = Style(style.default)
    style.money_silver = Style(style.default)
    style.money_copper = Style(style.default)
################
#STYLES FOR MONEY
init python:
    style.money_gold.font = "gui/fonts/MarkoOne-Regular.ttf"
    style.money_gold.color = "#FCE47C"
    style.money_gold.size = 20
    style.money_gold.outlines = [(2, "#000", 1, 1)]
    style.money_gold.hover_color = "#F0F0E1"
    style.money_gold.hover_outlines = [(2, "#000", 0, 0)]
    #style.menu_choice.line_spacing = 25
    style.money_gold.slow_cps = 55
    style.money_gold.bold = True

init python:
    style.money_silver.font = "gui/fonts/MarkoOne-Regular.ttf"
    style.money_silver.color = "#BAC0C8"
    style.money_silver.size = 20
    style.money_silver.outlines = [(2, "#000", 1, 1)]
    style.money_silver.hover_color = "#F0F0E1"
    style.money_silver.hover_outlines = [(2, "#000", 0, 0)]
    #style.menu_choice.line_spacing = 25
    style.money_silver.slow_cps = 55
    style.money_silver.bold = True

init python:
    style.money_copper.font = "gui/fonts/MarkoOne-Regular.ttf"
    style.money_copper.color = "#B08566"
    style.money_copper.size = 20
    style.money_copper.outlines = [(2, "#000", 1, 1)]
    style.money_copper.hover_color = "#F0F0E1"
    style.money_copper.hover_outlines = [(2, "#000", 0, 0)]
    #style.menu_choice.line_spacing = 25
    style.money_copper.slow_cps = 55
    style.money_copper.bold = True
##################################################################
#CHARACTERS
define mc_char= Character("[mc_char]",
            color = "#FFBFFF")

define r = Character('MC', color="#CD0000")
##################################################################

##################################################################
#Vbars here
default mc_hp = 90
default mc_hp_max = 100
default max_money = 0
default moneyz = 0
##################################################################

##################################################################
#MONEY defined here

define copper_per_silver = 100
define silver_per_gold = 100
define copper_per_gold = silver_per_gold * copper_per_silver

default chocolate = Item("chocolate", "gui/inv_chocolate_idle.png", "gui/inv_chocolate_hover.png", 0, mc_hp=40, consumable="chocolate", cost=1 * silver_per_gold) #costs 1 gold
default backpack = container()
default inv = []

label addmoney(type, amount):
    if type == "gold":
        $ inventory.earn(10000 * amount)
    elif type == "silver":
        $ inventory.earn(100 * amount)
    elif type == "copper":
        $ inventory.earn(1 * amount)
    else:
        $ inventory.earn(amount)
return

label return_screen:
    $ quick_menu = True
    #hide screen money_screen
    pause(0.2)
    return

label inv:
    $ renpy.retain_after_load()
    $ quick_menu = False
    call screen inventory

label start:

    show screen inventory_button

    $ mc_max_hp = 100
    $ mc_hp = mc_max_hp
    $ player.hp = mc_max_hp
    $ current_money = backpack.money #This is a hack to make the field a global so we can use it as a dynamic string below.

    $ copper = backpack.money % copper_per_silver
    $ silver = (backpack.money // copper_per_silver) % silver_per_gold
    $ gold = backpack.money // copper_per_gold

    "test"
    "test 2"
    "test 3"
    "adding another item"
    $ backpack.add_item(chocolate, 5)
    $ Item.add_stack(chocolate, 1)

    "lalalala"

    "removing item now."

    $ backpack.remove_item(chocolate, 5)
    $ Item.remove_stack(chocolate, 1)
    "Now going to add again."


    $ backpack.remove_item(chocolate, 5)
    $ Item.remove_stack(chocolate, 1)


    $ backpack.add_item(chocolate, 5)
    $ Item.add_stack(chocolate, 1)
    $ backpack.add_item(chocolate, 5)
    $ Item.add_stack(chocolate, 1)
    $ backpack.add_item(chocolate, 5)
    $ Item.add_stack(chocolate, 1)

    "And done."
    jump start

return


screen inventory_button:
    imagebutton auto "gui/inv_bag_%s.png" focus_mask None action [Hide("inventory_button"), Call("inv")] align (.95,.04)

######screen
screen inventory():

    modal True
    ###############################################################
    #Showing Amount of Gold/Silver/Copper
    vbar value AnimatedValue(max_money, moneyz, delay=1.0):
        xmaximum 335
        ymaximum 495
        left_bar Frame("gui/hearthealth0.png", 100, 0)
        right_bar Frame("gui/hearthealth.png", 100, 0)
    imagebutton idle "gui/coins.png" xpos -4 ypos 6 action NullAction()
    text "[gold] gold" style "money_gold" ypos 10 xpos 60
    text "[silver] silver" style "money_silver" ypos 30 xpos 80
    text "[copper] copper" style "money_copper" ypos 50 xpos 90
    ###############################################################

    window:
        add "gui/inventory.png" # the background
        xysize(1920, 1080)
        align(0.0, 0.0)
        imagebutton auto "gui/inv_bag_%s.png" focus_mask None action [Show("inventory_button"), Call("return_screen")] align (.95,.04)#, Show("gui_tooltip", my_picture=my_tooltip, my_tt_xpos=1715, my_tt_ypos=990) ] unhovered [Hide("gui_tooltip"), Function(set_cursor_default)] at main_eff1


        $ x = 515 # coordinates of the top left item position
        $ y = 25
        $ i = 0
        $ sorted_items = sorted(backpack.inv, key=attrgetter("permanent"), reverse=True) # we sort the items, so non-consumable items are listed first
        $ next_inv_page = inv_page + 1
        $ prev_inv_page = inv_page - 1 #my code

        $ max_inv_page = 5 # count the max page number
        if inv_page > max_inv_page: # if items in inventory were reduced and amount of pages were decreased too
            $ inv_page = max_inv_page

        if next_inv_page > int(len(backpack.inv)/9):
            $ next_inv_page = 0
        if prev_inv_page < int(len(backpack.inv)/54): #my code
            $ prev_inv_page = 0

        for item in sorted_items:
            if i+1 <= (inv_page+1)*9 and i+1>inv_page*9:
                $ x += 190
                if i%3==0:
                    $ y += 170
                    $ x = 515
                $ pic = item.img
                $ my_tooltip = "tooltip_inventory_" + pic.replace("gui/inv_", "").replace(".png", "") # we use tooltips to describe what the item does.
                imagebutton auto "gui/inv_bag_%s.png" xpos x ypos y action [Hide("gui_tooltip"), SetVariable("item", item), Function(renpy.invoke_in_new_context, backpack.updateSelected, item), item_use, Function(renpy.restart_interaction)] hovered [ Play ("sound", "sfx/click.wav"), Show("gui_tooltip", my_picture=my_tooltip, my_tt_ypos=693) ] unhovered [Hide("gui_tooltip")] at inv_eff
                if player.permanent and (player.permanent==item.permanent): #indicate the selected gun
                    add "gui/selected.png" xpos x ypos y anchor(.5,.5)

            $ i += 1
            if len(backpack.inv)>9:
                textbutton _("Next Page") action [ If(inv_page < max_inv_page, SetVariable('inv_page', inv_page + 1), SetVariable('inv_page', 0)), Show("inventory_screen") ] xpos .475 ypos .83 # if inv_page is less than max_inv_page increase the inv_page number, otherwise set the inv_page to 0
                #textbutton _("Next Page") action [SetVariable('inv_page', next_inv_page), Show("inventory_screen")] xpos .475 ypos .83
                textbutton _("Previous Page") action [SetVariable('inv_page', prev_inv_page), Show("inventory_screen")] xpos .575 ypos .83

screen gui_tooltip (my_picture="", my_tt_xpos=58, my_tt_ypos=687):
    add my_picture xpos my_tt_xpos ypos my_tt_ypos

init:
    transform inv_eff: # too lazy to make another version of each item, we just use ATL to make hovered items super bright
        zoom 0.5 xanchor 0.5 yanchor 0.5
        on idle:
            linear 0.2 alpha 1.0
        on hover:
            linear 0.2 alpha 2.5

    image information = Text("INFORMATION", style="tips_top")
    #Tooltips-inventory:
    image tooltip_inventory_chocolate=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("Generic chocolate to heal\n40 points of health.", style="tips_bottom"))
    image tooltip_inventory_banana=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("A healthy banana full of potassium! You can also use it as ammo for your guns! O.O Recharges 20 bullets.", style="tips_bottom"))
    image tooltip_inventory_gun=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("An gun that looks like something a cop would\ncarry around. Most effective on humans.", style="tips_bottom"))
    image tooltip_inventory_laser=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("An energy gun that shoots photon beams.\nMost effective on aliens.", style="tips_bottom"))
    image tooltip_inventory_bag_idle=LiveComposite((665, 73), (3,0), ImageReference("information"), (3,30), Text("Open bacpack.", style="tips_bottom"))

    image sidewalk = "Gallery_bg.png"

init -1 python:
    import renpy.store as store
    import renpy.exports as renpy # we need this so Ren'Py properly handles rollback with classes
    from operator import attrgetter # we need this for sorting items

    inv_page = 0 # initial page of teh inventory screen
    item = None

    class Player(renpy.store.object):
        def __init__(self, name, mc_max_hp=0, mc_max_mp=0, permanent=None, consumable=None):
            self.name=name
            self.mc_max_hp=mc_max_hp
            self.mc_hp=mc_max_hp
            self.mc_max_mp=mc_max_mp
            self.mp=mc_max_mp
            self.permanent=permanent
            self.consumable=consumable
    player = Player("[mc_char]", 100, 50)

    class Item(store.object):
        def __init__(self, name, img, himg, amt,player=None, mc_hp=0, mc_mp=0, permanent="", consumable="", cost=0):
            self.name = name
            self.player=player # which character can use this item?
            self.mc_hp = mc_hp # does this item restore hp?
            self.mc_mp = mc_mp # does this item restore mp?
            self.img = img
            self.himg = himg
            self.amt = amt
            self.cost = cost
            self.permanent=permanent # does this item change elemental damage?
            self.consumable=consumable

        def use(self): #here we define what should happen when we use the item
            if self.mc_hp>0 and self.consumable: #healing item
                player.mc_hp = player.mc_hp+self.mc_hp
                if player.mc_hp > player.mc_max_hp: # can't heal beyond max HP
                    player.mc_hp = player.mc_max_hp
                inventory.drop(self) # consumable item - drop after use
            elif self.mc_mp>0: #mp restore item
                player.mc_mp = player.mc_mp+self.mc_mp
                if player.mc_mp > player.mc_max_mp: # can't increase MP beyond max MP
                    player.mc_mp = player.mc_max_mp
                inventory.drop(self) # consumable item - drop after use
            else:
                player.permanent=self.permanent #item to change elemental damage; we don't drop it, since it's not a consumable item

        def output(self):
            return str(self.amt)

        def add_stack(self, amt):
            self.amt += 1
            return("success")

        def remove_stack(self, amt):
            if self.amt <= 0:
                self.amt = 0
                return("gone")
            else:
                self.amt -= 1
                return("not found")

    class invitem(store.object):
        def __init__(self, item, amount):
            self.item = item
            self.amount = amount

    class container(store.object):
        def __init__(self, money=0):
            self.money = money
            self.inv = []

        def buy(self, item, amount=1):
            if self.money >= item.cost:
                if self.has_item(item):
                    self.finditem(item).amount += amount
                    self.money -= item.cost #bonus code to next # line
                    return True
                else:
                    self.inv.append(invitem(item, amount))
                    self.money -= item.cost
                    return("success")
            else:
                return False

        def updateSelected(self, item=None):
            if item is not None:
                self.currentItem = item.name
            else:
                self.currentItem = None

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

        def add_item(self, item, amount=1):
            if self.has_item(item):
                self.finditem(item).amount += amount
                #if self.finditem(item).amount >= 99:
                #    self.inv.pop(self.inv.index(self.finditem(item)))
                #    return ("gone")
                #else:
                #    return ("more left")
            else:
                self.inv.append(invitem(item, amount))
                return("success")

        def has_item(self, item, amount=1):
            if item in [i.item for i in self.inv]:
                if self.finditem(item).amount >= amount:
                    return(self.finditem(item).amount)
                else: return(False)
            else:
                return(False)

        def finditem(self, item):
            return(self.inv[[i.item for i in self.inv].index(item)])

        def remove_item(self, item, amount=1):
            if self.has_item(item):
                self.finditem(item).amount -= amount
                if self.finditem(item).amount <= 0:
                    self.inv.pop(self.inv.index(self.finditem(item)))
                    return ("gone")
                else:
                    return ("more left")
            else:
                return ("not found")


    def item_use():
        item.use()


    showitems = False #turn True to debug the inventory

I am getting error code that:
Python:
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 105, in script call
    "test 2"
  File "game/script.rpy", line 87, in script
    call screen inventory
  File "renpy/common/000statements.rpy", line 531, in execute_call_screen
    store._return = renpy.call_screen(name, *args, **kwargs)
  File "game/script.rpy", line 142, in execute
    screen inventory():
  File "game/script.rpy", line 142, in execute
    screen inventory():
  File "game/script.rpy", line 158, in execute
    window:
  File "game/script.rpy", line 168, in execute
    $ sorted_items = sorted( backpack.inv, key=lambda x:x[0].permanent, reverse=True) # we sort the items, so non-consumable items are listed first
  File "game/script.rpy", line 168, in <module>
    $ sorted_items = sorted( backpack.inv, key=lambda x:x[0].permanent, reverse=True) # we sort the items, so non-consumable items are listed first
  File "game/script.rpy", line 168, in <lambda>
    $ sorted_items = sorted( backpack.inv, key=lambda x:x[0].permanent, reverse=True) # we sort the items, so non-consumable items are listed first
[B]TypeError: 'invitem' object does not support indexing[/B]
And I am stuck. And if I try to use the
Python:
$ sorted_items = sorted(inventory.items, key=attrgetter("permanent"), reverse=True)
I get that the obj 'invitem' does not have 'permanent' attribute. I really thought I could implement this code, but I guess I just can't. Which is crazy considering how easy it is to keep track of money, but items are just so hard to stack and display properly...
 
Last edited: