Ren'Py [Request] Help Fix variable viewer

eike2000

Member
May 9, 2017
146
70
I am creating a mod for myself but my poor knowledge that made me stuck, somebody help me please.
This is an example : game "Mybestfriendsdaughter" and this stage are : episode 2 to 15 (episode 1 is intro), each episode has 1 to 100 variables (ex : e2_10, e7_8, e12_22 ), value 1 or 0
I writed myself a mod that made me easy tracing but i tried someways and failed. So help me fix these please.
hbox spacing 5 :
vbox :
if episode1 == 1 :
textbutton "Ep1" action [] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode2 == 1 :
textbutton "Ep2" action [SetVariable("Ep_check", 2)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode3 == 1 :
textbutton "Ep3" action [SetVariable("Ep_check", 3)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode4 == 1 :
textbutton "Ep4" action [SetVariable("Ep_check", 4)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode5 == 1 :
textbutton "Ep5" action [SetVariable("Ep_check", 5)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode6 == 1 :
textbutton "Ep6" action [SetVariable("Ep_check", 6)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode7 == 1 :
textbutton "Ep7" action [SetVariable("Ep_check", 7)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode8 == 1 :
textbutton "Ep8" action [SetVariable("Ep_check", 8)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
vbox :
if episode9 == 1 :
textbutton "Ep9" action [SetVariable("Ep_check", 9)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode10 == 1 :
textbutton "Ep10" action [SetVariable("Ep_check", 10)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode11 == 1 :
textbutton "Ep11" action [SetVariable("Ep_check", 11)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode12 == 1 :
textbutton "Ep12" action [SetVariable("Ep_check", 12)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode13 == 1 :
textbutton "Ep13" action [SetVariable("Ep_check", 13)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode14 == 1 :
textbutton "Ep14" action [SetVariable("Ep_check", 14)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode15 == 1 :
textbutton "Ep15" action [SetVariable("Ep_check", 15)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5

viewport :
xsize x_col/2
xpos 100 + (x_col+50) *1
ypos 100
ymaximum 900
scrollbars "vertical"
mousewheel True
vbox :
for u in range(1,100) :
textbutton "e[Ep_check]_ = " + check_stagesx(Ep_check,u) action []
style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)
init python:
def check_stagesx(Ep_check,u) :
vars_x = ("e" + Ep_check + "_" + u)
if vars_x > 0 :
return "1"
else :
return "0"
 

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
Not sure I understood your question but this might help, you can use dir() to find all variables starting with e+any number to get all chapter names.

Something like this:
Python:
varList = []
for vars in dir():
    if len(vars)>2 and vars.startswith("e") and vars[1].isdigit():
        varList.append(vars)
Then varList will have a list of all episode variable names and you can do whatever you wish with them.

Even check their values by using globals() if needed, here is an example to see the value of the first variable in the list:
Python:
globals()[varlist[0]]
 

eike2000

Member
May 9, 2017
146
70
Not sure I understood your question but this might help, you can use dir() to find all variables starting with e+any number to get all chapter names.

Something like this:
Python:
varList = []
for vars in dir():
    if len(vars)>2 and vars.startswith("e") and vars[1].isdigit():
        varList.append(vars)
Then varList will have a list of all episode variable names and you can do whatever you wish with them.

Even check their values by using globals() if needed, here is an example to see the value of the first variable in the list:
Python:
globals()[varlist[0]]
can you help me by writed down in my attacted file in #1 post? First i don't know how to using dir() command, i tried in console and it appear alot of things, second, Cause i not understand so i think this will display begin with "e" and next a digit in tail and variable is not 1 - 100 is display too (example 100_1 or e12_6_2 it will be pop, i am doing this cause i try for another game too) but it not what i want to return, it just limit at 1 to 100, not include any tail. i mean e12_6 return 1 or 0, but e12_6_1 will not appear
 

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
I don't have the game downloaded so I can't modify your file without testing. But if you read carefully it probably might help you a bit.
i don't know how to using dir() command, i tried in console and it appear alot of things
returns all visible variables names , so every e2_1, e2_2, e3_1, e3_2... and so on that exists can be found with it.
After running that code from my previous post basically varList should have the name of every episode that exists.

Then you can directly iterate over all episodes inside varList or filter then down to more specific ones to create the textbuttons you need.

If you also need to find what the episode value currently is use .
It returns you the variable value of any string you pass to it, so globals()["e2_1"] should return either true(1) of false(0) depending on what part of the game you are.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
Not sure I understood your question but this might help, you can use dir() to find all variables starting with e+any number to get all chapter names.
No, no, no and no.

There's no need for the hundred internal Ren'Py variables. Using renpy.python.store_dicts["store"].ever_been_changed dict is what is needed here.

Python:
varList = []
for vars in renpy.python.store_dicts["store"].ever_been_changed:
    if vars.startswith("e") and vars[1].isdigit():
        varList.append(vars)

Even check their values by using globals() if needed,
It would return exactly the same thing than dir().



Code:
hbox spacing 5 :
                       vbox :
  if episode1 == 1 :
textbutton "Ep1" action [] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
if episode2 == 1 :
    textbutton "Ep2" action [SetVariable("Ep_check", 2)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
[...]
if episode15 == 1 :
    textbutton "Ep15" action [SetVariable("Ep_check", 15)] style 'xmod_button_nb' text_style 'xmod_button_txt' text_size (x_text_size) xsize (x_col/2)-5
what an overly complicated thing.
Python:
For i in range( 1, 16 ):
    if getattr( store, "episode{}".format( i ) ) == 1:
        textbutton "Ep{}".format( i ):
            action SetVariable("Ep_check", i)
            style 'xmod_button_nb'
            #text_style 'xmod_button_txt'  # the name is "xmod_button_nb_text" and it will be automatically used.
            text_size (x_text_size)  # why not directly on the style ?
            xsize (x_col/2)-5  # why not directly on the style ?

Code:
init python:
   def check_stagesx(Ep_check,u) :
       vars_x = ("e" + Ep_check + "_" + u)
       if vars_x > 0 :
           return "1"
       else :
           return "0"
A string will never be greater than 0. What you want to do is:
Code:
init python:
   def check_stagesx(Ep_check,u) :
       return getattr( store, "e" + Ep_check + "_" + u ) > 0
What will return True if the variable value is greater than 0, and False else.
 

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
There's no need for the hundred internal Ren'Py variables. Using renpy.python.store_dicts["store"].ever_been_changed dict is what is needed here.
Renpy does a great job removing the cleanliness from the python language lol.

Talk about unintuitive call, but it basically does the same thing so who am I to complain.
It would return exactly the same thing than dir().
Not really because dir only returns names
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
Talk about unintuitive call, but it basically does the same thing so who am I to complain.
It's the list of values that will be saved (because their value changed) for one of the 15 stores used by Ren'Py. And it's a part of Ren'Py that regard Python...


It's not because you don't know, that it's not intuitive and in fact relatively compliant with Python spirit.
 

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
It's not because you don't know, that it's not intuitive
Well, but the point of something being intuitive is not needing to know about it...

dir is a console command that lists all visible folders and that's why python calls it that, not sure what you have against it.
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,490
7,035
Well, but the point of something being intuitive is not needing to know about it...

dir is a console command that lists all visible folders and that's why python calls it that, not sure what you have against it.
No, the Python dir() does NOT "list all visible folders". Quoting directly from where you linked:
Without arguments, return the list of names in the current local scope. With an argument, attempt to return a list of valid attributes for that object.
But when it returns names, it returns ALL the names - variable names, function names, class names and names of built-ins.

The point that anne O'nymous is making is that your approach wades through far more things than just "game variables." Her approach focuses tightly on the game variables, by understanding how and where Ren'py stores those game variables. The fact that those game variables appear to be in the local scope is actually a result of some crafty Python within Ren'py, because they aren't stored in the global scope.

This isn't a case of Ren'py "removing the cleanliness from the python language" - this is case of Ren'py being a complex engine with a necessarily complex way of storing game variables so that it can detect when they're updated (so it knows to include them in the save) and so that game variables that devs create don't (or are very unlikely to) conflict with Ren'py internals.

I'm not arguing that the approach that you took might not work, but it also might very well return stuff that isn't part of the game, but, instead, is part of the engine, function names, etc., which might, in turn, break things if it's assumed that "everything dir() returns is a variable name".
 
  • Like
Reactions: gojira667

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
No, the Python dir() does NOT "list all visible folders". Quoting directly from where you linked:
I was talking about the popular dir console command that influenced python's dir(), which is why it is intuitive.
I'm not arguing that the approach that you took might not work, but it also might very well return stuff that isn't part of the game, but, instead, is part of the engine, function names, etc.
Well but relying in the internal dict will ignore non-game variables and depending on how much coding was done outside the library you could miss stuff that way, so there could be cons of not using dir() also.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
dir is a console command that lists all visible folders and that's why python calls it that, not sure what you have against it.
And what make you think that the guys behind Python, who are all known Unix(-like) hackers, named one of the built-in function in reference of a DOS only command ? Perhaps is it the case, but it will need proof.


Well but relying in the internal dict will ignore non-game variables
And who care ? Both OP question and your answer are targeting the game variables created to know the state of the game story.
Anyway, since I'm currently working on the Python 3.x port of my variable viewer, I can tell you that by default there's around 2 000 "game variables", and more than 500 000 "non-game variables". None of the last ones being significant for the player and really few (less than 0.5%) of them being saved. If Ren'Py wasn't built the way it is, your approach (especially the one relying directly on global) would be killing OP.


and depending on how much coding was done outside the library you could miss stuff that way, so there could be cons of not using dir() also.
I haven't said that you "MUST NOT" use dir, but that you "SHOULD NOT" ; rfc 2119 compliance. And I explained why it's better in regard of the question. This is far to be a "cons" regarding the use of dir.
This being said, since the behavior of dir can be changed through __dir__, one shouldn't use it blindly. And dir can also be overwrote, what mean that technically you should recommend to use __builtins__["dir"], just in case someone have redefined it.


As for the missing stuff, did you even tried to understand my answer ?

Firstly, it's the list of the variables that will be saved. What mean that, whatever is returned by dir, but isn't in the ever_been_changed set, will not be saved. They can be significant values for the game, but they are values that will be reset every time you quit the game ; what mean that they don't reflect the state of the game, just the state of the engine.

Secondly, as I said, dir() and globals() would return exactly the same thing. This mean that, seen from the game, the current scope and the global scope are exactly the same. Therefore, something that you would add "outside the library" (what mean nothing for Ren'Py) would either be found in the ever_been_changed, if it's significant, or not available from the game scope.

The fact is that, by following this approach, Ren'Py is totally compliant with Python spirit. The default store (named "store") is the global scope in which the game will be proceeded. And you can access its content both with dir() and globals().
But if you know that what you'll search is part of the saved values, then there's a shortcut that will speed-up all the processing ; and that's all I was saying...


The fact that those game variables appear to be in the local scope is actually a result of some crafty Python within Ren'py, because they aren't stored in the global scope.
It's more that Ren'Py added its own scope in top of Python's one. This to separate what is related to the sole engine and what is related to the sole game.


[...] which might, in turn, break things if it's assumed that "everything dir() returns is a variable name".
In a way the assumption is right. But only because in Python everything, including modules, classes and even abstracts, is a variable.
 
  • Like
Reactions: gojira667 and Rich

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
And what make you think that the guys behind Python, who are all known Unix(-like) hackers, named one of the built-in function in reference of a DOS only command ? Perhaps is it the case, but it will need proof.
dir is also an alias on unix, and there is no ls() on python so...
I can tell you that by default there's around 2 000 "game variables", and more than 500 000 "non-game variables".
well now I'm sure we are talking about a dir() hater...
1645835731634.png
You are clearly overblowing something that is much simpler than you believe to be.
 
  • Angry
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
You are clearly overblowing something that is much simpler than you believe to be.
Read again, and try to understand this time:
dir() while show you what is directly available from the game scope, and the game scope is put on top of the core scope, that host the, near to, 300 modules that compose it.
 

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
dir() while show you what is directly available from the game scope, and the game scope is put on top of the core scope, that host the, near to, 300 modules that compose it.
Well nobody was talking about inaccessible modules... dir() already ignores those so you don't have to worry about them.
 
  • Angry
Reactions: anne O'nymous

Meushi

Well-Known Member
Aug 4, 2017
1,146
12,727
well now I'm sure we are talking about a dir() hater...
1645835731634.png

You are clearly overblowing something that is much simpler than you believe to be.
This number will vary from game to game & save to save. How does it compare to the alternative approach proposed?

For an end of current version save in Stay True Stay You:
len(dir()) returns 797
len(renpy.python.store_dicts["store"].ever_been_changed) returns 229

For an end of current version save in BaDIK:
len(dir()) returns 3602
len(renpy.python.store_dicts["store"].ever_been_changed) returns 2450

For the reasons AON & Rich explained, won't the ever_been_changed approach be more efficient, because it's limited to variables & returns fewer items?

In the current case will it make any difference which approach is used, given the list is being parsed to limit the results to a particular variable name template? I'd assume not. I'd imagine most coders would go with dir() because it's more widely known?

I'm a Ren'Py & Python numpty, but I'd have thought using the smaller, better constrained ever_been_changed list would be the better option still?
 

Meaning Less

Engaged Member
Sep 13, 2016
3,540
7,112
For the reasons AON & Rich explained, won't the ever_been_changed approach be more efficient, because it's limited to variables & returns fewer items?
Performancewise almost negligibe in this case because there is no significant difference in the list size.

Both approaches are unsafe because you are relying that the game developer did a good job naming variables, you never know if the dev decides to create a random "e7" (in this case) or something that actually has a random string not related to episodes, so if your goal was to be extra safe you would need to add more checks and filters before apprending them to the list of vars you want to find.
In the current case will it make any difference which approach is used, given the list is being parsed to limit the results to a particular variable name template? I'd assume not. I'd imagine most coders would go with dir() because it's more widely known?

I'm a Ren'Py & Python numpty, but I'd have thought using the smaller, better constrained ever_been_changed list would be the better option still?
Personal preference mostly, much easier to remember "dir()" if you ask anyone coming from python.
But I guess people coming from renpy like to use the renpy commands instead, not very charming but does the job "renpy.python.store_dicts["store"].ever_been_changed"

Is the renpy method slightly more constrained? yes
Is it worth using one over the other trading readability for a small difference in results? debateable.

Python is already far from a "fast" language, readibility and intuitiveness is it's defining factor and what made it popular.
Personally If I wanted actual performance I would go for c++ or related, the differences here are so minute which is why I was questioning the nitpickiness since the end result after filtering returns the same list
 
  • Like
Reactions: xj47

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,369
15,285
Performancewise almost negligibe in this case because there is no significant difference in the list size.
When it come to the second slowest script language, going for the smallest iteration is always a benefit, because it's where the bottleneck is. Especially when you're working with a language build on top of this said second slowest one ; what is exactly what happen with Ren'Py.

Of course, Ren'Py is still fast enough to perform efficiently even when facing complex situation. He can works at 60fps without problem, what is fast enough for an engine that is not supposed to works in real time. And the screen by themselves can works a 20Hz, what once again is fast enough for such engine.
Likewise, the difference between both approach would be relatively insignificant, something like 0.1 milliseconds in place of a full 1. But the code being embedded in a screen, it will be proceeded every single time the screen is refreshed. Therefore, depending what is displayed by the game itself, those 0.9 milliseconds can make a big difference.

Because yes, the code have to be proceeded every time the screen is refreshed. You talk about the dev not following a constant naming scheme, and/or not following a constant variable meaning. But doing so you forgot that you aren't facing a compiled language ; at any time a variable can appear from nowhere, or disappear.
Since the screen is a tracker-like, it's a "display and forget" one ; you put it in the overlay list, and let Ren'Py do the rest for you. This mean that a variable can appear between two refresh of the screen, what is very likely to happen with Ren'Py since too many devs don't really care to correctly declare them through default.