Ren'Py Can't make If, Persistent and String work [SOLVED]

JohnDupont

Active Member
Modder
May 26, 2017
838
2,810
Hello.

Here is the code I've written:
Code:
init -1:
    default gallery_customization_taboo = ["dad", "ashdad", "dough", "evab", "jack", "jasmin", "jes", "nat", "tyrone", "wilson"]
    default persistent.gallery_taboo_dad = False
    default persistent.gallery_taboo_ashdad = False
    default persistent.gallery_taboo_dough = False
    default persistent.gallery_taboo_evab = False
    default persistent.gallery_taboo_jack = False
    default persistent.gallery_taboo_jasmin = False
    default persistent.gallery_taboo_jes = False
    default persistent.gallery_taboo_nat = False
    default persistent.gallery_taboo_tyrone = False
    default persistent.gallery_taboo_wilson = False
  
screen gallery_customization:
    frame anchor (0.5,1.0) align (0.50,0.78) padding (5,5) xysize (1200,520):
        background Solid("#99d6ff")
        vpgrid cols 7 spacing 5:
            for t in range(0,len(gallery_customization_taboo)):
                frame:                 
                    background Frame("JDMOD/system/background_customization.png", left=0, top=0)             
                    button padding (0,0) xysize (143,240):
                        hover_background Frame("JDMOD/system/character/hovered_{}.png".format(gallery_customization_taboo[t]), left=0, top=0)
                        if "persistent.gallery_taboo_{}".format(gallery_customization_taboo[t]):
                            background Frame("JDMOD/system/character/hovered_{}.png".format(gallery_customization_taboo[t]), left=0, top=0)
                        add Frame("images/{}.png".format(gallery_customization_taboo[t]), left=0, top=0)
                        clicked ToggleField(persistent, "gallery_taboo_{}".format(gallery_customization_taboo[t]), true_value=None, false_value=None)
The "if "persistent.gallery_taboo_{}".format(gallery_customization_taboo[t]):" doesn't work as intended.
It is always on, even when I change the value of the persistent data. And if I add "== True" or "== False" to it, it's always off.
 

Saki_Sliz

Well-Known Member
May 3, 2018
1,403
1,011
What is this, python? Guessing from the use of indexing to group code block it is, but some of the syntaxes is still beyond me. From what I can guess, it looks like you are trying to get a string, "Persistent.gallery_taboo_{}" and check if this string is equal to another string (just guessing) using a public boolean String.format(String) function, at least that is my guess from the way you are using it. This function is similar to a String class public boolean equal(STRING) class used to do a similar check in many other languages. It looks like you throw 't' into the "gallery_customization_taboo" array to run through the list of names (dad, ashdad, etc) to see which one matches.

However, I don't thing this is exactly how the format(string) function is supposed to be used (I am just guessing since I don't know what language or api you are using). I say that because you have this other function "Frame" where it looks like you throw in a string "JDMOD/system/background_customization.png" which looks like a file location string, you have another usage of Frame which I am going to assume needs a string like before, and if so you pass in the argument "JDMOD/system/character/hovered_{}.png".format(gallery_customization_taboo[t]) so if this function is working that means the string.format(string) works which means format() returns a string, probably the same string reformated to a style because that would be what I would guess the format function would do. So this means the function returns a string. Anything not = to zero, false, or null the computer system will assume means true (1 or any number > 0, and text are numbers >0 ) so if it is returning a string, words, then the if statement will always read true, hence the issue.

So there are two things I am guessing you want, are you trying to see if one text equals another text (found in the array) or are you trying to see if the text is formatted like what is listed in the array.

If you want the text to match, try and "".equal() or "".equals() function instead of a "".format() function instead.

if however, you are checking one string to see if it is formatted in a way that matches the list, this is much more complicated. To check formatting, you would have to write code that does one of two things.

If you know you are going to get a string, you are going to convert it to a format, and then make a choice (aka the if statement) based on the format used, do not check to see which formatting the string matches, just have the code remember which format was used (ie the t that was used), then do the extra bit of code.

However, looking at the code coming after the if statement, where you have another Frame("",,) where the string is formatted using the matching gallery_customization_taboo[t] I am guessing you are doing something different. You are getting a string, checking the format by scanning through the array, and reformating the images if the format matches. In this case, two things need to happen, either you have to build a system that can scan through the string to determine the format type (not for beginners), or (and what I recommend, possibly after talking with someone) rewrite all of the code, using a different mechanic to figure out when to reformat the image. I could talk more, but I would need to know a lot more context such as languag, where are you getting inputs from, what data are you expecting to have, what are you trying to do with your code, etc.
 
  • Like
