Doubt with renpy inventory system

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
Hi,

I have a question about my inventory system.

I used this like template and adapt it to my game:


Here my code:
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):
        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.items = []
        def add(self, item): 
            self.items.append(item)
        def drop(self, item):
            self.items.remove(item)
        

    def item_use():
        item.use()
My question is, how can I add a conditional state in script so if a concret item is selected one action take place and if not another action take place?

I tried things like this:

if item.selected (nameofselecteditem):

if self.name (nameofselecteditem):

or similar...always get an error

the only sentence that don't give me error is:

if Item.use (nameofselecteditem):
(but doesn't do the concret action)

Can someone help me? Thanks in advance
 

xcribr

Active Member
Game Developer
Nov 7, 2017
622
3,364
For starters, there is no mention of a "selected" variable in your code, so item.selected won't work.

self only works inside the python code, so yeah, that won't work anywhere else.

I use this system:

It's well documented with examples, more flexible, and has the selection function you want.
 

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
Hi envixer, thanks for your help, I will take a look later at your link (now I'm working), seems very interesting.

But, I already have my actual code adapted to my game, all works as I want in terms of buy an item in store, it is added correctly to inventory and I can select them as well, the only problem is to make a conditional sentence that if I have item selected (and goes to certain location) an action take place and if not another action take place, so if there are any way to add some lines at code to do this will be fantastic.
If I click on an item it's marked as selected so I assume that there is some variables that store that state of selected.

Thanks.
 
  • Like
Reactions: xcribr

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
Hi envixer, thanks for your help, I will take a look later at your link (now I'm working), seems very interesting.

But, I already have my actual code adapted to my game, all works as I want in terms of buy an item in store, it is added correctly to inventory and I can select them as well, the only problem is to make a conditional sentence that if I have item selected (and goes to certain location) an action take place and if not another action take place, so if there are any way to add some lines at code to do this will be fantastic.
If I click on an item it's marked as selected so I assume that there is some variables that store that state of selected.

Thanks.
Well that's not a good inventory but if you still want to use it...
How did you implement the "buttons" for the items? The same way the template did? I for one would use an imagebutton for that.... What is it you want to do exactly? An action when the player clicks on the item? Or just if an item is selected show a menu what you can do with the item in another screen or what exactly? You're not providing enough information to really help you ;)

What I'd need from you:

  • The code for the screens that work with your classes
  • What you really want to do with the items in the inventory (i.e. if the player clicks the item a new screen should pop up which shows the options the player has for said item? )
  • Generally more information about your real problem.
There are literally thousands of ways to do a "if selected then do this or that" but depending on what you want to do you should always take the one that works best for said problem. He used imagebuttons for his template, so I guess you did too and also know about the imagebutton auto function?
If you do and you already use a better imagebutton template in the inventory screen
original:
Code:
imagebutton idle pic hover pic xpos x ypos y action [Hide("gui_tooltip"), Show("inventory_button"), SetVariable("item", item), Hide("inventory_screen"), item_use] hovered [ Play ("sound", "sfx/click.wav"), Show("gui_tooltip", my_picture=my_tooltip, my_tt_ypos=693) ] unhovered [Hide("gui_tooltip")] at inv_eff
You could change it like that:
Code:
imagebutton idle pic hover pic xpos x ypos y action [Hide("gui_tooltip"), Show("inventory_button"), SetVariable("item", item), Hide("inventory_screen"), item_use] hovered [ Play ("sound", "sfx/click.wav"), Show("gui_tooltip", my_picture=my_tooltip, my_tt_ypos=693) ] unhovered [Hide("gui_tooltip")] selected (DO WHAT YOU WANT) at inv_eff
This would make the button "DO WHAT YOU WANT" when it is selected, which would be useless in this case since if you select the button (click on it i.e.) it would do the
Code:
action [Hide("gui_tooltip"), Show("inventory_button"), SetVariable("item", item), Hide("inventory_screen"), item_use]
and then the item would be gone from the inventory instead of whatever you want to do if it is selected ;)

You see, there are multiple things I don't know which won't help me in helping you. Be more specific please.
 
  • Like
Reactions: Porcus Dev

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
Hi Palanto, thank you for responding!

Well that's not a good inventory but if you still want to use it...
Well, I don't want to use it if isn't good, but I'd like to know why isn't good...it's because the code is old? or are something wrong that can affect the funcionality in the future?

I'm sorry, I have very basic programming knowledge and I'm not a python expert.

How did you implement the "buttons" for the items? The same way the template did? I for one would use an imagebutton for that....
That's my full code:

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):
        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.items = []
        def add(self, item):
            self.items.append(item)
        def drop(self, item):
            self.items.remove(item)
       

    def item_use():
        item.use()

    #Tooltips:
    style.tips_top = Style(style.default)
    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"


    showitems = True

           
screen inventory_screen:   
    add "gui/inventory.png"
    modal True
    imagebutton auto "nav/stats/exit_small_%s.png" xpos 1395 ypos 145 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), item_use] hovered [ Play ("sound", "sfx/click.wav") ] unhovered [Hide("gui_tooltip")] at inv_eff
            if player.element and (player.element==item.element):
                add "gui/selected.png" xpos x+200 ypos y+70 anchor(.5,.5)
           
        $ 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

