Python item condition

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
sculpteur
Veteran
Posts: 312
Joined: Fri Nov 17, 2017 6:40 pm
Completed: Apocalypse Lovers
Projects: Apocalypse Lovers
Organization: Awake_Production
Location: France
Discord: https://discord.gg/apocalypse-lovers
Contact:

Python item condition

#1 Post by sculpteur »

Hello, I'am having an issue with this part of my code :

Code: Select all

            #USE ITEM FOUND (INV/CONT)
            elif self.name == "gun" and obj_gun in inventory.items and found_session:
                inventory.drop(self)
                container_found.add(self)
            elif self.name == "gun" and obj_gun in container_found.items and inventory_havespace and found_session:
                inventory.add(self)
                container_found.drop(self)
It's fine it's working and I was fine with it until know.
Because now I want the player to be able to have more than one "gun".
The matter is when there is more than one item "gun" in container, you can click and add the first one to your inventory but when you click the second one the "gun" you just took is drop back into the container because of my conditions.

I want to be able to distinguish this two item without having to change there name (I don't want "gunA", "gun2", etc) so I have try this :
(changing obj_gun in container_found and obj_gun in inventory by self in container_found and self in inventory)

Code: Select all

            #USE ITEM FOUND (INV/CONT)
            elif self.name == "gun" and obj_gun in inventory.items and found_session:
                inventory.drop(self)
                container_found.add(self)
            elif self.name == "gun" and obj_gun in container_found.items and inventory_havespace and found_session:
                inventory.add(self)
                container_found.drop(self)
It's also working but the issue stay the same.

Do someone have an idea to how should I proceed ? I am in the fog when it comes to Python synthax so I don't really know how to do it.



This is the complete code for the inventory :

Code: Select all

$ obj_gun = Item("Gun", image="gui/item_icon_gun.png") 

Code: Select all

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

    item = None
    class Item(store.object):
        def __init__(self, name, image=""):
            self.name = name
            self.image=image # image file to use for this item
        def use(self):
            
            #USE ITEM which is in the INVENTORY
            if self.name == "Tools" and obj_tool_is_dropable and obj_tool in inventory.items:
                container.add(self)
                inventory.drop(self)
            elif self.name == "Ducktape" and obj_ducktape_is_dropable and obj_ducktape in inventory.items:
                container.add(self)
                inventory.drop(self)
            elif self.name == "Teddybear" and obj_teddybear_is_dropable and obj_teddybear in inventory.items:
                container.add(self)
                inventory.drop(self)
            elif self.name == "Bassin" and obj_bassin_is_dropable and obj_bassin in inventory.items:
                container.add(self)
                inventory.drop(self)
            elif self.name == "Soap" and obj_soap_is_dropable and obj_soap in inventory.items:
                container.add(self)
                inventory.drop(self)
            elif self.name == "Sponge" and obj_sponge_is_dropable and obj_sponge in inventory.items:
                container.add(self)
                inventory.drop(self)
            elif self.name == "Towel" and obj_towel_is_dropable and obj_towel in inventory.items:
                container.add(self)
                inventory.drop(self)
                
            #USE ITEM which is in the CONTAINER
            elif self.name == "Tools" and obj_tool in container.items and inventory_havespace:
                inventory.add(self)
                container.drop(self)
            elif self.name == "Ducktape" and obj_ducktape in container.items and inventory_havespace:
                inventory.add(self)
                container.drop(self)
            elif self.name == "Teddybear" and obj_teddybear in container.items and inventory_havespace:
                inventory.add(self)
                container.drop(self)

            #USE ITEM FOUND (INV/CONT)
            elif self.name == "gun" and self in inventory.items and found_session:
                inventory.drop(self)
                container_found.add(self)
            elif self.name == "gun" and self in container_found.items and inventory_havespace and found_session:
                inventory.add(self)
                container_found.drop(self)

                
    class Inventory(store.object):
        def __init__(self):
            self.items = []
        def add(self, item): # we could also add conditions that check the space in the inventory
            self.items.append(item)
            if len(inventory.items)> 3 :
                store.inventory_havespace = False
            else:
                store.inventory_havespace = True
        def drop(self, item):
            self.items.remove(item)
            if len(inventory.items)> 3 :
                store.inventory_havespace = False
            else:
                store.inventory_havespace = True
            
    def item_use():
        item.use()

    class Container(store.object):
        def __init__(self):
            self.items = []
        def add(self, item):
            self.items.append(item)
        def drop(self, item):
            self.items.remove(item)
            
Image

“He asked me to calm down, close my eyes and be quiet. He explained to me that if I was afraid, the shadow that ran barefoot in the street would feel it. I got scared seeing Jumanji on TV, so let me tell you, we didn't stay hidden for long and had to start running again.”
Jessica's Diary.

kivik
Miko-Class Veteran
Posts: 786
Joined: Fri Jun 24, 2016 5:58 pm
Contact:

Re: Python item condition

#2 Post by kivik »

Ok before starting this, I think we need to unpick the code a little bit: you're using an object, but you're not using it like an object. Your use function should be agnostic of what the item is in the use() function, and whether it's in a container or inventory. Your item should not even be able to put itself in any containers - it should be something above it that does that: whether it's a non-class function or an Inventory class function.

So the first thing I'd suggest is add a self.droppable attribute to your Item class, and from now on you'll check whether an item is droppable by item_name.droppable. Your Inventory.drop() function should now check whether the item is droppable, and then drop it. You should also have an Inventory.spaces attribute to determine whether there's space left, and not hardcode len(inventory.items)>3

So your Inventory should look like this: (note: I'm not sure why you're using store.object instead of object, but maybe you know better than me?)

Code: Select all

    class Inventory(store.object):
        def __init__(self, spaces = 3):
            self.items = []
            self.spaces = spaces

        def add(self, item):
            if self.has_space():
                self.items.append(item)
                return True
            else:
                return False

        def drop(self, item):
            if item.droppable and item in self.items:
                self.items.remove(item)
                return True
            else:
                return False

        def has_space(self):
            return len(self.items) < self.spaces
So things I've changed:
- I've added a self.spaces attribute, which tells us how many items we can put in the inventory.
- I've added a have_space function (instead of using a global variable to track it). Whenever we want to know whether the inventory is full, we can do inventory.has_space() to check it, or when it's run from within the inventory: self.has_space().
- In your add() function, I added a check to see if there's space, before adding the item, and then returns True or False depending on whether the item was added. This means you can check the result of adding an item to the inventory and handle the outcome. We'll talk about that later
- In your drop function, I actually check the item's droppable attribute AND if the item is in the inventory - if so I remove it and return True, otherwise return False. Alternatively you can wrap the self.items.remove(item) in a try block and throw an error - and you can catch the error in wherever you attempt to drop an item. That way you can determine why an item wasn't dropped: error means it doesn't exist, False means it's not droppable.

Next, you're going to get rid of your container object, you just don't need it. It should behave the same as the inventory class with one exception: whether there's a limit to spaces or not. If your containers can hold more than the player inventory (likely), you can set their spaces to 99 or even 999 if you want. I'm being lazy here - officially you'd actually create the Container class with add (without checking available spaces) and remove, then extend it to Inventory, add the has_space function before overriding the add function - but for this maybe we don't need to get that quite right.


Now, when you use an item (which I'm quite confused about, do you mean move?), you should actually have a handle on which container it comes from, and add / drop it accordingly. This will be a combination of your interface or code logic to show player what's actually happening.

Chances are you'll have a screen with textbuttons that'd execute a function:

Not tested this code, also I haven't done anything with the return but it's just to show you the conditions and what's happening.

Code: Select all

init python:
    def move_item(from_container, to_container, item):
        if to_container.add(item):
            if not from_container.drop(item):
                return "Failed to drop [item.name]!"
            else:
                return "Item moved"
        else:
            if to_container.has_space():
                return "Failed to add [item.name] for some reason."
            else:
                return "Failed to add [item.name]: out of space!

screen move_item_screen(container):
    hbox:
        frame:
            vbox:
                for item in inventory.items:
                    textbutton "[item.name]" action Function("move_item", from_container=inventory, to_container=container, item=item)
        frame:
            vbox:
                for item in container.items:
                    textbutton "[item.name]" action Function("move_item", from_container=container, to_container=inventory, item=item)
The screen basically has two columns: lists the inventory items and the container items with buttons on each side. You know from the screen which container you're pulling the items from and thus which way the items should go when you move them.

It's by no means perfect / completed yet: you can add a name to the Inventory class so your error messages (when handled) can read "inventory" and "container" or "chest" or "storage box" as well.

Hope it gives you an idea of what's going on and how to actually use object orientation - let us know if you need more help with it.

edit: noticed an error in the textbutton lines.
Last edited by kivik on Sun May 20, 2018 4:53 pm, edited 1 time in total.

sculpteur
Veteran
Posts: 312
Joined: Fri Nov 17, 2017 6:40 pm
Completed: Apocalypse Lovers
Projects: Apocalypse Lovers
Organization: Awake_Production
Location: France
Discord: https://discord.gg/apocalypse-lovers
Contact:

Re: Python item condition

#3 Post by sculpteur »

Waw, you're amazing! I just read your answer and now I will try to make it working. It seem I am going to have a lot of work but I think it's worth it. Because your thing seem to be more simple and more accurate.

I scavange that code but like you said I don't think I am using it correctly and it's a pain in the ass for an everyday use.

Everything you said is absolutly right .
Except this maybe :
(note: I'm not sure why you're using store.object instead of object, but maybe you know better than me?)
Because I have no idea why I am using this, I don't even know what's the difference betweed store.object and object.
So obviously I don't know better than you ^^

So before I follow your lead and I destroy my previous patch up code and botch everything, I want to let you know the other parts of my code which are linked to my inventory system because I probably need to rework them to.

I think the main issue you stress is that my items are refered with string like this "gun".
But currently other part of my code refer to them like this. It's mainly about interface.
I don't know if i could work something out or if I need to change everything.
Because there is already a lot of line which are using my current inventory system.
I give you some representative examples of the use of objects elsewhere in the code. Take a look :


Code: Select all

screen inventory_main:
    modal True
    add "gui/Inventory_bar_simple.png" xpos 0.0015 ypos 0.75
    
    imagebutton:
        keysym "i"
        xpos 0.806 ypos 0.850
        insensitive "GUI_inv_butt_small_insensitive"
        idle "GUI_inv_butt_small"
        hover "GUI_inv_butt_small_hover"
        action [ToggleVariable( "show_inventory" ), Hide("inventory_main")]
        
    $ x = 0.264 # coordinates of the top left item position
    $ y = 0.83
    for item in inventory.items:
        $ pic = item.image
        $ x += 0.0354
        $ my_tooltip = "tooltip_inventory_" + pic.replace("gui/item_icon_", "").replace(".png", "") # tooltips
        imagebutton:
        	idle pic hover pic xpos x ypos y
        	action [Hide("gui_tooltip"), Play ("sound", "0 - sound_useitem.mp3"), SetVariable("item", item), item_use]
        	hovered [ Play ("sound", "0 - sound_hoveritem.mp3"), Show("gui_tooltip", my_picture=my_tooltip, my_tt_xpos=362, my_tt_ypos=935) ]
        	unhovered [Hide("gui_tooltip")] at inv_eff
        
        
screen gui_tooltip (my_picture="", my_tt_xpos="", my_tt_ypos=""):
    add my_picture xpos my_tt_xpos ypos my_tt_ypos

Code: Select all

init -1:
    #Tooltips-inventory:
    image tooltip_inventory_tools=LiveComposite((665, 73), (3,0), Text("Tools", style="tips_top"), (185,25), Text("Some tools, useful to repair stuff", size=24))
    image tooltip_inventory_ducktape=LiveComposite((665, 73), (3,0), Text("Duck tape", style="tips_top"), (185,25), Text("A roll of tape, might be useful for fixing stuff", size=24))

Code: Select all

screen container_repair_generator:
    modal False
    if obj_tool in container.items and obj_ducktape in container.items:
        imagebutton:
            xpos 1100 ypos 100
            idle "GUI_inv_container_button_repair_simple"
            hover "GUI_inv_container_button_repair_hovered"
            action [Play ("sound", "0 - sound_upgrade.mp3"), Jump("DarkBasment3_Generator_bis1")]
    else:
        imagebutton:
            xpos 1100 ypos 100
            idle "GUI_inv_container_button_repair_simple"
            hover "GUI_inv_container_button_repair_hovered"
            action Play ("sound", "0 - sound_click_wrong.mp3")

    $ x_item = 1135
    $ y_item = 98
    $ x_slot = 1100
    $ y_slot = 130
    add "GUI_inv_container_slot_empty" xpos x_slot ypos y_slot
    add "GUI_inv_container_slot_empty" xpos x_slot ypos 198
    #ypos +68 chaque bouton
    for item in container.items:
        add "GUI_inv_container_slot_full" xpos x_slot ypos y_slot
        $ pic = item.image
        $ y_item += 68
        $ y_slot += 68
        $ my_tooltip = "tooltip_inventory_" + pic.replace("gui/item_icon_", "").replace(".png", "") # tooltips
        imagebutton idle pic hover pic xpos x_item ypos y_item action [ToggleVariable( "show_inventory" ),Show("inventory_main"), Play ("sound", "0 - sound_useitem.mp3"), SetVariable("item", item), item_use] hovered Play ("sound", "0 - sound_hoveritem.mp3") at inv_eff
Image

“He asked me to calm down, close my eyes and be quiet. He explained to me that if I was afraid, the shadow that ran barefoot in the street would feel it. I got scared seeing Jumanji on TV, so let me tell you, we didn't stay hidden for long and had to start running again.”
Jessica's Diary.

sculpteur
Veteran
Posts: 312
Joined: Fri Nov 17, 2017 6:40 pm
Completed: Apocalypse Lovers
Projects: Apocalypse Lovers
Organization: Awake_Production
Location: France
Discord: https://discord.gg/apocalypse-lovers
Contact:

Re: Python item condition

#4 Post by sculpteur »

Oh and I just understood something.

You don't want me to get rid of my item class right ?
You just want me to change the inventory class.

So I should keep this :

Code: Select all

    item = None
    class Item(store.object):
        def __init__(self, name, image=""):
            self.name = name
            self.image=image # image file to use for this item
        def use(self):
            
            #USE ITEM which is in the INVENTORY
            if self.name == "Tools" and obj_tool_is_dropable and obj_tool in inventory.items:
                container.add(self)
                inventory.drop(self)
           etc etc etc
and add self.droppable

like this :

Code: Select all

    item = None
    class Item(store.object):
        def __init__(self, name, image="", droppable):
            self.name=name
            self.image=image
            self.droppable=droppable
But your

Code: Select all

def move_item(from_container, to_container, item):
will replace my

Code: Select all

def use(self):
 	if self.name == "Tools" and obj_tool_is_dropable and obj_tool in inventory.items:
                container.add(self)
                inventory.drop(self)
  etc etc etc
Am I on the right track ?
Image

“He asked me to calm down, close my eyes and be quiet. He explained to me that if I was afraid, the shadow that ran barefoot in the street would feel it. I got scared seeing Jumanji on TV, so let me tell you, we didn't stay hidden for long and had to start running again.”
Jessica's Diary.

sculpteur
Veteran
Posts: 312
Joined: Fri Nov 17, 2017 6:40 pm
Completed: Apocalypse Lovers
Projects: Apocalypse Lovers
Organization: Awake_Production
Location: France
Discord: https://discord.gg/apocalypse-lovers
Contact:

Re: Python item condition

#5 Post by sculpteur »

I think that might be too complicated for me. I am not good at coding in Phyton. I barely understand what a class is omg, even if I watch online tutorial about Python. It's too high level. When I saw your code, it was so simple and easy to read that I thought I could modify it and implement it in my code but I was too optimistic about my abilities. Maybe I should stick to my previous inventory system. It's really ugly and ineffective but at least I am understanding it a little bit lol
Image

“He asked me to calm down, close my eyes and be quiet. He explained to me that if I was afraid, the shadow that ran barefoot in the street would feel it. I got scared seeing Jumanji on TV, so let me tell you, we didn't stay hidden for long and had to start running again.”
Jessica's Diary.

kivik
Miko-Class Veteran
Posts: 786
Joined: Fri Jun 24, 2016 5:58 pm
Contact:

Re: Python item condition

#6 Post by kivik »

Yeah you were on the right track, sorry I was asleep when you posted your reply.

The best way to think about objects is that they're containers with labels on them: a class is the blueprint of what the object can hold and what it can do; an object is an instance of an object made from the blueprint; an attribute can either be a label (a string attribute like name) or another container (a list, an object, anything).

Your approach is not very scaleable - in that each time you add a new item to your game, you'll need to add another condition to your use() function, which does have a performance impact, and it makes it more and more complicated if you have different conditions to follow.

If you really want to stick with your long condition statements, then an easy way out is to add an item.type attribute to your Item class - so that you can have two guns with different names, and you can still identify what it is by type if you need to do stuff with that later. But I honestly can't recommend it.


If you want some tutorial sessions about classes and objects, ping me a pm, maybe we can chat on discord or something in real time to figure it out :)

User avatar
gas
Miko-Class Veteran
Posts: 842
Joined: Mon Jan 26, 2009 7:21 pm
Contact:

Re: Python item condition

#7 Post by gas »

Flying kicking in the discussion.
Usually, when you KNOW the values of something, and the code is used by you, you can use simple python types and methods.
What a lotta people there do with classes could be easily done in strict Python, that being mostly procedural it's easier to manage. They just apply total abstraction learned in IT classroom, but it's like learning languages. Actual people speak differently.
For example lists and dictionaries will build a better inventory system, without REWRITING from scratch basic methods (for example, a method to check or remove items).
Thee are very RARE cases that force you to make classes in renpy, and well... renpy is done mostly to avoid you use classes :-).
So go first learn basic python, you'll be surprised.
If you want to debate on a reply I gave to your posts, please QUOTE ME or i'll not be notified about. << now red so probably you'll see it.

