Ren'Py Help with Persistence/Loading

Somatra

Member
Game Developer
Mar 15, 2021
458
575
I have this problem in my game:


Code:
label start:

    $persistent.coldweapons = 0
    $persistent.SkillPoints = 0
  
screen game_play: 
    showif status == True:                                      ## Show the status menu
        showif persistent.skillpoints > 0:           ## This controls if u have points to use.
            showif persistent.coldweapons <= 100:   ## This is needed cause the maximum of skill is 100
                textbutton "+":
                   action [SetVariable("persistent.skillpoints",persistent.skillpoints-1),
                   SetVariable("persistent.coldweapons",persistent.coldweapons+1)]
                   xpos any number
                   ypos any number
Everything works fine until u try to load the save

File "game/script/script.rpy", line 1212, in execute
screen begin_gameplay:
File "game/script/script.rpy", line 9400, in execute
showif Status_on == True:
File "game/script/script.rpy", line 9620, in execute
showif persistent.Skill_points > 0:
File "game/script/script.rpy", line 9621, in execute
if persistent.cold_weapons < 100:
File "game/script/script.rpy", line 9622, in execute
textbutton "{size=28}{color=#FFFFFF}+{/size}{/color}":
File "game/script/script.rpy", line 9622, in keywords
textbutton "{size=28}{color=#FFFFFF}+{/size}{/color}":
File "<screen language>", line 9623, in <module>
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'

IDK why this happens. But I know what means that's basically said to me that's my persistent is == None and none is not a number so they can do " none-1"
But the persistent are supposed to save when the game close at least is that's whats are on manual


** All data reachable through fields on persistent is saved when Ren'Py terminates, or when renpy.save_persistent() is called. Persistent data is loaded when Ren'Py starts, and when Ren'Py detects that the persistent data has been updated on disk. **

Does anyone know how I can fix this? and why this happens?
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
Not sure why the screen is not working, I may try to deep delve that later if I can find time.

But...

My initial reaction is that you're using persistent for the wrong reasons.
I figure there's two possibilities here... either:
  • You're an experienced dev who has a real practical reason to use persistent- in which case, ignore the rest of my comments.
  • You're an inexperienced dev who saw persistent somewhere and misunderstood it's usage.
persistent is for game wide variables. Stuff, completely independent of the multiple playthroughs or the current save file. Things like the sound volume or whether the game should run fullscreen or not. A more common use would be things like showing Unlocked Achievements or Scene/Replay Galleries... unlocked through multiple playthroughs of a game.

You have the following variables from your screenshot:

Python:
persistent.fire_weapons
persistent.cold_weapons
persistent.Skill_points
persistent.Acting
persistent.Cooking
persistent.Dancing

Taking Cooking as an example... to me, that looks like "player cooking skill". Lets say a player starts with a cooking skill 0 and through various actions in game, ends up at cooking skill 35 by the time they finish the game. Except, next time they start a new game - things start at 35, not 0... because Cooking is persistent.

You may have coded around this, resetting the value as the game starts. But that overlooks the core problem. persistent is not directly for player driven changes.

A more serious consequence of this would be " " to raise the values extraordinarily high.

Let's say there's a scene in your game...
Doing a specific action or picking a particular choice results in $ persistent.Cooking += 1.
The player comes along and just before this scene starts... saves the game. They play through the scene and Cooking rises from 0 to 1... as intended.
Next... the player loads their save file and replays the same scene again. Because Cooking is persistent, it rises from 1 to 2.
Rinse and repeat... through "creative use of game mechanics", a player replaying that scene a few hundred times would be able to raise their cooking to 100, 200, 10,000 if they were really patient enough.
That can't be your intention, surely?

Comments like ## This controls if u have points to use. in your copy/pasted code make me think these are player driven values not system wide.

Which is why my reaction is that you shouldn't really be using persistent for these variables.

Instead, create the variables using default and let them be "normal" variables.

Python:
default fire_weapons = 0
default cold_weapons = 0
default Skill_points = 20
default Acting = 0
default Cooking = 0
default Dancing = 0