label start:

    python:
        player = Player("Tony")
         
        rosa = Item("Rosa", element="rosa", image="images/shop/items/Rosa_idle.png")

        inventory = Inventory()

What is it you want to do exactly? An action when the player clicks on the item? Or just if an item is selected show a menu what you can do with the item in another screen or what exactly? You're not providing enough information to really help you ;)
In game you can only have access to inventory in map (really there are 3 navigational maps at the moment but it's the same functionality); and game have a shop too.
I tested and works well that when I go to shop I can buy an item, pay for it and item adds well to inventory, then in map clicking over inventory it is showed with the item and I can select them... now what I need is that when I go to certain place in map, if a correct item has been selected before, an action take place, for example: if I've selected a rose and go to the bethroom of one of the roomates, then the action will be different that if I go to the same bethroom but without the rose.

I supose that I need and "if" conditional with a certain variable but I don't know which variable stores the selected item value (I supose too that there are in the code some variable that stores the selected item value because if I selected an item, hide the inventory and show it again, I can see the item selected, so it's stored somewhere, I'm not sure...perhaps it's only a puntual action that really don't store values in any variable).


Now, reviewing the code I think that perhaps the problem is with that part:
Code:
            if player.element and (player.element==item.element): #indicate the selected item
                add "gui/selected.png" xpos x+200 ypos y+70 anchor(.5,.5)
This is the action that take place when I select an item so perhaps adding a line with a new variable that store the item value then I can use this variable to make a conditional statement...Am I okay?


For last, thanks for your time people and sorry if I make mistakes in English.
 
  • Like
Reactions: Palanto

Epadder

Programmer
Game Developer
Oct 25, 2016
567
1,046
looking at the code player.element is saying what item you are "holding". So then you should be able to.
Code:
if player.element == "rosa":
    #Do Something Here (another function or a list of commands)
If the element name and the name of the item are different though...
Code:
if player.element == rosa.element:
    #Do Something Here (another function or a list of commands)
That should work... didn't test it though ;o
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
Hi Palanto, thank you for responding!



Well, I don't want to use it if isn't good, but I'd like to know why isn't good...it's because the code is old? or are something wrong that can affect the funcionality in the future?

I'm sorry, I have very basic programming knowledge and I'm not a python expert.



That's my full code:

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):
        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.items = []
        def add(self, item):
            self.items.append(item)
        def drop(self, item):
            self.items.remove(item)
    

    def item_use():
        item.use()

    #Tooltips:
    style.tips_top = Style(style.default)
    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"


    showitems = True

        
