Ren'Py A little help with Ren'py custom preferences

xxx3me

Newbie
Jun 19, 2020
57
30
I'm trying to implement a simple option in Preferences, so players can choose between having in-story tips or not. When active, player will be advised about the direction that decision will take to story, see alerts about good or bad decisions, etc. I know some people like to play "in the dark" for more immersion.

Basically, I did a new screen like this:

Python:
vbox:
                    style_prefix "radio"
                    label _("Tips")

                    textbutton _("Show") action [SetVariable("tips",True), gui.SetPreference("tips",True)] alt "Show tips in-game"
                    textbutton _("Hide") action [SetVariable("tips",False), gui.SetPreference("tips",False)] alt "Hide tips in-game"
I also defined a "default" option when the game starts, like this: define tips = gui.preference("tips",None)

And it kind of works to change teh var "tips" value, that I can reuse in many ways. The problem is: Ren'py is not storing the option properly, showing both active even being a radiogroup.

EDIT: I forgot to say it's happening when I load a game. I set it to Show, save the game. Set to Hide, load the game, boom, the two options checked.

1642035460713.png

Any experienced RP user could tell me how to make it work properly? Thank you!
 
Last edited:

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,490
7,035
Any experienced RP user could tell me how to make it work properly? Thank you!
I think the problem is that you aren't given the buttons anything to tell them whether they should be "selected" or not, so they're both coming up "selected."

So, one way of attacking this might be:
Code:
textbutton _("Show"):
    selected tips
    action [SetVariable("tips",True), gui.SetPreference("tips",True)]
    alt "Show tips in-game"
textbutton _("Hide"):
    selected not tips
    action [SetVariable("tips",False), gui.SetPreference("tips",False)]
    alt "Hide tips in-game"
In other words, explicitly tell the individual buttons whether they should be selected or not based on the value of "tips".

When I've made preferences, however, I've stored the results in persistent values. So, I use:
Code:
vbox:
    label _("Dialog Font")
    textbutton _("Original") action [SetField(persistent, "override_say_font", False), Function(adjust_say_font)]
    textbutton _("Alternate") action [SetField(persistent, "override_say_font", True), Function(adjust_say_font)]