I'm making a LOT of assumptions here. Maybe Cooking really is completely independent of player actions/choices. Maybe you're trying to implement some sort of "Game+" solution, where a player can finish a game and start a new one, carrying forward skills from the previous playthrough (though if you're trying that, a better solution would be to use normal variables for normal gameplay and then copy the values to/from persistent variables at the very start/end of the game).

This post only aims to warn you that you may be on the wrong track. If you've already thought all this through or I've got the wrong end of the stick... just keep in mind I'm reacting to a very limited viewpoint of your project.

I'll try to return to your original problem later... but I've got a busy couple of days ahead...
 
Last edited:

MidnightArrow

Member
Aug 22, 2021
499
429
IIRC "persistent" returns a special type of variable to allow comparisons with variables that haven't been declared yet.

The reason you're having a problem is because "persistent.SkillPoints" was declared to be an integer but you're trying to compare/assign to the uninitialized variable "persistent.skillpoints" which is returning None. Those are two different variables since the capitalization is different.

But I totally agree with 79flavors that this is not a good usage of "persistent".
 
  • Like
Reactions: Somatra

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
The reason you're having a problem is because "persistent.SkillPoints" was declared to be an integer but you're trying to compare/assign to the uninitialized variable "persistent.skillpoints" which is returning None. Those are two different variables since the capitalization is different.

Good catch.

I also note that the error screen also mentions showif persistent.Skill_points > 0:

So far in your code, I've already now noticed:

Python:
$ persistent.Skill_points = 0
$persistent.SkillPoints = 0

showif persistent.skillpoints > 0:

Python (and by extension RenPy) is a case sensitive language. mydata, myData, MyData, MYData and MYDATA are separate and independent of each other.

There are a number of conventions within programming.
  • One is " ", where the names start with a lower case letter, but each new word is capitialized. myData would be an example of Camel Case.
  • Another is "Pascal Case", which is just the same as Camel Case... except the first word is also capitalized. MyData would be an example of Pascal Case.
  • Another is " ", which uses all lowercase - but each word is separated by an underscore. my_data would be an example of Snake Case.
There are, of course, others (like " ", which doesn't work with RenPy anyway).

Personally, I only ever use Snake Case for variables, functions, screen and file names. Then Pascal Case for classes.
ALWAYS using underscores and lowercase for pretty much everything means I never need worry about the sort of issue you've run into.

The problem is... typing as camelCase or PascalCase is kind of a habit... and it's a habit that is hard to break. Worse if you already have a lot of code that is already there and worse still (as you have), if it's inconsistent.

My suggestion is to change everything to snake_case. Short term pain for long term peace of mind.
If that's a step too far, as least change everything to camelCase.

There is no single right answer here. But as long as you're consistent... you should be fine.
 
  • Like
Reactions: Somatra

MidnightArrow

Member
Aug 22, 2021
499
429
Good catch.

I also note that the error screen also mentions showif persistent.Skill_points > 0:

So far in your code, I've already now noticed:

Python:
$ persistent.Skill_points = 0
$persistent.SkillPoints = 0

showif persistent.skillpoints > 0:
I actually just noticed the OP has different code in the attached screenshot from what they posted in the code block, so now I'm not sure which is the code they're trying to troubleshoot. But anyway "Persistent" will return a None datatype if the variable isn't defined so somewhere in the code the OP is comparing an integer to a persistent variable that isn't defined.
 

Somatra

Member
Game Developer
Mar 15, 2021
458
575
Not sure why the screen is not working, I may try to deep delve that later if I can find time.

But...

My initial reaction is that you're using persistent for the wrong reasons.
I figure there's two possibilities here... either:
  • You're an experienced dev who has a real practical reason to use persistent- in which case, ignore the rest of my comments.
  • You're an inexperienced dev who saw persistent somewhere and misunderstood it's usage.
persistent is for game wide variables. Stuff, completely independent of the multiple playthroughs or the current save file. Things like the sound volume or whether the game should run fullscreen or not. A more common use would be things like showing Unlocked Achievements or Scene/Replay Galleries... unlocked through multiple playthroughs of a game.

You have the following variables from your screenshot:

Python:
persistent.fire_weapons
persistent.cold_weapons
persistent.Skill_points
persistent.Acting
persistent.Cooking
persistent.Dancing

Taking Cooking as an example... to me, that looks like "player cooking skill". Lets say a player starts with a cooking skill 0 and through various actions in game, ends up at cooking skill 35 by the time they finish the game. Except, next time they start a new game - things start at 35, not 0... because Cooking is persistent.

You may have coded around this, resetting the value as the game starts. But that overlooks the core problem. persistent is not directly for player driven changes.

A more serious consequence of this would be " " to raise the values extraordinarily high.

Let's say there's a scene in your game...
Doing a specific action or picking a particular choice results in $ persistent.Cooking += 1.
The player comes along and just before this scene starts... saves the game. They play through the scene and Cooking rises from 0 to 1... as intended.
Next... the player loads their save file and replays the same scene again. Because Cooking is persistent, it rises from 1 to 2.
Rinse and repeat... through "creative use of game mechanics", a player replaying that scene a few hundred times would be able to raise their cooking to 100, 200, 10,000 if they were really patient enough.
That can't be your intention, surely?

Comments like ## This controls if u have points to use. in your copy/pasted code make me think these are player driven values not system wide.

Which is why my reaction is that you shouldn't really be using persistent for these variables.

Instead, create the variables using default and let them be "normal" variables.

Python:
default fire_weapons = 0
default cold_weapons = 0
default Skill_points = 20
default Acting = 0
default Cooking = 0
default Dancing = 0

I'm making a LOT of assumptions here. Maybe Cooking really is completely independent of player actions/choices. Maybe you're trying to implement some sort of "Game+" solution, where a player can finish a game and start a new one, carrying forward skills from the previous playthrough (though if you're trying that, a better solution would be to use normal variables for normal gameplay and then copy the values to/from persistent variables at the very start/end of the game).

This post only aims to warn you that you may be on the wrong track. If you've already thought all this through or I've got the wrong end of the stick... just keep in mind I'm reacting to a very limited viewpoint of your project.

I'll try to return to your original problem later... but I've got a busy couple of days ahead...
Well I already tried the default variables like the example
Code:
default fire_weapons = 0
but for some reason, they reset to the original value when a player save and load the game
I show here more info maybe u can help me solve this problem, cause I can't do it with default and I can't do it with persistent
I'm not an experienced dev but I study hard at least.

Just out of curiosity the default values have to be inside the start label oooooooooor i can put them anywhere in the cod?

I want to understand why they work when I open and played normally.
and why they don't load correctly
 

Somatra

Member
Game Developer
Mar 15, 2021
458
575
I really need to fix this to launch the 0.3 but no clue at the moment :cry:
 

Playstorepers

Member
May 24, 2020
160
79
default can be anywhere and even should be anywhere, except start, tbh.
Same with your define, which you have also put into your start.

Just like the others before me said, you should refrain from using persistent-variables unless you need to, since they work globally for each save-state etc.

I'd advise you to put all your default/define variables into an extra rpy-file.

As for the reason, why the game doesn't load:
It's just as the people above me said, you have a capital SkillPoints and persistent.skillpoints in the showif.

I'd still recommend you to change your entire organization of variables. Persistent is just not what you want for your goal.
 

Somatra

Member
Game Developer
Mar 15, 2021
458
575
default can be anywhere and even should be anywhere, except start, tbh.
Same with your define, which you have also put into your start.

Just like the others before me said, you should refrain from using persistent-variables unless you need to, since they work globally for each save-state etc.

I'd advise you to put all your default/define variables into an extra rpy-file.

As for the reason, why the game doesn't load:
It's just as the people above me said, you have a capital SkillPoints and persistent.skillpoints in the showif.

I'd still recommend you to change your entire organization of variables. Persistent is just not what you want for your goal.
Isn't abt capital I just mistake the writing here. The error is causing by the persistent.cold_weapons doesn't exist when u load the game.

as I said I already tried the default but for some reason when you load the game they reset to initial values
 

Playstorepers

Member
May 24, 2020
160
79
Isn't abt capital I just mistake the writing here. The error is causing by the persistent.cold_weapons doesn't exist when u load the game.

as I said I already tried the default but for some reason when a loading they reset to initial values
I assume the save-load error is a side-effect of the RenPy-variable save feature.
It only saves variable-changes, after a line of text was displayed to which you can rollback iirc.
So if you change the variables in your screen and do nothing but save and reload, nothing gets saved.

EDIT:

Since I'm a shitty programmer as well, you shouldn't take it from me, but google yielded this:
 

Somatra

Member
Game Developer
Mar 15, 2021
458
575
I assume the save-load error is a side-effect of the RenPy-variable save feature.
It only saves variable-changes, after a line of text was displayed to which you can rollback iirc.
So if you change the variables in your screen and do nothing but save and reload, nothing gets saved.

EDIT:

Since I'm a shitty programmer as well, you shouldn't take it from me, but google yielded this:
my game doesn't use the renpy textbox and rollbacks are disabled
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
Well I already tried the default variables [...]
but for some reason, they reset to the original value when a player save and load the game

Something else is happening.
Because that's not how default works.

RenPy scans for any default lines as the game is player clicks [START].
If the variable doesn't already exist, the variable is created and the initial value is assigned.
If the variable already exists, the "default" value is used.

Nothing in the save/load processes messes with this. Although values stored in the save file will overwrite the default values when a save file is loaded. But the values will be the values when the save file was saved... not the original zero/False/etc values.

[...] the default values have to be inside the start label oooooooooor can I put them anywhere in the code?

They can be written anywhere. But usually they are put at the top of the script file. Some developers will create a separate script file with just the variables in it.

But I would expect something vaguely like:

Python:
define mc = Character("[mc_name]")
define e = Character("Eileen")

default mc_name = "Unnamed"

default acting = 0
default cooking = 0
default dancing = 0
default swimming = 0
default climbing = 0

default saw_tooth_fairy = False
default visited_santa_clause = False

label start:

    scene black with fade

    $ mc_name = renpy.input("What is your name? {i}(Press ENTER for 'Peter'){/i}")
    $ mc_name = mc_name.strip() or "Peter"

    "Welcome to my game [mc]."

    "*** THE END ***"
    return

There used to be a old method for creating variables using init: and $ lines. But it had problems and it's the main reason why the default statement was created.


It isn't about capitals... I just mistake the writing here. The error is causing by the persistent.cold_weapons doesn't exist when u load the game.

as I said I already tried the default but for some reason when you load the game they reset to initial values

I guess our worry is that if you can make a typo here - you can make a typo in your script.
I know I've spent hours looking at my own code before... and I read what I "intended" to type... not what I actually typed.

Let me download 0.2 of your game. See if I can see any obvious reasons why the default thing would cause problems for loading and saving.
 
  • Like
Reactions: Somatra

Playstorepers

Member
May 24, 2020
160
79
my game doesn't use the renpy textbox and rollbacks are disabled
Which would explain, why the variables don't save normally xD

According to this, they found a solution:


EDIT:
Try this one:


$ renpy.retain_after_load()
is the one that you're looking for, but I haven't read, how to use it efficiently.
 
Last edited:
  • Like
Reactions: Somatra

Somatra

Member
Game Developer
Mar 15, 2021
458
575
Which would explain, why the variables don't save normally xD

According to this, they found a solution:
I'll read when i have a more time now I'm in my work through my cellphone.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,286
Something else is happening.
Because that's not how default works.
Totally agree with this. default is performed at start time and load time, and the value will be assigned only if the variable don't already exist:
Code:
        if self.varname not in defaults_set:

            if start or (self.varname not in d.ever_been_changed):
                d[self.varname] = renpy.python.py_eval_bytecode(self.code.bytecode)

            d.ever_been_changed.add(self.varname)

            defaults_set.add(self.varname)

        else:

            if start and renpy.config.developer:
                raise Exception("{}.{} is being given a default a second time.".format(self.store, self.varname))
Humanly said:
If the variable haven't been "defaulted" yet, and if it's not start time, and if the variable isn't already known, give it a value. In all other case, do not give it a value.

So far, my guess goes for the fact that the value isn't effectively changed due to either Ren'py code optimization (an interaction is needed for the change to take effect) or due to the change happening in another context.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,286
Sorry for the double post.

So far, my guess goes for the fact that the value isn't effectively changed due to either Ren'py code optimization (an interaction is needed for the change to take effect) or due to the change happening in another context.
And I was right. Once you pass the introduction, go to the "Traits" tab, and click on the "+" to add one point in acting.
If you save there, then load, the point disappear.
But if you do other things, by example I goes to the map, clicked on the square with the red star, then decided to take a bath then sleep. And now that there were interactions, the change is effective. If you save, then load, the acting point is still present.
 

Somatra

Member
Game Developer
Mar 15, 2021
458
575
Sorry for the double post.



And I was right. Once you pass the introduction, go to the "Traits" tab, and click on the "+" to add one point in acting.
If you save there, then load, the point disappear.
But if you do other things, by example I goes to the map, clicked on the square with the red star, then decided to take a bath then sleep. And now that there were interactions, the change is effective. If you save, then load, the acting point is still present.
so all that's I have to do is fake an interaction? like those, I use in the bath and sleep? when i come home I'll run some tests
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
So... took me a little while to catch up to where you both already were.

You don't have permission to view the spoiler content. Log in or register now.

But the problem as I see it is due to the way the game progresses.

The game shows a screen, but within that single screen the game progresses by setting various variables which impact the screen contents (hence all the showif commands). The player clicks a textbutton and that button sets a variable, which shows some other text/image and that new text might include a completely new textbutton that does something else. All the progress happens within the screen... until a new screen is shown.

The problem is that RenPy doesn't update game variables until the screen is closed. Well, sort of... I think what it really is... is the next RenPy statement outside of the screen is executed. So because the action never leaves the screen... none of that progress is acknowledged until the next RenPy command is processed. If you save after clicking a few things... the variables haven't been updated yet and so their values are saved as they were just before the screen was shown.

I think the answer, without rewriting your code from scratch is to exit the screen and then reshow it each time the action is to ONLY set a variable.

So for example...

BEFORE:

Python:
label Cg_Greating:
    hide screen begin_part02
    call screen Cg_Greating

screen Cg_Greating:
    add "Cg_hud"

    showif Greating_old_man_on == True:
        add "Greating_old_man":
            xpos 380
            ypos 110
            zoom 0.9
        add "Cg_Greating_01":
            xpos 1461
            ypos 95
        textbutton "{size=22}{color=#FF9700}Yes{/color}{/size}":
            action [SetVariable("Cg_Greating_02_on",True),SetVariable("Cg_Greating_01_on",False),
            SetVariable("Greating_old_man_on",False)]
            xpos 1550
            ypos 690
        textbutton "{size=22}{color=#FF9700}No{/color}{/size}":
            action [SetVariable("ending_edgar02",True),
            SetVariable("Greating_old_man_on",False),
            SetVariable("Cg_no_begin_on",True),
            SetVariable("finish_on",True)]
            xpos 1765
            ypos 690

    # lots more code like this...

AFTER:

Python:
label Cg_Greating:
    hide screen begin_part02

label reshow_cg_greating:
    call screen Cg_Greating
    jump reshow_cg_greating

screen Cg_Greating:
    add "Cg_hud"

    showif Greating_old_man_on == True:
        add "Greating_old_man":
            xpos 380
            ypos 110
            zoom 0.9
        add "Cg_Greating_01":
            xpos 1461
            ypos 95
        textbutton "{size=22}{color=#FF9700}Yes{/color}{/size}":
            action [SetVariable("Cg_Greating_02_on",True),
                    SetVariable("Cg_Greating_01_on",False),
                    SetVariable("Greating_old_man_on",False),
                    Return()]
            xpos 1550
            ypos 690
        textbutton "{size=22}{color=#FF9700}No{/color}{/size}":
            action [SetVariable("ending_edgar02",True),
                    SetVariable("Greating_old_man_on",False),
                    SetVariable("Cg_no_begin_on",True),
                    SetVariable("finish_on",True),
                    Return()]
            xpos 1765
            ypos 690

    # lots more code like this...

My theory is that by having each action ending in a Return() or a Jump() - the game will do at least one RenPy command (and all it's associated internal logic that updates variables and get things ready in case a save is done) before the screen is reshown.

You don't have permission to view the spoiler content. Log in or register now.
 
Last edited:

Somatra

Member
Game Developer
Mar 15, 2021
458
575
So... took me a little while to catch up to where you both already were.

You don't have permission to view the spoiler content. Log in or register now.

But the problem as I see it is due to the way the game progresses.

The game shows a screen, but within that single screen the game progresses by setting various variables which impact the screen contents (hence all the showif commands). The player clicks a textbutton and that button sets a variable, which shows some other text/image and that new text might include a completely new textbutton that does something else. All the progress happens within the screen... until a new screen is shown.

The problem is that RenPy doesn't update game variables until the screen is closed. Well, sort of... I think what it really is... is the next RenPy statement is executed. So because the action never leaves the screen... none of that progress is acknowledged until the next screen is processed. If you save after clicking a few things... the variables haven't been updated yet and so their values are saved as they were just before the screen was shown.

I think the answer, without rewriting your code from scratch is to exit the screen and then reshow it each time the action is to ONLY set a variable.

So for example...

BEFORE:

Python:
label Cg_Greating:
    hide screen begin_part02
    call screen Cg_Greating

screen Cg_Greating:
    add "Cg_hud"

    showif Greating_old_man_on == True:
        add "Greating_old_man":
            xpos 380
            ypos 110
            zoom 0.9
        add "Cg_Greating_01":
            xpos 1461
            ypos 95
        textbutton "{size=22}{color=#FF9700}Yes{/color}{/size}":
            action [SetVariable("Cg_Greating_02_on",True),SetVariable("Cg_Greating_01_on",False),
            SetVariable("Greating_old_man_on",False)]
            xpos 1550
            ypos 690
        textbutton "{size=22}{color=#FF9700}No{/color}{/size}":
            action [SetVariable("ending_edgar02",True),
            SetVariable("Greating_old_man_on",False),
            SetVariable("Cg_no_begin_on",True),
            SetVariable("finish_on",True)]
            xpos 1765
            ypos 690

    # lots more code like this...

AFTER:

Python:
label Cg_Greating:
    hide screen begin_part02

label reshow_cg_greating:
    call screen Cg_Greating
    jump reshow_cg_greating

screen Cg_Greating:
    add "Cg_hud"

    showif Greating_old_man_on == True:
        add "Greating_old_man":
            xpos 380
            ypos 110
            zoom 0.9
        add "Cg_Greating_01":
            xpos 1461
            ypos 95
        textbutton "{size=22}{color=#FF9700}Yes{/color}{/size}":
            action [SetVariable("Cg_Greating_02_on",True),
                    SetVariable("Cg_Greating_01_on",False),
                    SetVariable("Greating_old_man_on",False),
                    Return()]
            xpos 1550
            ypos 690
        textbutton "{size=22}{color=#FF9700}No{/color}{/size}":
            action [SetVariable("ending_edgar02",True),
                    SetVariable("Greating_old_man_on",False),
                    SetVariable("Cg_no_begin_on",True),
                    SetVariable("finish_on",True),
                    Return()]
            xpos 1765
            ypos 690

    # lots more code like this...

My theory is that by having each action ending in a Return() or a Jump() - the game will do at least one RenPy command (and all it's associated internal logic that updates variables and get things ready in case a save is done) before the screen is reshown.


You don't have permission to view the spoiler content. Log in or register now.
I understand your code it's a lot easier to do but as I said I'm just an amateur dev with a lot of will and u won't believe but I already do it that... somewhere in the clothing store ( attach )

but IDK if I have enough time to rewrite the code in that's way but will be good if i can...
Now I know how to fix the save/load problem and how to optimize my code to a lot better one <3

Thank you 79flavors , anne O'nymous and Playstorepers