screen inventory_screen:
    add "gui/inventory.png"
    modal True
    imagebutton auto "nav/stats/exit_small_%s.png" xpos 1395 ypos 145 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), item_use] hovered [ Play ("sound", "sfx/click.wav") ] unhovered [Hide("gui_tooltip")] at inv_eff
            if player.element and (player.element==item.element):
                add "gui/selected.png" xpos x+200 ypos y+70 anchor(.5,.5)
        
        $ 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

label start:

    python:
        player = Player("Tony")
      
        rosa = Item("Rosa", element="rosa", image="images/shop/items/Rosa_idle.png")

        inventory = Inventory()



In game you can only have access to inventory in map (really there are 3 navigational maps at the moment but it's the same functionality); and game have a shop too.
I tested and works well that when I go to shop I can buy an item, pay for it and item adds well to inventory, then in map clicking over inventory it is showed with the item and I can select them... now what I need is that when I go to certain place in map, if a correct item has been selected before, an action take place, for example: if I've selected a rose and go to the bethroom of one of the roomates, then the action will be different that if I go to the same bethroom but without the rose.

I supose that I need and "if" conditional with a certain variable but I don't know which variable stores the selected item value (I supose too that there are in the code some variable that stores the selected item value because if I selected an item, hide the inventory and show it again, I can see the item selected, so it's stored somewhere, I'm not sure...perhaps it's only a puntual action that really don't store values in any variable).


Now, reviewing the code I think that perhaps the problem is with that part:
Code:
            if player.element and (player.element==item.element): #indicate the selected item
                add "gui/selected.png" xpos x+200 ypos y+70 anchor(.5,.5)
This is the action that take place when I select an item so perhaps adding a line with a new variable that store the item value then I can use this variable to make a conditional statement...Am I okay?


For last, thanks for your time people and sorry if I make mistakes in English.
About the first thing, yes it's old and messy code :) But it does what it's supposed to do even though it's rather complicated than straight forward.
Also it doesn't make good use of inheritance. You see, the inventory should be bound to the player character to make it effective, this way you could create multiple character classes with their own inventory each... Well whatever, that's probably not necessary if you have just one MC and create a new inventory just for shops and so on ;)

Your example approach wouldn't be the best to do but it's at least one ;) Try it :) I've g2g to work and couldn't finish testing some other ways to do it a little better. I'd try it with a selfwritten function inside the inventory class but I'll get back to you later when I have the time to test some cases....

p.S.: You should have figured out by now that the above mentioned "try" would just run through your inventory and mark each item you have in there as what you've supposedly activated the last time, and that it will always only show the last item in the inventory instead of the one you really clicked.

So below will be a new posting that you get alerted, with another way to do it... I'm not perfectly happy with it but that's all I could get out of it for now ;)
 
  • Like
Reactions: Porcus Dev

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
Alright, sorry for my late answer. Is there a reason that the inventory can't be accessed anywhere else except the map?
I encounter a problem with this inventory that bugs me and I can't figure out why it doesn't do it like I intend to... Well anyway, I have a solution for your problem, the biggest problem is, if it doesn't return to a screen (i.e. your map or the quickmenu or wherever) it will advance in dialogue... so here's how it looks like:

example projects script.rpy edited so you can check what it looks like for now:
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 teh inventory screen
    item = None
    class Player(renpy.store.object):
        def __init__(self, name, max_hp=0, max_mp=0, element=None):
            self.name=name
            self.max_hp=max_hp
            self.hp=max_hp
            self.max_mp=max_mp
            self.mp=max_mp
            self.element=element
    player = Player("Derp", 100, 50)

    class Item(store.object):
        def __init__(self, name, player=None, hp=0, mp=0, element="", image="", cost=0):
            self.name = name
            self.player=player # which character can use this item?
            self.hp = hp # does this item restore hp?
            self.mp = 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?
        def use(self): #here we define what should happen when we use the item
            if self.hp>0: #healing item
                player.hp = player.hp+self.hp
                if player.hp > player.max_hp: # can't heal beyond max HP
                    player.hp = player.max_hp
                inventory.drop(self) # consumable item - drop after use
            elif self.mp>0: #mp restore item
                player.mp = player.mp+self.mp
                if player.mp > player.max_mp: # can't increase MP beyond max MP
                    player.mp = player.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=10):
            self.currentItem = None
            self.money = money
            self.items = []
        def add(self, item): # a simple method that adds an item; we could also add conditions here (like check if there is space in the inventory)
            self.items.append(item)
        def drop(self, item):
            self.items.remove(item)
        def buy(self, item):
            if self.money >= item.cost:
                self.items.append(item)
                self.money -= item.cost
        def updateSelected(self, item=None):
            if item is not None:
                self.currentItem = item.name
            else:
                self.currentItem = None


    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/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:
    modal True #prevent clicking on other stuff when inventory is shown
    #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)
        hbox align (.95,.04) spacing 20:
            textbutton "Close Inventory" action [Show("inventory_button"), Return()]
        $ 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
        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 += 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 [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: # 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 sidewalk = "Sidewalk.jpg"

# The game starts here.
label start:
    python:
        player = Player("Derp", 100, 50)
        player.hp = 50
        player.mp = 10
        chocolate = Item("Chocolate", hp=40, image="gui/inv_chocolate.png")
        banana = Item("Banana", mp=20, image="gui/inv_banana.png")
        gun = Item("Gun", element="bullets", image="gui/inv_gun.png", cost=7)
        laser = Item("Laser Gun", element="laser", image="gui/inv_laser.png")
        inventory = Inventory()
        #add items to the initial inventory:
        inventory.add(chocolate)
        inventory.add(chocolate)
        inventory.add(banana)

    scene sidewalk
    "Me" "Lalala~"
    show screen inventory_button
    "Look! An inventory button!"
    "Strange guy" "Hey! You wanna buy a gun?"
    "Me" "Okay"
    $inventory.buy(gun)
    "Me" "Nice"
    $inventory.add(laser)
    "You found a laser gun!"

    "The end."
    return

    label inv:
        $ renpy.retain_after_load()
        call screen inventory_screen
        return
and here the important changes for your own above posted script:
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

    class Item(store.object):
        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)
### This function gets used by the item button inside the inventory automatically, if you want to reset the state of the variable "self.currentItem" you can simply put this wherever you want to remove the last selected item: $ inventory.updateSelected()       <--- that's it, Now self.currentItem is set to None again.
        def updateSelected(self, item=None):
            if item is not None:
                self.currentItem = item.name
            else:
                self.currentItem = None

    def item_use():
        item.use()

    #Tooltips:
    style.tips_top = Style(style.default)
    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"


    # showitems = True <------------ can be removed

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

screen inventory_screen:
    add "gui/inventory.png"
    modal True
    imagebutton auto "nav/stats/exit_small_%s.png" xpos 1395 ypos 145 xalign 1.0 focus_mask True action [ Show("inventory_button"), Return()]

    $ 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), Function(renpy.invoke_in_new_context, inventory.updateSelected, item), item_use, Function(renpy.restart_interaction)] hovered [ Play ("sound", "sfx/click.wav") ] unhovered [Hide("gui_tooltip")] at inv_eff
            if player.element and (player.element==item.element):
                add "gui/selected.png" xpos x+200 ypos y+70 anchor(.5,.5)

        $ 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

label start:

    python:
        player = Player("Tony")

        rosa = Item("Rosa", element="rosa", image="images/shop/items/Rosa_idle.png")

        inventory = Inventory()


    label inv:
        $ renpy.retain_after_load()
        call screen inventory_screen
        return
and here the only real changes I made:

class Inventory:
Code:
    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)
### This function gets used by the item button inside the inventory automatically, if you want to reset the state of the variable "self.currentItem" you can simply put this wherever you want to remove the last selected item: $ inventory.updateSelected()       <--- that's it, Now self.currentItem is set to None again.
        def updateSelected(self, item=None):
            if item is not None:
                self.currentItem = item.name
            else:
                self.currentItem = None

    def item_use():
        item.use()