10 ? "RENPY"
20 GOTO 10

RUN

sculpteur
Veteran
Posts: 312
Joined: Fri Nov 17, 2017 6:40 pm
Completed: Apocalypse Lovers
Projects: Apocalypse Lovers
Organization: Awake_Production
Location: France
Discord: https://discord.gg/apocalypse-lovers
Contact:

Re: Python item condition

#8 Post by sculpteur »

For example lists and dictionaries will build a better inventory system, without REWRITING from scratch basic methods (for example, a method to check or remove items).
So you think differently than kivik ? You read my code and you think I can achieve the same goal and having the same invenotry fonctions and conditions without using classes ?
Image

“He asked me to calm down, close my eyes and be quiet. He explained to me that if I was afraid, the shadow that ran barefoot in the street would feel it. I got scared seeing Jumanji on TV, so let me tell you, we didn't stay hidden for long and had to start running again.”
Jessica's Diary.

sculpteur
Veteran
Posts: 312
Joined: Fri Nov 17, 2017 6:40 pm
Completed: Apocalypse Lovers
Projects: Apocalypse Lovers
Organization: Awake_Production
Location: France
Discord: https://discord.gg/apocalypse-lovers
Contact:

Re: Python item condition

#9 Post by sculpteur »

Code: Select all

    def move_item(from_container, to_container, item):
        if to_container.add(item):
            if not from_container.drop(item):
                return "Failed to drop [item.name]!"
            else:
                return "Item moved"
        else:
            if to_container.has_space():
                return "Failed to add [item.name] for some reason."
            else:
                return "Failed to add [item.name]: out of space!"
Am I missing something or this fonction isn't adding or deleting anything in the list of item.
I don't see any append, add or drop except in the condition.
Image

“He asked me to calm down, close my eyes and be quiet. He explained to me that if I was afraid, the shadow that ran barefoot in the street would feel it. I got scared seeing Jumanji on TV, so let me tell you, we didn't stay hidden for long and had to start running again.”
Jessica's Diary.

kivik
Miko-Class Veteran
Posts: 786
Joined: Fri Jun 24, 2016 5:58 pm
Contact:

Re: Python item condition

#10 Post by kivik »

The add() function in the Inventory returns True or False:

Code: Select all

        def add(self, item):
            if self.has_space():
                self.items.append(item)
                return True
            else:
                return False
This means whenever you call it, it returns True or False depending on the result. This means you can use it in conditions, allowing you to execute it AND check the result in one shot. It's equivalent to doing:

Code: Select all

result = to_container.add(item)
if result:
    # do one thing
else:
    # do something else
It means if you have functions that returns things that can be further manipulated, like an item, you can even do this:

Code: Select all

object.add(object.get())
This is just an example, not relevant to the rest of the code though.

Post Reply

Who is online

Users browsing this forum: Google [Bot]