Reactions: JohnDupont

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,566
7,382
@JohnDupont :

I haven't analyzed your code in detail, but a few points:

  • "gallery_customization_taboo" should probably be set up using "define" instead of "default." I presume that this array is not going to change over the course of the game, and doesn't need to be included in the save. It can just be "part of the code."
  • It is almost never necessary to initialize "persistent" variables to "False". Unlike "regular" variables, if you reference a persistent variable that hasn't been defined, you don't get an exception. Instead, it returns None. And None is a "falsy" value. So you can just do things like
Code:
if not persistent.some_variable:
    to be executed if persistent.some_variable hasn't been set, or has been explicitly set to False, None or an empty string
  • if "persistent.gallery_taboo_{}".format(gallery_customization_taboo[t]) is certainly wrong. The part after the "if" results in the creation of a string that contains the formatted name, but it's just a string. This doesn't retrieve the value from persistent. It's just a string containing "persistent.gallery_taboo_something". Nothing more. Non-empty strings are "truthy", so this expression is always going to evaluate as True. What I think you're trying to do is dynamically choose which of the entries to use and then look at the persistent value associated with it, but that's not what this expression does. What you probably want is something like
Code:
if persistent["gallery_taboo_{}".format(gallery_customization_taboo[t])]:
    ....
Note here that we compute the name of the persistent attribute as a string, but then we retrieve it from the persistent object using the [] nomenclature. (persistent["abc"] is the same as persistent.abc)
  • clicked ToggleField(persistent, "gallery_taboo_{}".format(gallery_customization_taboo[t]), true_value=None, false_value=None) isn't going to do anything, because you have both the true_value and false_value set to None. As a result, when you click, the persistent field is going to be set to None, regardless of its current value. And None, of course, is falsy.
Hopefully that gets you on track...
 
  • Like
Reactions: JohnDupont

JohnDupont

Active Member
Modder
May 26, 2017
838
2,810
Thanks @Rich
"gallery_customization_taboo" should probably be set up using "define" instead of "default." I presume that this array is not going to change over the course of the game, and doesn't need to be included in the save. It can just be "part of the code."
You're right, even though it should never be included in a saved, 'define' is always better here. Since all the other variables are persistent, 'default' became a habit.

It is almost never necessary to initialize "persistent" variables to "False". Unlike "regular" variables, if you reference a persistent variable that hasn't been defined, you don't get an exception. Instead, it returns None. And None is a "falsy" value.
By "falsy", do you mean a ToggleField would consider it as "False"? That would be good to know.
if "persistent.gallery_taboo_{}".format(gallery_customization_taboo[t])
is certainly wrong. The part after the "if" results in the creation of a string that contains the formatted name, but it's just a string. This doesn't retrieve the value from persistent. It's just a string containing "persistent.gallery_taboo_something". Nothing more. Non-empty strings are "truthy", so this expression is always going to evaluate as True. What I think you're trying to do is dynamically choose which of the entries to use and then look at the persistent value associated with it, but that's not what this expression does. What you probably want is something like
Code:
if persistent["gallery_taboo_{}".format(gallery_customization_taboo[t])]:
    ....