Here I just added a variable which holds the necessary information at init it will only hold the value: None
self.currentItem = None
and a really small function called updateSelected()
this function is explained in the comment. The inventory item button (i.e. Rosa) will call this function and sets self.currentItem = "Rosa"
It's getting the currently clicked on item to check the name. So if you want to try it in the console, either support it with a prior created "Item" or without anything at all. If no item will be sent in the function call, it will automatically reset self.currentItem to None, here a code example of how you could do it if you wouldn't want to use a button to set the currentItem variable:

Code:
$ rosa = Item("Rosa", element="rosa", image="images/shop/items/Rosa_idle.png")
$ inventory.updateSelected(rosa)
# Now inventory.currentItem is set to "rosa"

if inventory.currentItem == "Rosa":
    "You go to the bedroom with a rose between your teeth."
else:
    "You go to the bedroom without a gift and your girlfriend kicks you out before you could even say hi!"
Ok the nextpart is the button to show the inventory:
Code:
screen inventory_button:
    textbutton "Show Inventory" action [Hide("inventory_button"), Call("inv")] align (.95,.04)
Couldn't find a button to show the inventory in your "full code" up above, so I put that one in, use yours but use the Call("inv") action instead of show or whatever.
the really important thing is just the action Call("inv") which calls a label which uses a renpy build in function and then calls the real screen, after returning from the screen it will return to the previous screen (i.e. the map). Hide("inventory_button") well I don't know what your map screen is called and if the inventory is supposed to show up "above" it or not, so you might better use "zorder" to determine which screen is on top of which (map would be zorder 0 [standart setting] and the inventory_screen zorder 1 )

Same with the exit button inside the inventory screen:
Code:
imagebutton auto "nav/stats/exit_small_%s.png" xpos 1395 ypos 145 xalign 1.0 focus_mask True action [ Show("inventory_button"), Return()]
use the Return() function not Hide()
showing the button depends on how you implemented it (see above)

Ok the next button is the one in the for loop, so the real item buttons inside the inventory:
Code:
imagebutton idle pic hover pic xpos x+300 ypos y+110 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") ] unhovered [Hide("gui_tooltip")] at inv_eff
Ok what does it do? Lets start from the first added action and what it does:
  • Function(renpy.invoke_in_new_context, inventory.updateSelected, item) - this actually just invokes the new function in a new context, runs it, sets the inventory.currentItem to item.name (in your single item case it would be "Rosa")
  • Function(renpy.restart_interaction) - this one is pretty nice, it means, if you have a "consumable" item (like the chocolates and the banana in the template game) it will remove them from the inventory WHILE you're in the inventory. This means, you don't necessarily have to leave the inventory with each item use, but just when you used the last item. Which then gets set as the currentItem ;)


Last but not least, the label inv:
label inv:
$ renpy.retain_after_load()
call screen inventory_screen
return


This label makes it possible to "call" the screen instead of showing it, which isn't perfect either, but in this case probably a lot better than the "show" screen button function (you can try both if you want, Call("inv") or Show("inventory_screen") , if you use the later remember that you have to change the "back button" again so that it does Hide("inventory_screen") instead of Return() )
 

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
I make some tries today but didn't have luck, I didn't get it to work for now... but today I hadn't much time to understand all the new functionalities and to make more tries, I hope that tomorrow I can take a look in detail and make another try.

I really, really appreciate all your help and your time to show me the path, sure that finally will works ;)
I'll let you know the progress and put the final code when all works well.
 
  • Like
Reactions: Palanto

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
If you encounter any problems just let me know ;) If you get traceback errors, you can't seem to fix, post the copy to clipboard message here ;)
 
  • Like
Reactions: 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
Some comments not directly related to the efficiency, just the code you used. Side note, I can't test right now, so I cross my fingers that I didn't made too many typos.


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
You are in an init block, you don't need these lines. Both store and renpy are already directly available.

