Renpy, rollback and variables

Jackboo1

Member
Aug 26, 2017
277
1,387
I have two classes and some functions to handle an inventory system. This system allows me to create a store to buy items anywhere easily, and add multiple items in the inventory, and if this item already exists, the quantity of the same item is displayed, without show the item twice or more.

The problem here is that Renpy doesn't save changes to objects to allow you to go back using the Rollback function. Looking for a solution, I came across that you have to add "renpy.store.object" as inherit in the classes. But that isn't working for me.

This behavior creates a hierarchy of other issues in the entire game. If you don't interact with any label after adding items to inventory, these items are not saved if you reload the game. After loading, the inventory is completely empty.

The issue also affects a daytime system in the game, where I have variables associated with week days... if I skip days and rollback the game takes me to the last label interaction, and not to the previous day.

Does anyone have a pratical fix for this?

Vren had a similar problem, but his solution, like I said, doesn't seem to work for me.

 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
Well my classes and functions do work even without that solution. But I create the class after label start, while it gets defined in a init -99 python: block
Where do you "create" it? Also in an init block perhaps? If so try it after label start
$ name = className(parameters)
 
  • Like
Reactions: Jackboo1

Jackboo1

Member
Aug 26, 2017
277
1,387
Well my classes and functions do work even without that solution. But I create the class after label start, while it gets defined in a init -99 python: block
Where do you "create" it? Also in an init block perhaps? If so try it after label start
$ name = className(parameters)
Do you mean after label start, but indented to start, or after the whole label, not indented?

I think this issue is because I don't have any init priorities set, maybe?

This is how the code is presented:

inv.rpy file

Here are the classes, functions and screens with the actions. And on top the "init python:" for the classes and functions.

vars.rpy file

Only "init" on top for all the variables. The code here starts with define for the characters > default for default values (variables) and then > the variables itself, including inv = {}

scripts.rpy

Nothing here inside the label start, besides the code for choosing the character name - not real dialog yet. After label start, not indented, I have 8 labels for the map navigation, calling the inventory etc, etc.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,565
7,378
The issue is probably in how you declared the object. You probably either want to do:
Code:
default myInventory = MyInventoryClass()
or else, as one of the very first lines after "start"
Code:
$ myInventory = MyInventoryClass()
Ren'py only saves objects that are declared with "default" (the first option) or which are set after the "start" label is executed. Thus, while your classes should be defined in the "init python" block, the instance of the class itself should not be initialized inside the python block.

In general, the 'default' approach is superior, in that if you add a new object between v1 and v2 of your game, the default statement will add it into the v2 game when a v1 save is loaded. That's the entire point of "default".

Note that the default statement should be outside any init block, although Ren'py will run it as if it was declared in one. (At init level 0, as I recall, which typically means your class should be defined earlier, or else in a file that comes alphabetically beforehand.)
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
Do you mean after label start, but indented to start, or after the whole label, not indented?

I think this issue is because I don't have any init priorities set, maybe?

This is how the code is presented:

inv.rpy file

Here are the classes, functions and screens with the actions. And on top the "init python:" for the classes and functions.

vars.rpy file

Only "init" on top for all the variables. The code here starts with define for the characters > default for default values (variables) and then > the variables itself, including inv = {}

scripts.rpy

Nothing here inside the label start, besides the code for choosing the character name - not real dialog yet. After label start, not indented, I have 8 labels for the map navigation, calling the inventory etc, etc.
Hmh, try doing the classes and functions in inv.rpy at least as an init -1 python:
just to be safe.
Ok so you create the inventory as a
Code:
default inv = class(Param)
?
If so then it should work...
If the class and functions were unknown to renpy it wouldn't even create the inventory and crash your game with a message: couldn't find className bla bla
So it at least found the class and it's functions.... Without proper code it's hard to tell where the error is though...
If you don't want to post it in the open, you could send it per PM?

The issue is probably in how you declared the object. You probably either want to do:
Code:
default myInventory = MyInventoryClass()
or else, as one of the very first lines after "start"
Code:
$ myInventory = MyInventoryClass()
Ren'py only saves objects that are declared with "default" (the first option) or which are set after the "start" label is executed. Thus, while your classes should be defined in the "init python" block, the instance of the class itself should not be initialized inside the python block.

In general, the 'default' approach is superior, in that if you add a new object between v1 and v2 of your game, the default statement will add it into the v2 game when a v1 save is loaded. That's the entire point of "default".

Note that the default statement should be outside any init block, although Ren'py will run it as if it was declared in one. (At init level 0, as I recall, which typically means your class should be defined earlier, or else in a file that comes alphabetically beforehand.)
Yep but in some cases it's counter productive to add a class in a savegame later on. I.e. a Questlog, which would need a million if->elif->else statements to check which quests where done by the time the game gets loaded and which weren't... But in this case default is definitely better than adding it after label start.
 
  • Like