Note here that we compute the name of the persistent attribute as a string, but then we retrieve it from the persistent object using the [] nomenclature. (persistent["abc"] is the same as persistent.abc
You described exactly my problem, thanks. I tried your solution and I got this error: Capture.PNG

Persistent is an object and gallery_taboo_... is a field. I don't know if this information can be useful.
clicked ToggleField(persistent, "gallery_taboo_{}".format(gallery_customization_taboo[t]), true_value=None, false_value=None)
isn't going to do anything, because you have both the true_value and false_value set to None. As a result, when you click, the persistent field is going to be set to None, regardless of its current value. And None, of course, is falsy.
It actually works. Those two are only taken into account if they are not equal to None. Therefore, true_value=None, false_value=None is the same as true_value=True, false_value=False.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,566
7,382
By "falsy", do you mean a ToggleField would consider it as "False"? That would be good to know.
"Truthy" and "falsey" are colloquial terms that are sometimes used for how Python considers variable values if you do something like
Code:
if x:
    ....
or
Code:
if not x:
    ....
If "x" is a boolean, then the meaning is obvious. But if "x" is not a boolean, there are rules for how to treat the variable. Essentially, variables whose value are "None", "False," zero or an empty string are all treated as if the variable was a boolean False (hence "falsey") and everything else is treated as if it were a boolean True (hence "Truthy"). That's why your expression didn't work - since the result of the operation is a non-empty string, and since a non-empty string is "truthy," Python evaluates
Code:
if that_string_you_constructed:
as if it read
Code:
if True:
You described exactly my problem, thanks. I tried your solution and I got this error:

Persistent is an object and gallery_taboo_... is a field. I don't know if this information can be useful.
Crap. I thought "persistent" worked like a dictionary, but that error message indicates that it doesn't. This should work:
Instead of
Code:
if persistent["the string you constructed"]:
use
Code:
if getattr(persistent, "the string you constructed"):
"getattr" is a function that's part of Python, and basically says, "get the attribute whose name is in this string from this object."

It actually works. Those two are only taken into account if they are not equal to None. Therefore, true_value=None, false_value=None is the same as true_value=True, false_value=False.
Headslap.

Of course, since they default to None, there's no reason to specify them. So what you wrote is equivalent to just
Code:
ToggleField(persistent, "gallery_taboo_{}".format(gallery_customization_taboo[t]))
which would have been much clearer for mere mortals like me. XD But you're correct - RTFM...
 
  • Like
Reactions: JohnDupont

JohnDupont

Active Member
Modder
May 26, 2017
838
2,810
use
Code:
if getattr(persistent, "the string you constructed"):
"getattr" is a function that's part of Python, and basically says, "get the attribute whose name is in this string from this object."
It works. You're the best.

Of course, since they default to None, there's no reason to specify them. So what you wrote is equivalent to just
Code:
ToggleField(persistent, "gallery_taboo_{}".format(gallery_customization_taboo[t]))
which would have been much clearer for mere mortals like me. XD But you're correct - RTFM...
I actually didn't know, sorry.

Thanks a lot. I came here with a single question and ended up learning a ton.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,566
7,382
It works. You're the best.
You're welcome - that's why we're here. And I learned something about the "persistent" object that I didn't know.

I actually didn't know, sorry.
No worries, and absolutely, positively no need to apologize - "this is how we learn."

To explain further, "just because I like explaining:" Python allows you to have required and optional arguments. When you see just an argument name, then it's required - you have to provide a value. When you see "argName=value", this means that that argument is optional, and that if you don't provide it, it will default to the value given. Required arguments are basically always positional - you have to give them in the order specified, while optional arguments are "named", and can appear in any order, but have to appear after all the required arguments.

So, to use ToggleField as an example, "object" and "field" are required, positional arguments, while "true_value" and "false_value" are optional "named" arguments. Thus, the following lines all have the same effect:
Code:
ToggleField(object, field)
ToggleField(object, field, true_value = None)
ToggleField(object, field, true_value = None, false_value = None)
ToggleField(object, field, false_value = None, true_value = None)
In the first, we take both defaults. In the second, we specify one (although using the same as the default), in the third both are specified, and in the fourth, we inverted the order of the named arguments - being named, they can come in any order you like.

By convention, Python-ers don't include optional arguments when they write calls unless they want a value different from the default - it just clutters up things. Not that it's "wrong," since you get the exact same effect, it's just a matter of convention. That's part of the reason I hiccupped - since you were including an explicit "None" value, I got a mental image that the default for "true_value" was probably "True", and that you were overriding that with "None." Had I been correct on the default value (which I wasn't), then in all likelihood that line of code would have been bugged. But that's what I get for just looking at what you wrote, assuming it was following Python conventions and not RTFM. "Assumption is the mother of all f-ups..."

But you can probably see how that would be an easy conclusion to jump to - which is probably one of the reasons why Python-ers don't repeat default arguments (aside from saving typing). If you provide a named argument, the assumption, because of the convention, is that you're passing in a value other than the default. (No excuses on my part, however.)

But glad you got it working. (And, btw, computing the name of a property the way you're doing and then using "getattr" - or the dictionary approach when it works - is a very Python-like thing to be doing. It's something you can't do - easily - in some other languages. So, a tip of the cap in your direction.)
 
  • Like
Reactions: JohnDupont