Code:
    from operator import attrgetter # we need this for sorting items
    [...]
       $ sorted_items = sorted(inventory.items, key=attrgetter('element'), reverse=True)
The following should do the same without the need to import something:
You don't have permission to view the spoiler content. Log in or register now.


Another way to enforce boundaries for some values ("mp" and "hp"):
You don't have permission to view the spoiler content. Log in or register now.


Code:
   class Inventory(store.object):
       def __init__(self, money=10):
           self.currentItem = None
           self.money = money
           self.items = []
       def add(self, item):
           self.items.append(item)
       def drop(self, item):
           self.items.remove(item)
       def buy(self, item):
           if self.money >= item.cost:
               self.items.append(item)
               self.money -= item.cost
What bother me with this code, is that you'll occupy one case by item. Therefore :
You don't have permission to view the spoiler content. Log in or register now.


Code:
    def item_use():
        item.use()
    [...]
screen inventory_screen:
    [...]
        for item in sorted_items:
        [...]
Er... So item_use can only works when called from the screen, while giving random result if used outside. Why not directly address item.use ?
You don't have permission to view the spoiler content. Log in or register now.


Code:
    #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
    [...]
better wrote directly as style, no ?
You don't have permission to view the spoiler content. Log in or register now.


Code:
            if i+1 <= (inv_page+1)*9 and i+1>inv_page*9:
Isn't it equal to :
Code:
            if i < (inv_page+1)*9 and i+1>inv_page*9:


Code:
                $ pic = item.image
                $ my_tooltip = "tooltip_inventory_" + pic.replace("gui/inv_", "").replace(".png", "") # we use tooltips to describe what the item does.
                imagebutton [...] hovered [ Play ("sound", "sfx/click.wav"), Show("gui_tooltip", my_picture=my_tooltip, my_tt_ypos=693) ] unhovered [Hide("gui_tooltip")] at inv_eff
               [...]
screen gui_tooltip (my_picture="", my_tt_xpos=58, my_tt_ypos=687):
    add my_picture xpos my_tt_xpos ypos my_tt_ypos
Tooltip ?
You don't have permission to view the spoiler content. Log in or register now.


  • Function(renpy.invoke_in_new_context, inventory.updateSelected, item) - this actually just invokes the new function in a new context, runs it, sets the inventory.currentItem to item.name (in your single item case it would be "Rosa")
But why in a new context ? I can have missed something, but I see no reason for it to not works in the actual context, and few possible side effect when playing it in another context.
 
  • Like
Reactions: Palanto

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
Thanks for all that info anne O'nymous!
I can't read in detail, I'll check it on weekend I supose, but it seems a very good info :)

Today I was able make some new test. Palanto, I have needed to go back to some of my old code cause with yours I have some problems that before I dind't have :p problems about showing/hiding inventory screen.

Then I checked some games code to figured it out why does work what I want to do...and finally...IT WORKS!!! I'm so happy :biggrin:
I need to do a deep test but at least now seems that I can go to shop, buy an item, select it in inventory, and then when I go to location with this item selected,the correct action take place...but I need to do more test to ensure that all the rest works fine.

I'll put the final code here, but surely I'll make some other changes based on your recommendations and comments.

Eternal thanks for all your help people!!! :heartcoveredeyes:;)
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
Gotta check the post from you later @anne O'nymous sorry gotta go to work right now :D But just the first thing you mentioned already: that was code from the template ;) Didn't change much about that one, it's one of the reasons I said that this inventory template isn't that good... :D But great that you took the time to change some deprecated stuff :D Like I said, will review the changes later :D Thanks for your posting :D
 
  • Like
Reactions: 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
that was code from the template ;) Didn't change much about that one, it's one of the reasons I said that this inventory template isn't that good... :D
Yeah, I understood it. That why I kept my comment out of any context. You can't adapt a code and improve it at the same time without loosing part of your mind. Like you did the adaptation, I looked at the improvement.
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
  • Function(renpy.invoke_in_new_context, inventory.updateSelected, item) - this actually just invokes the new function in a new context, runs it, sets the inventory.currentItem to item.name (in your single item case it would be "Rosa")
