Classes only partially saving?

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
User avatar
voluorem
Regular
Posts: 29
Joined: Fri Jun 24, 2022 3:32 pm
Projects: Sweet Release
Discord: voluorem
Contact:

Classes only partially saving?

#1 Post by voluorem »

So I've been using the inventory system in this post: viewtopic.php?t=44730
But now I'm having issues with saving and loading and I can't figure out how to fix it. I've read the documentation about saving/loading and I understand what's saved and what's not, but what my game is saving and what it's not doesn't make any sense to me.
I made this little checklist thing on the crafting screen to show which ingredients the recipe needs and whether or not you have the ingredients. This is the code for that:

Code: Select all

for key in angestuff_recipes.inventory[i].item.ingredients:
    hbox:
        spacing 5
        if angestuff_material.has_item(key):
            add "gui/button/checkbox_filled.png"
        else:
            add "gui/button/checkbox_blank.png"
        text angestuff_recipes.inventory[i].item.ingredients[key] size 40
And this is what it looks like:
Image
After loading, the checklist stays consistent and shows which ingredients you have. But if you try to make the item, it says that you don't have enough, even though the checklist says that you do. Here's the code for making an item (I know it's janky):

Code: Select all

if angestuff_material.has_item(sugar) and angestuff_material.has_item(milk) and angestuff_material.has_item(flour) and angestuff_material.has_item(chocolate) and angestuff_material.has_item(eggs) and angestuff_material.has_item(butter):
    $ angestuff_material.remove_item(sugar)
    $ angestuff_material.remove_item(eggs)
    $ angestuff_material.remove_item(flour)
    $ angestuff_material.remove_item(chocolate)
    $ angestuff_material.remove_item(milk)
    $ angestuff_material.remove_item(butter)
    $ angestuff_present.add_item(cookies)
    $ cook_item = cookies
    show screen item_made
    $ renpy.pause()
    hide screen item_made
    jump cookbook
else:
    "You don't have enough for this."
    jump cookbook
The keys in the ingredients dictionary are all just the objects in the second code, so I don't understand what's different between the two (since they're using the same function). I also noticed that when you get more items after saving it makes a separate object instead of adding to the original item.
Here's my classes/functions:

Code: Select all

    import renpy.store as store
    
    class InvItem(store.object):
        def __init__(self, item, amount):
            self.item = item
            self.amount = amount

    class Container(store.object):
        def __init__(self):

            self.inventory = []

        def add_item(self, item, amount=1):
            if item in [i.item for i in self.inventory]:
                    self.inventory[[i.item for i in self.inventory].index(item)].amount += amount
            else:
                self.inventory.append(InvItem(item, amount))
        def has_item(self, item, amount=1):
            if item in [i.item for i in self.inventory]:
                if self.finditem(item).amount >= amount:
                    return(self.finditem(item).amount)
                else:
                    return(False)
            else:
                return(False)
I feel like it's something to do with the InvItem class because the Containers are defaulted properly (but I can't really default anything for InvItem). Is there some way to force the InvItem class data to be saved?

User avatar
m_from_space
Miko-Class Veteran
Posts: 975
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: Classes only partially saving?

#2 Post by m_from_space »

voluorem wrote: Mon Jan 15, 2024 11:09 pm So I've been using the inventory system in this post: viewtopic.php?t=44730
But now I'm having issues with saving and loading and I can't figure out how to fix it.
First of all, I think this inventory system is a bit confusing (the amount of appendices on a single item call proves me right). Don't know why the author decided to create another class object to save inside the inventory, while you could just us a dictionary as the inventory. But oh well.

I don't think there is something wrong with your own code that you presented. But I am wondering how you create your inventory container in the first place. Make sure to not put it inside python code at init time. You have to use "default" to declare it. Can you show the relevant parts for this?

It should look like this:

Code: Select all

default angestuff_material = Container()
Is there some way to force the InvItem class data to be saved?
I'm not sure I understand your question. You don't have to worry about the InvItem class, because the Container class is using it to store the items. As long as your "inventory" is created using "default", Renpy will save all of what's inside of it.

philat
Eileen-Class Veteran
Posts: 1912
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Classes only partially saving?

#3 Post by philat »

It's hard to say what the problem is without knowing all of what you're doing (for instance, how are you defining/defaulting your variables), but since I was bored tonight I set up a little test structure for a simple ingredient/recipe system. Unlikely that it will fit all your needs out of the bag, but perhaps it can be inspirational in figuring out what is going on with your code. Usual disclaimers: I did this on the fly and haven't given it a ton of thought, so perhaps I'm missing something, but it in general is where I would start if I were making this system rather than trying to patch together things from another tutorial that doesn't quite fit the bill.

Code: Select all

init python:
    class Ingredient(): # contains immutable characteristics of ingredients. For now, just names.
        def __init__(self, name):
            self.name = name

    class Container():
        def __init__(self):
            self.inventory = {} # a dictionary where the key, value will be ingredient: amount (obviously you can change this if needed)
        
        def initialize(self, available_ingredient_list):
            for item in available_ingredient_list:
                self.inventory[item] = 0

        def add_item(self, item, amount=1):
            self.inventory[item] += amount
        
        def remove_item(self, item, amount=1):
            if self.has_item(item, amount):
                self.inventory[item] -= amount
            else:
                return False
        
        def has_item(self, item, amount):
            return (self.inventory[item] >= amount)

        def can_make(self, recipe):
            for key, value in recipe.ingredient_dictionary.items(): 
            # loop over all required ingredients in a recipe and return False if any one of them is not available
                if not self.has_item(key, value):
                    return False
            return True
        
        def make(self, recipe): # note that all this does for now is remove all used ingredients, so modify if extra functionality is needed
            if self.can_make(recipe):
                for key, value in recipe.ingredient_dictionary.items():
                    self.remove_item(key, value)
            else:
                return False

    class Recipe():
        def __init__(self, name, ingredient_dictionary):
            self.name = name
            self.ingredient_dictionary = ingredient_dictionary


screen inventory_screen(container):
    vbox:
        for key in container.inventory:
            hbox:
                spacing 30
                text key.name
                text str(container.inventory[key])
        null height 50
        text "Can make cookies: {}".format(container.can_make(cookies))


default sugar = Ingredient("Sugar")
default eggs = Ingredient("Eggs")
default flour = Ingredient("Flour")

default all_ingredients = [sugar, eggs, flour]

default cookies = Recipe("Cookies", {sugar:1, eggs:3, flour:5})

default inventory_container = Container()


label start:
    $ inventory_container.initialize(all_ingredients)
    show screen inventory_screen(inventory_container)
    pause
    $ inventory_container.add_item(sugar)
    pause
    $ inventory_container.add_item(eggs, 6)
    pause
    $ inventory_container.remove_item(flour) # will do nothing
    pause
    $ inventory_container.remove_item(eggs, 2)
    pause
    $ inventory_container.add_item(flour, 8)
    pause
    $ inventory_container.make(cookies)
    pause
    $ inventory_container.make(cookies)  # will do nothing
    pause

User avatar
voluorem
Regular
Posts: 29
Joined: Fri Jun 24, 2022 3:32 pm
Projects: Sweet Release
Discord: voluorem
Contact:

Re: Classes only partially saving?

#4 Post by voluorem »

I redid it based on this code, but I'm having an issue with the ingredients checklist. I'm not sure what I'm doing wrong (I think I'm just not understanding something but I don't know what). Here's the updated checklist code:

Code: Select all

vbox:
    for key in angestuff_recipes[i].ingredient_dictionary: ## this is just a list of the recipes, not a container object
        hbox:
            spacing 5
            if angestuff_material.has_item(key, 1):
                add "gui/button/checkbox_filled.png"
            else:
                add "gui/button/checkbox_blank.png"
            text angestuff_recipes[i].ingredient_dictionary[key].name size 40
What I wanted it to do is display the names of the ingredients by getting the keys inside ingredient_dictionary and then check if each ingredient is in the inventory. But it's giving me this KeyError:

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 78, in script call
    call roomload
  File "game/technical.rpy", line 2979, in script
    $ renpy.pause(hard=True)
  File "game/technical.rpy", line 2979, in <module>
    $ renpy.pause(hard=True)
  File "game/screens.rpy", line 1307, in execute
    screen cookbook():
  File "game/screens.rpy", line 1307, in execute
    screen cookbook():
  File "game/screens.rpy", line 1310, in execute
    hbox:
  File "game/screens.rpy", line 1314, in execute
    if chr_protag.player == "ange":
  File "game/screens.rpy", line 1315, in execute
    grid 2 2:
  File "game/screens.rpy", line 1318, in execute
    for i in range(4*page_number, 4*(page_number+1)):
  File "game/screens.rpy", line 1319, in execute
    if i < len(angestuff_recipes):
  File "game/screens.rpy", line 1320, in execute
    frame:
  File "game/screens.rpy", line 1335, in execute
    frame:
  File "game/screens.rpy", line 1340, in execute
    vbox:
  File "game/screens.rpy", line 1341, in execute
    for key in angestuff_recipes[i].ingredient_dictionary:
  File "game/screens.rpy", line 1342, in execute
    hbox:
  File "game/screens.rpy", line 1344, in execute
    if angestuff_material.has_item(key, 1):
  File "game/screens.rpy", line 1344, in <module>
    if angestuff_material.has_item(key, 1):
  File "game/technical.rpy", line 1088, in has_item
    return (self.inventory[item] >= amount)
KeyError: <store.Material object at 0x0000000006be3520>

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "game/script.rpy", line 78, in script call
    call roomload
  File "game/technical.rpy", line 2979, in script
    $ renpy.pause(hard=True)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\ast.py", line 1138, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\python.py", line 1122, in py_exec_bytecode
    exec(bytecode, globals, locals)
  File "game/technical.rpy", line 2979, in <module>
    $ renpy.pause(hard=True)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\exports.py", line 1637, in pause
    rv = renpy.ui.interact(mouse='pause', type='pause', roll_forward=roll_forward, pause=delay, pause_modal=modal)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\ui.py", line 299, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\core.py", line 3579, in interact
    repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, pause=pause, pause_start=pause_start, pause_modal=pause_modal, **kwargs) # type: ignore
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\core.py", line 4052, in interact_core
    root_widget.visit_all(lambda d : d.per_interact())
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\core.py", line 681, in visit_all
    d.visit_all(callback, seen)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\core.py", line 681, in visit_all
    d.visit_all(callback, seen)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\core.py", line 681, in visit_all
    d.visit_all(callback, seen)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\screen.py", line 476, in visit_all
    callback(self)
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\core.py", line 4052, in <lambda>
    root_widget.visit_all(lambda d : d.per_interact())
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\screen.py", line 487, in per_interact
    self.update()
  File "C:\Users\thepr\Downloads\renpy-7.4.11-sdk\renpy\display\screen.py", line 680, in update
    self.screen.function(**self.scope)
  File "game/screens.rpy", line 1307, in execute
    screen cookbook():
  File "game/screens.rpy", line 1307, in execute
    screen cookbook():
  File "game/screens.rpy", line 1310, in execute
    hbox:
  File "game/screens.rpy", line 1314, in execute
    if chr_protag.player == "ange":
  File "game/screens.rpy", line 1315, in execute
    grid 2 2:
  File "game/screens.rpy", line 1318, in execute
    for i in range(4*page_number, 4*(page_number+1)):
  File "game/screens.rpy", line 1319, in execute
    if i < len(angestuff_recipes):
  File "game/screens.rpy", line 1320, in execute
    frame:
  File "game/screens.rpy", line 1335, in execute
    frame:
  File "game/screens.rpy", line 1340, in execute
    vbox:
  File "game/screens.rpy", line 1341, in execute
    for key in angestuff_recipes[i].ingredient_dictionary:
  File "game/screens.rpy", line 1342, in execute
    hbox:
  File "game/screens.rpy", line 1344, in execute
    if angestuff_material.has_item(key, 1):
  File "game/screens.rpy", line 1344, in <module>
    if angestuff_material.has_item(key, 1):
  File "game/technical.rpy", line 1088, in has_item
    return (self.inventory[item] >= amount)