Reactions: Jackboo1

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,565
7,378
Yep but in some cases it's counter productive to add a class in a savegame later on. I.e. a Questlog, which would need a million if->elif->else statements to check which quests where done by the time the game gets loaded and which weren't... But in this case default is definitely better than adding it after label start.
Of course, adding a class in v2 and then using it in code that was part of v1 would obviously make for challenges. But if you added new functionality via new classes and only accessed it in your v2 code, then it wouldn't be a problem, since your class would be "empty" at the start of your v2 code regardless of whether you started from the beginning or from a save.

One real "gotcha," however, is if you have an existing class to which you add a new attribute in v2. v1 saves will be missing that attribute when restored (i.e. unpickled), which means that you have to devote some extra effort to post-processing older saves. Thus, you might have to include some "after_load" fu...
 

Jackboo1

Member
Aug 26, 2017
277
1,387
Thanks for the tips guys. But I don't think I can fix this without change how my inventory works. According to my research with some game inventory scripts, this seems like a common problem in renpy.

Here is what happens most clearly:

- My inventory consists of classes, functions, and screens.
- The player can open the inventory anytime (and then you can use items or remove items, interacting only with screens - Show, Hide, etc) or buy items any time, by clicking on two respective buttons on the screen.
- This project is "open world", you can go to the kitchen, living room, etc - and it's using labels, screens and calls.

Event 1

1. The player is in the living room, opens the inventory and buys items
2. Items are added to the inventory
3. If the player saves the game here, nothing in the inventory will be saved

Event 2

1. The player is in the living room, opens the inventory and buys items
2. Items are added to the inventory
3. Then the player choose to go to the kitchen (new label)
4. If the player saves the game after that, the inventory is saved with no issues, not only that, but also the day and period of day are saved as well. Everything is correctly.

By my understanding, when calling another label, Renpy creates a checkpoint that allows you to rollback, and it saves all the changes. I tried to explore a fix with renpy.checkpoint, but without success.

I think I'm going to need to change this inventory system, so when the player buys items, the character says something about it, because it creates a checkpoint.
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
How are you "calling" the inventory and/or shop if I may ask? :)
 

Jackboo1

Member
Aug 26, 2017
277
1,387
How are you "calling" the inventory and/or shop if I may ask? :)
default inventory = {}, inside the vars.rpy file, all variables here have default values.

This simple code shows what is my problem with renpy



You can change the number, but if you save and load, the variable returns to the default 10... all the changes are gone

I found this



but...nothing

:biggrin:
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
Actually all you're missing is a simple:
Code:
    $ renpy.retain_after_load()
before each:
Code:
    call screen changenumber
in your script file it must look like this before each screen call that changes variables:
Code:
    $ renpy.retain_after_load()
    call screen changenumber
p.s.: I experimented a bit with that here's what I did to your code:

Code:
screen changenumber(obj):
    textbutton "Change Number" yalign 0.5 xalign 0.5:
        action Function(obj.updateNumber)
    textbutton "Exit" yalign 0.7 xalign 0.5:
        action Return()

    python:
        realnumber = obj.displayNumber()
    text "{size=40}[realnumber]{/size}" xalign 0.5

init -1 python:
    import renpy.store as store
    import renpy.exports as renpy

    class numberChange(store.object):
        number = 0
        def createNumber(self, number):
            self.number = number
        def displayNumber(self):
            return self.number
        def updateNumber(self, number=1):
            self.number += number

label start:

    $ first = numberChange()
    $ second = numberChange()
    $ first.createNumber(10)

    scene black with dissolve
    "Test before"
    $ renpy.retain_after_load()
    $ renpy.call_screen("changenumber", first)

    "Test after"
    $ renpy.retain_after_load()
    $ renpy.call_screen("changenumber", second)
    "Another after"

    return
p.s.s.: Good luck and have fun with your project :) If you need help don't hesitate to ask ;)
 
  • Like
Reactions: Jackboo1

Jackboo1

Member
Aug 26, 2017
277
1,387
You are a genius. In fact, I see this function in renpy doc before, but didn't try to use yet.

I put $ renpy.retain_after_load() right after the last interacts with the variables and inventory, and it works beautifully... what short end to my pain :biggrin:

Thank you my man :p

Btw, is this safe to use and abuse? o_O
 
  • Like
Reactions: Palanto

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
Depends on what you mean by use and abuse :D But it's definitely safe to "use" ;)
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
You are a genius. In fact, I see this function in renpy doc before, but didn't try to use yet.

I put $ renpy.retain_after_load() right after the last interacts with the variables and inventory, and it works beautifully... what short end to my pain :biggrin:

Thank you my man :p

Btw, is this safe to use and abuse? o_O
btw. what do you mean "after" the last interacts? :D It should be put before the screen call :D
 
  • Like
Reactions: Jackboo1

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,565
7,378
To follow up a bit on what's going on, if you're in the middle of a statement (like "call screen") when Ren'py saves, the state of the game will be rolled back to its state at the BEGINNING of that statement. In other words, to just before the "call screen." That's why your variables were resetting.

renpy.retain_after_load() is explicitly designed to address that issue.

See