But why in a new context ? I can have missed something, but I see no reason for it to not works in the actual context, and few possible side effect when playing it in another context.
So much true stuff above that one, nothing to argue about :) And that was template code which we already talked about so I answer the question about the action "I" added :D

The new context was a test before, since I "tried" to do it without a label which calls the renpy.retain_after_load() function before calling the actual screen.
Now since I did it in a label (the new context thing didn't do anything I imagined, i.e. saving the name of the last clicked item in the prepared variable) this could just be
Code:
Function(inventory.updateSelected, item)
instead of the renpy.invoke_in_new_context function.
Just forgot to replace that, sorry :D

p.s.: besides even the renpy.retain_after_load doesn't seem to work there at all :-\ At least if you let the screen close automatically (through the click of the item button instead of a seperate inventory close button) it doesn't save the variable if you i.e. open the console right after you returned to the game. Nor does it remove the item from the inventory (it's back inside after opening the inventory again...) .... it only worked if you clicked an item then, after the screen closed itself by clicking the item, click on inventory again or continue with the dialogue. Else it didn't save the changes... :-\ Not sure why though :D
 
  • Like
Reactions: Porcus Dev

Porcus Dev

Engaged Member
Game Developer
Oct 12, 2017
2,582
4,685
p.s.: besides even the renpy.retain_after_load doesn't seem to work there at all :-\ At least if you let the screen close automatically (through the click of the item button instead of a seperate inventory close button) it doesn't save the variable if you i.e. open the console right after you returned to the game. Nor does it remove the item from the inventory (it's back inside after opening the inventory again...) .... it only worked if you clicked an item then, after the screen closed itself by clicking the item, click on inventory again or continue with the dialogue. Else it didn't save the changes... :-\ Not sure why though
Exactly, after I select an item and go to console to check variables, none of those store any object...but now with the new code it works, only I have to pulish the code a little and make some minor changes, then I'll put it here if you would to review.
 
  • Like
Reactions: Palanto

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
(the new context thing didn't do anything I imagined, i.e. saving the name of the last clicked item in the prepared variable)
For Ren'py, creating a new context basically mean that the internal state (and it only) is saved, then a fresh new context is created for what will follow. But the store stay untouched unless you use to explicitly declare a variable as local for this context. This said, "untouched" is not to take too literally ; the store will be the one before the current interaction, not the effective actual store (see below).
So yes, in regard of this part of the code, it do nothing.


Just forgot to replace that, sorry :D
No need to be sorry. I'll not blame you, especially today when it took me almost an hour to see that I miswrote a variable name ; yeah, it was a productive morning at work :D


At least if you let the screen close automatically (through the click of the item button instead of a seperate inventory close button) it doesn't save the variable if you i.e. open the console right after you returned to the game.
The console is not always a good way to test, since it works in a new context, and the recent optimization made it (almost) unpredictable. Take a look at this with a 6.99.13, or further, version :
Code:
    $ a = "abc"
    "[a] for the console, 'a' do not exist."
    $ a = "def"
    "[a] you can't change 'a' from the console."
When you see the first line, you can't look at the value of a from the console ; you'll have a NameError exception. And when you see the second line, you can't change the value of a from the console. This happen because the Python line is part of the implied say statement that follow.
It mean that, the first time, the variable will really exist only after you end the interaction (so here, when you'll see the second line) ; for Ren'py it's not a problem, but the console being in another context, it will works with the store as seen before the current interaction. As for the second case, when you quit the console, Ren'py will restart the interaction, and so assign again "def" as value for a.


it only worked if you clicked an item then, after the screen closed itself by clicking the item, click on inventory again or continue with the dialogue. Else it didn't save the changes... :-\ Not sure why though :D
So, said otherwise, it only work... if you end the interaction and start a new one. Which is the default behavior supposed to be changed by renpy.retain_after_load. Like I'm curious, I did an experience, using the exact code found on the ; the answer is clear, it don't works.

This said, like it's a problem of interaction, have you tried to use renpy.restart_interaction ? It's a black magic command, because you never really know if it will works for this case or not ; especially since the notion of "interaction" depend of the version of Ren'py and the optimization added by Tom. But when it works it change your life ;)


Not sure why though :D
Well, now you have the answer. It's because you save in the middle of an "interaction" and so save the store as seen at the start of it, and also because renpy.retain_after_load is apparently broke.
 
  • Like
Reactions: Palanto

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,835
For Ren'py, creating a new context basically mean that the internal state (and it only) is saved, then a fresh new context is created for what will follow. But the store stay untouched unless you use to explicitly declare a variable as local for this context. This said, "untouched" is not to take too literally ; the store will be the one before the current interaction, not the effective actual store (see below).
So yes, in regard of this part of the code, it do nothing.




No need to be sorry. I'll not blame you, especially today when it took me almost an hour to see that I miswrote a variable name ; yeah, it was a productive morning at work :D




The console is not always a good way to test, since it works in a new context, and the recent optimization made it (almost) unpredictable. Take a look at this with a 6.99.13, or further, version :
Code:
    $ a = "abc"
    "[a] for the console, 'a' do not exist."
    $ a = "def"
    "[a] you can't change 'a' from the console."
When you see the first line, you can't look at the value of a from the console ; you'll have a NameError exception. And when you see the second line, you can't change the value of a from the console. This happen because the Python line is part of the implied say statement that follow.
It mean that, the first time, the variable will really exist only after you end the interaction (so here, when you'll see the second line) ; for Ren'py it's not a problem, but the console being in another context, it will works with the store as seen before the current interaction. As for the second case, when you quit the console, Ren'py will restart the interaction, and so assign again "def" as value for a.




So, said otherwise, it only work... if you end the interaction and start a new one. Which is the default behavior supposed to be changed by renpy.retain_after_load. Like I'm curious, I did an experience, using the exact code found on the ; the answer is clear, it don't works.

This said, like it's a problem of interaction, have you tried to use renpy.restart_interaction ? It's a black magic command, because you never really know if it will works for this case or not ; especially since the notion of "interaction" depend of the version of Ren'py and the optimization added by Tom. But when it works it change your life ;)




Well, now you have the answer. It's because you save in the middle of an "interaction" and so save the store as seen at the start of it, and also because renpy.retain_after_load is apparently broke.
woah thanks Anne! :D
Yeah, but I know for certain that renpy.retain_after_load worked before the last update. So it got broken in the last renpy update I guess....
Actually I did it in my personal test case with the renpy.restart_interaction() command when I changed the inventory screen to NOT close right after clicking an item but only if you click the "close button" so it always restarted the interaction on a click of an item and removed it correctly from the inventory, but at one point (maybe because of the broken renpy.retain_after_load? ) it didn't work anymore, well it worked as it should, but when you closed the inventory and reopened it, it showed all the removed items again as if they weren't used and deleted from the list. :)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,070
14,698
Yeah, but I know for certain that renpy.retain_after_load worked before the last update. So it got broken in the last renpy update I guess....
As I see it, it's one of the most sensible function. After all it change the natural behavior of Ren'py, and it's supposed to do it whatever the situation. This while restart_interaction, by example, take count of the actual situation and eventually decide to simply do nothing. So, a single change in the way Ren'py works, and retain_after_load is momentarily broke, or even just half broke depending of the moment it's used.
The only thing I'm sure about, is that the new way Tom optimized Ren'py is good for performance, but also mean that we have to relearn few things. He did his best to avoid side effect and achieved it. The example shown with the console have in fact no impact in the game ; and there's also some funny, impact free, examples with rollback. But still we need to be more cautious because there's few exceptional cases where it interfere.
 
  • Like
Reactions: Palanto

M-77

Newbie
Sep 18, 2017
82
22
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"?

Quote Reply