KeyError: <store.Material object at 0x0000000006be3520>

Windows-10-10.0.22621 AMD64
Ren'Py 8.1.1.23060707
Sweet Release 1.0
Tue Jan 16 20:01:06 2024
Here's how I'm defaulting the recipes:

Code: Select all

default cookies_recipe = Recipe(cookies, {flour:1, sugar:1, eggs:1, chocolate:1, butter:1, milk:1})

philat
Eileen-Class Veteran
Posts: 1912
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Classes only partially saving?

#5 Post by philat »

Again, impossible to say because you won't post all the relevant code 🤷‍♀️ but basically the error is saying there's an issue referring to an instance of a Material() class object. I would guess that you didn't default it, but that's just guessing.

User avatar
voluorem
Regular
Posts: 29
Joined: Fri Jun 24, 2022 3:32 pm
Projects: Sweet Release
Discord: voluorem
Contact:

Re: Classes only partially saving?

#6 Post by voluorem »

I didn't think it was relevant because it worked fine before and I didn't change anything, but:

Code: Select all

default flour = Material("flour", "Flour", "gui/button/rarity_1.png", 1, None, "gui/button/material_flour.png")
default sugar = Material("sugar", "Sugar", "gui/button/rarity_1.png", 1, None, "gui/button/material_sugar.png")
default milk = Material("milk", "Milk", "gui/button/rarity_1.png", 1, None, "gui/button/material_milk.png")
default eggs = Material("eggs", "Eggs", "gui/button/rarity_1.png", 1, None, "gui/button/material_eggs.png")
default butter = Material("butter", "Butter", "gui/button/rarity_1.png", 1, None, "gui/button/material_butter.png")
default chocolate = Material("chocolate", "Chocolate", "gui/button/rarity_1.png", 1, None, "gui/button/material_chocolate.png")
And here's the Material class if that helps at all:

Code: Select all

class Material(object):
    def __init__(self, shortname, name, rarity_img, rarity, growthtime, img):
        self.shortname = shortname
        self.name = name
        self.rarity_img = rarity_img
        self.rarity = rarity
        self.growthtime = growthtime
        self.img = img

Post Reply

Who is online

Users browsing this forum: Google [Bot]