(the adjust_say_font then fiddles with the font style settings, based on the value of persistent.override_say_font. It's also invoked during the splash screen so that the font gets set up correctly.)

I think part of the secret sauce on all this stuff, is listed in
Actions may determine when a button is selected or insensitive.
So, it's possible that SetVariable doesn't do this, but SetField does. Not 100% sure, but when I use SetField the buttons behave the way I want them to, so...

But, one reason you should use persistent data instead of "vanilla in-game variables" in your preferences is that if someone invokes the Preferences off your main menu, you're not inside a game at that point, so you don't have access to game variables. Thus, not sure what your SetVariable would even do then. You do have access to the persistent stuff then, however, so the SetField approach works both inside and outside a running game.
 
  • Red Heart
Reactions: xxx3me

xxx3me

Newbie
Jun 19, 2020
57
30
Thanks! The selected attribute solution seems to be the way, just didn't find a way to make the preferences keep it when returning from a saved game. So when using:

Python:
textbutton _("Show"):
    selected tips
    action [SetVariable("tips",True), gui.SetPreference("tips",True)]
    alt "Show tips in-game"
textbutton _("Hide"):
    selected not tips
    action [SetVariable("tips",False), gui.SetPreference("tips",False)]
    alt "Hide tips in-game"
And...

Python:
define tips = gui.preference("tips", True)
to define and "remember" it, everything seems to work, except if: a game is saved with Hide, and the game saved with Hide active is loaded while Show is active on preferences (or vice-versa). The option will be set as tips True (show), but the var will still be False.

Ahh, I'll try again later. Probably it's something so easy and I'm not seeing...
 

crabsinthekitchen

Well-Known Member
Apr 28, 2020
1,550
8,807
I'm trying to implement a simple option in Preferences, so players can choose between having in-story tips or not. When active, player will be advised about the direction that decision will take to story, see alerts about good or bad decisions, etc. I know some people like to play "in the dark" for more immersion.

Basically, I did a new screen like this:

Python:
vbox:
                    style_prefix "radio"
                    label _("Tips")

                    textbutton _("Show") action [SetVariable("tips",True), gui.SetPreference("tips",True)] alt "Show tips in-game"
                    textbutton _("Hide") action [SetVariable("tips",False), gui.SetPreference("tips",False)] alt "Hide tips in-game"
I also defined a "default" option when the game starts, like this: define tips = gui.preference("tips",None)

And it kind of works to change teh var "tips" value, that I can reuse in many ways. The problem is: Ren'py is not storing the option properly, showing both active even being a radiogroup.

EDIT: I forgot to say it's happening when I load a game. I set it to Show, save the game. Set to Hide, load the game, boom, the two options checked.

View attachment 1593635

Any experienced RP user could tell me how to make it work properly? Thank you!
In my mod I do the same thing and I just use a checkbox and store the value in the persistent file

Python:
hbox:
    frame:
        style_prefix "check"
        has vbox
        label _("Walkthrough")
        $ setting_description = "Enabled" if persistent.crabsinthekitchen_wt_mod else "Disabled"
        textbutton "[setting_description]" action [ToggleField(persistent, 'crabsinthekitchen_wt_mod', True, False), ToggleScreenVariable("setting_description", "Enabled", "Disabled")]
 
Last edited:

noping123

Well-Known Member
Game Developer
Jun 24, 2021
1,510
2,451
Thanks! The selected attribute solution seems to be the way, just didn't find a way to make the preferences keep it when returning from a saved game. So when using:

Python:
textbutton _("Show"):
    selected tips
    action [SetVariable("tips",True), gui.SetPreference("tips",True)]
    alt "Show tips in-game"
textbutton _("Hide"):
    selected not tips
    action [SetVariable("tips",False), gui.SetPreference("tips",False)]
    alt "Hide tips in-game"
And...

Python:
define tips = gui.preference("tips", True)
to define and "remember" it, everything seems to work, except if: a game is saved with Hide, and the game saved with Hide active is loaded while Show is active on preferences (or vice-versa). The option will be set as tips True (show), but the var will still be False.

Ahh, I'll try again later. Probably it's something so easy and I'm not seeing...

I'm like 70% sure your problem is solved by using default, and not define. Setting define to true means every time the game is loaded, it'll be set to the defined value. default allows it to be changed, and have those changes saved. It looks to me the way you're doing it now, it's setting the defined value (gui.pref) to True on every load, but its leaving the variable (tips) at false - which I assume is defaulted and not defined.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
to define and "remember" it, everything seems to work, except if: a game is saved with Hide, and the game saved with Hide active is loaded while Show is active on preferences (or vice-versa). The option will be set as tips True (show), but the var will still be False.
What I don't understand is why you need to have two variables to save this, and why you use the gui preference system, that is designed to host the style default configuration/user preference.

You just need to use a persistent data, like said by crabsinthekitchen and nothing more.
 
  • Like
Reactions: crabsinthekitchen

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,581
2,219
You seem to be mixing a couple of separate solutions.

Firstly... your use of variables.

For "normal" operations, people tend to explicitly use two difference datastores for variables. store and persistent.

store variables are the ones you'd normally access during a game. If you code $ tips = False, that's actually store.tips. Likewise if you have define tips = False or default tips = False - those are both using store.tips too.
The store datastore is effectively dumped to the save files (sort of) when you save. It is used to hold variables that will change per playthrough. Just because a value is True now, doesn't mean it will be True when a player restarts a new game.

persistent variables exist outside of the current gameplay loop. They hold values that aren't tied to choices made by the player. An unlocked achievement for example will still be unlocked if the player starts a new game.

However, these aren't the only two datastores. There is also a preferences datastore, which is VERY like the persistent datastore. Values set now will persist, even if the player starts a new playthrough. Otherwise, you'd end up with annoying stuff like having to set fullscreen on/off when you started the game up... or set the music volume or any of that stuff.

Your mix of action SetVariable("tips") and action SetPreference("tips") means you are using two entirely separate variables to control the same thing (store.tips and preferences.tips). Using action SetVariable() doesn't make sense here at all, since it isn't something that you'd want to reset if the player started a new game.

It isn't clear to me that developers are supposed to add their own custom preferences. The reason I say that is because there is a finite . But the screen action doesn't use those exact same names. For example, preferences.afm_time controls how fast or slow the auto text advance feature works. But the screen action to control that is action Preference("auto-forward time"). Exactly how it makes a connection between "auto-forward time" and "afm_time isn't clear to me. Plus while testing, RenPy threw an exception whenever I tried to reference my custom preference variable that way. That said, whilst action Preference() doesn't work with unrecognized preferences... and work just fine. As does accessing the current value using gui.preference("myprefhere"). Given that action Preference() is supposed to be the preferred method and it doesn't support custom preferences, I'm leaning toward "it's likely not supposed to be done that way".

For that reason only, you might consider storing your custom preferences in persistent rather than preferences. Though I can see the temptation. Clearly there are mechanisms to make it work with preferences though, so maybe I'm just being overly cautious about some legacy code that doesn't fit how I think it should work.

Edit: The article linked below by crabsinthekitchen by the RenPy author himself implies I'm wrong though. I do note however he uses action Preference() for all the existing preferences and action SetField(preferences, ...) for his custom stuff. That article may or may not pre-date ). But who am I to say PyTom is inconsistent.



Secondly, how things like preferences show as toggled on/off.

A somewhat hidden function of what a screen action does is how it deals with a screen object property .selected. If when displaying something as part of a screen, RenPy decides that the screen action is already True, then it flags that text/image/button as selected (unless explicitly overridden). This has a knock-on effect to the text/image/button's use of style. And since styles for the majority of the pre-existing RenPy screens are all inherited, it's not always obvious where the actual display style came from.

For example, the screen quick_menu() includes a toggle for text auto-forward/auto-advance. It shows as "Auto" in most games.

It's simply textbutton _("Auto") action Preference("auto-forward", "toggle")

Yet, when playing a game, clicking this text button often changes the color of the word "Auto" to indicate whether it is On or Off.

It takes some backtracking of style quick_button_text: to link things back to two lines in the gui.rpy:

define gui.quick_button_text_idle_color = gui.idle_small_color
define gui.quick_button_text_selected_color = gui.accent_color

The selected_color and idle_color affect the colors, but there is no obvious connection between action Preference() and whether the .selected property is set True or False. And if it's mentioned in the documentation, I've not seen it. But... all screen actions set .selected to be True or False... it's just a bit of a leap of faith as to how.

The same mechanism controls whether radio buttons are shown with a mark (bar or tick or something) next to them.

My guess is that your buttons are locked to showing as True because you've coded multiple actions (due to using multiple variables). Once you simplify that, it should end up working as intended.
 
Last edited:

crabsinthekitchen

Well-Known Member
Apr 28, 2020
1,550
8,807
It isn't clear to me that developers are supposed to add their own custom preferences. The reason I say that is because there is a finite .
There's also on RenPy's website that I found after posting today's answer (I don't remember if I saw it when I was coding my own custom preferences), there they show how to add custom preferences to the persistent
 
  • Like
Reactions: 79flavors

xxx3me

Newbie
Jun 19, 2020
57
30
What I don't understand is why you need to have two variables to save this, and why you use the gui preference system, that is designed to host the style default configuration/user preference.
It was a misunderstanding from my part on how Ren'py works, as also pointed by 79flavors. I thought that I would NEED to set a preference + the variable to remember the preference later :p I'll let the code finished later here for other people needing it, but it's working as expected now. Thank you all!
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
Given that action Preference() is supposed to be the preferred method and it doesn't support custom preferences, I'm leaning toward "it's likely not supposed to be done that way".
Personally it's the fact that both SetPreference and TogglePreference are located in the "gui" store that make me exclude this approach. After all, you init the store by giving it the height and width of the game... Way too specific to be used for something else than the style.

Now, this being said, I never tested it, but one should be able to add Preference value through renpy.preferences.Preference ; it's how Ren'Py define its own preference variables after all. So here it would be something like:
Code:
init python:
    renpy.preferences.Preference( "hint", False )

[...]
screen options():

    label "Hint:"
    textbutton "show":
        action Preference( "hint", True )
    textbutton "hide":
        action Preference( "hint", False )