Ren'Py How to mod games where the dev failed to hide some images?

Meushi

Well-Known Member
Aug 4, 2017
1,146
12,751
Hi,

I'm trying to fix some bugs in a game which mostly uses show/hide statements to display it's images.

In some cases the dev forgot to hide an image, so they remain in the stack and show at odd moments.
You don't have permission to view the spoiler content. Log in or register now.
I could use label_overrides and correct it in a separate copy of the scene. But that seems a bit inefficient if you're copying out hundreds of lines to just fix a couple of missing hide statements.

So I cobbled together a python function leveraging to issue a hide statement for the errant images:
You don't have permission to view the spoiler content. Log in or register now.
This works fine when I manually call it from the console, but I've no clue how to have it constantly running. I assume it'd need to be executed from a callback, but couldn't work out any applicable from the online renpy docs. Though that could be because I have minimal grasp of how renpy callbacks work.

Is there a way to make this work, or indeed a better approach to the problem?
 

barotok

New Member
Apr 30, 2019
13
10
Probably by making a empty screen that just has that function running then just show that screen at the beginning
 
  • Like
Reactions: Meushi

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,978
16,236
This works fine when I manually call it from the console, but I've no clue how to have it constantly running.
Option one, use . It will call the function more or less every 0.05 seconds, what is a bit overkill.
Python:
init python:

    img_dict_mi = {
        [...]

    def hide_img_mi():
        [...]

    config.periodic_callback.append( hide_img_mi )
Option two, use to hook yourself to the show and scene statements.
Python:
init python:

   img_dict_mi = {
        "image_1" : "background", # hide background if image_1 displayed
        "image_2" : "image_1",    # hide image_1 if image_2 displayed
        }

    def myShow( *args, **kwargs ):
       # You don't need to copy the dict, especially since in fact you haven't copied it
       for name in store.img_dict_mi: 
            if renpy.showing(i) == True and renpy.showing(img_dict_mi[i]) == True:
                renpy.hide(img_dict_mi[i])
       renpy.show( *args, **kwargs )

    config.show = myShow
 
  • Like
Reactions: Meushi

Meushi

Well-Known Member
Aug 4, 2017
1,146
12,751
Thanks, that's very helpful.

I got option one: use to work, after changing
config.periodic_callback.append( hide_img_mi ) to renpy.config.periodic_callbacks.append( hide_img_mi )

Which is great, but option two: use sounds better, since I assume that's only calling the function when a show statement occurs, rather than every 0.05 seconds.

Unfortunately I haven't been able to get option two working, so have a few questions.
Option two, use to hook yourself to the show and scene statements.
Python:
init python:

   img_dict_mi = {
        "image_1" : "background", # hide background if image_1 displayed
        "image_2" : "image_1",    # hide image_1 if image_2 displayed
        }

    def myShow( *args, **kwargs ):
       for name in store.img_dict_mi:  # (1/2)
            if renpy.showing(i) == True and renpy.showing(img_dict_mi[i]) == True:
                renpy.hide(img_dict_mi[i])
       renpy.show( *args, **kwargs )  # (3)

    config.show = myShow  # (4)
As evidenced by referencing a variable instead of copying it, these may be Python for Dummies level questions:
1. Why name, rather than i, which is the var used in the renpy.showing & renpy.hide statements?
2. Why store.img_dict_mi rather than directly referring to img_dict_mi?
3. I have a limited understanding of how *args, **kwargs work, but it doesn't look like these args are modified after being pulled into the myShow function? So won't the renpy.show statement display all the images present when myShow was initially called, including any which were hidden by renpy.hide?
4. This is inside a python block, should it be renpy.config.show = myShow?

With some of those changes it doesn't error out, but doesn't hide the orphaned images either.
Python:
init python:

    # List of orphaned images to test : hide
    img_dict_mi = {
        "image_1" : "background", # hide background if image_1 displayed
        "image_2" : "image_1",    # hide image_1 if image_2 displayed
        }

    # Function to hide orphaned images per AON
    def myShow( *args, **kwargs ):
        #for name in store.img_dict_mi: 
        for i in store.img_dict_mi: 
            if renpy.showing(i) == True and renpy.showing(img_dict_mi[i]) == True:
                renpy.hide(img_dict_mi[i])
        renpy.show( *args, **kwargs )

    #config.show = myShow
    renpy.config.show = myShow
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,978
16,236
[...] after changing config.periodic_callback.append( hide_img_mi ) to renpy.config.periodic_callbacks.append( hide_img_mi )
Oops. This one is on PyTom, what Idea to have two configuration value that do the same things, one as direct callback, the other as list of callback. I'm not even sure that the first one is really still used nowadays.


Which is great, but option two: use sounds better, since I assume that's only calling the function when a show statement occurs, rather than every 0.05 seconds.
It is, yes. What ensure that there's effectively an image that is shown in place of the bogus one.
I presented the first option thinking about the question, "how can I make the call automatic", more than thinking about the context itself ; what was a really bad idea.


1. Why name, rather than i, which is the var used in the renpy.showing & renpy.hide statements?
Because I'm an idiot ?
I was thinking about the function declaration (show( name, .... )). So, I assume that my brain was thinking something like if name in list_of_bogus_screen:, while writing the loop.
Note that the "if" approach could also be interesting, but I'll come back to it after.


2. Why store.img_dict_mi rather than directly referring to img_dict_mi?
Because the branch 7.4.x made me become even more paranoid than I already was. Many times now addressing a variable inside a screen do not works without the store. prefix, so it became more or less an habit to put it everywhere.
But since you aren't assigning a value to it, Python would effectively fallback to the global, and so find the dict.


3. I have a limited understanding of how *args, **kwargs work, but it doesn't look like these args are modified after being pulled into the myShow function? So won't the renpy.show statement display all the images present when myShow was initially called, including any which were hidden by renpy.hide?
Effectively, nothing is changed. The goal of *args, **kwargs here is to catch, then pass, all the parameters without having to care about them, because they are insignificant.
You don't care what image Ren'Py will show, just that the bogus image aren't actually shown. Like the real "show" is called after, this will works only if the image is replaced by another one.
Let's say that "bogus" is what you want to remove :
Python:
label whatever:
    show whatever
    #  'bogus' is not displayed, nothing to hide, 'whatever' is displayed.
    [...]
    hide whatever
    show 'bogus'
    # 'bogus' is not displayed YET, nothing to hide, 'bogus' is displayed.
    [...]
    show something
    # 'bogus' is displayed, 'bogus' is hidden, 'something' is displayed.
    [...]
    hide something
    show anotherthing
    # 'bogus' is not displayed, nothing to hide, 'anotherthing' is displayed.
4. This is inside a python block, should it be renpy.config.show = myShow?
No.
It's technical here.
config. refer to the "config" store-like, where the configuration values are available, while renpy.config refer to the module named "config", where the configuration values are created and that handle the "config" store-like.
Practically, the main difference is that the store-like have some securities. You can't change some values once the game is started, you can't add new variables, and you can't delete a variable ; there's perhaps some other that I forgot.
You should always address the store-like, so use config.whatever, this will ensure that you don't make some mistakes. renpy.config is to use only for dirty moves, when you know what you are doing.


With some of those changes it doesn't error out, but doesn't hide the orphaned images either.
This one is on you. I copied the code you gave, and it don't works:
if renpy.showing(i) == True and renpy.showing(img_dict_mi[i]) == True:. It should be or, not and.

This works perfectly, I tested it:
Code:
    def myShow( *args, **kwargs ):
        for i in store.img_dict_mi:
            if renpy.showing(i) == True or renpy.showing(img_dict_mi[i]) == True:
                renpy.hide(img_dict_mi[i])

    config.show = myShow

Now, all this being said, back to the "name" parameter...

The whole problem with this code, is that by default shown images are expected to be sprites. What mean that you can have:
Code:
label whatever:
    show girl1_standing
    [...]
    show girl2_idle
    [...]
    hide girl1_standing
    show girl1_afraid
    [...]
    hide girl1_afraid
    [...]
    show girl3_idle
    [...]
    hide girl3_idle
    #hide girl2_idle
    jump somewhere

label somewhere:
    show background
    [...]
The error is that "girl2_idle" is not removed at the end of the scene. But the fix would remove it when "girl1_afraid" is shown, what is way too early.

To correct this, you can rely on the name of the first image shown after the image should have been hidden ; here it's "background".

Python:
init python:
    # Key is the image used as marker ; when this image is shown, there's possibly one 
    # that should have been hidden but wasn't.
    img_dict_mi = {
        #  The value is a tuple, to let you use one maker for more than one image.
        "background": ( "girl1_idle", ),
        }

    def myShow( name, *args, **kwargs ):
        # The image to display is one of our markers.
        if name in img_dict_mi:
            # For all the image possibly still shown.
            for i in img_dict_mi[name]:
                # If it's shown, hide it.
                if renpy.showing(i) == True: renpy.hide(i)

        # And now display the image as asked.
        renpy.show( name, *args, **kwargs )

    config.show = myShow
Side note:
renpy.showing works with image names, not with the image itself. Therefore your renpy.showing(img_dict_mi[i]) was useless. It would have worked only in case of: show "images/name.ext".
 

Meushi

Well-Known Member
Aug 4, 2017
1,146
12,751
Thanks again for your insights & patience with my rudimentary renpy/python questions.

I definitely don't know what I'm doing, so will heed the warning about renpy.config.

I take some comfort in the knowledge that even experienced coders mess things up sometimes.
This one is on you. I copied the code you gave, and it don't works:
if renpy.showing(i) == True and renpy.showing(img_dict_mi[i]) == True:. It should be or, not and.
No, that and logic worked in the code I initially posted in the OP, and my implementation of your first option using renpy.config.periodic_callbacks.append( hide_img_mi ). In both cases it correctly removed the bogus image only when the key image was displayed too. It evidently doesn't work in the config.show context though.
This works perfectly, I tested it:
Code:
    def myShow( *args, **kwargs ):
        for i in store.img_dict_mi:
            if renpy.showing(i) == True or renpy.showing(img_dict_mi[i]) == True:
                renpy.hide(img_dict_mi[i])

    config.show = myShow
This code did not work for me. As soon as it encountered any dict value (img_dict_mi[i]) it cleared the entire image stack, leaving just the characters talking on a blank screen (I have the dev. console enabled).

That's because this code lacks the renpy.show( *args, **kwargs ) statement from your original version? So when myShow executes it's telling the callback to display nothing? In any case including the renpy.show statement fixed that problem.

The other issue as you pointed out is changing the hide logic to key or value, means it's hiding the dict value (img_dict_mi[i]) any time it appears, independently of the key image.

That's why my original logic used key and value, to only hide the value if both images were displayed. But you're right, the and logic doesn't work in this context. With if renpy.showing(i) == True and renpy.showing(img_dict_mi[i]) == True: the function doesn't hide anything. I tried using print statements to see why it's failing:
renpy.showing(i) is always None, even when the relevant image is actually showing.
renpy.showing(img_dict_mi[i]) returns True only when the relevant image is being displayed.

Not having much luck working out why renpy.showing(i) is always None, I moved on to your next approach using a name arg.
Python:
init python:
    # Key is the image used as marker ; when this image is shown, there's possibly one
    # that should have been hidden but wasn't.
    img_dict_mi = {
        #  The value is a tuple, to let you use one maker for more than one image.
        "background": ( "girl1_idle", ),
        }

    def myShow( name, *args, **kwargs ):
        # The image to display is one of our markers.
        if name in img_dict_mi:
            # For all the image possibly still shown.
            for i in img_dict_mi[name]:
                # If it's shown, hide it.
                if renpy.showing(i) == True: renpy.hide(i)

        # And now display the image as asked.
        renpy.show( name, *args, **kwargs )

    config.show = myShow
When I used this code it didn't hide the errant images.

The initial problem seems to be that if name in img_dict_mi: is never True, because name is returning a tuple? Referencing the first item of the name tuple instead solved that problem:
Python:
        if name[0] in img_dict_mi:
            for i in img_dict_mi[name[0]]:
But then it runs into the same problem of renpy.showing(i) always being None, so has the same roadblock as the previous approach. See attached hide_img_config_name_debug.rpy.txt for the code I ran (with ugly print debugging), and hide_img_config_name_debug.jpg to see the results in game.

I still have no idea why renpy.showing(i) isn't working as expected for me in the config.show examples. Both renpy.showing(i) & renpy.showing(img_dict_mi[i]) work correctly in the code I initially posted in the OP, and my implementation of your first option using renpy.config.periodic_callbacks.append( hide_img_mi ).

This particular game is running Ren'Py 7.3.5, in case that's relevant.
Side note:
renpy.showing works with image names, not with the image itself. Therefore your renpy.showing(img_dict_mi[i]) was useless. It would have worked only in case of: show "images/name.ext".
Well that's confusing, in my testing renpy.showing(img_dict_mi[i]) worked in every case, renpy.showing(i) worked in my original code, but not in your config.show examples.

All of the images I'm listing in img_dict_mi are image names. renpy.showing(img_dict_mi[i]) is referencing the value for the i key in img_dict_mi, so should also be a image name? As I mentioned this syntax has worked in all examples, it's renpy.showing(i) that's causing me grief in the config.show examples.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,978
16,236
This code did not work for me. [...]
That's because this code lacks the renpy.show( *args, **kwargs ) statement from your original version?
It is. I left a blank line before the "renpy.show" and forgot to get it when I copy/pasted the working code.
I start to believe that I seriously need vacations :(


renpy.showing(i) is always None, even when the relevant image is actually showing.
renpy.showing(img_dict_mi[i]) returns True only when the relevant image is being displayed.
Is it a question of "tag Versus FullyQualifiedFileName" ? On one way it's "image_1" that is used, when on the other way it's "images/whatever/image_1.ext" ?


The initial problem seems to be that if name in img_dict_mi: is never True, because name is returning a tuple?
Oh, it's possible. I admit without shame (well, with a little bit of it) that I looked at the function declaration, but not at its code. Sometimes "name" is a string, some other a tuple ( name, layer ), sometimes another kind of tuple, and... I'll not say that I always get it wrong, but it really looks like PyTom and I have an inverted logic.
This said, if it's a tuple, then be careful. Since it's images, it's possibly a list of "tags" ; like in show girl idle happy. In this case, name[0] should be name = " ".join( name ). What would lead, for my example, to "girl idle happy", what is the effective name of the image, or for show girl to just "girl".
But I'm not really sure of this, being at works I can't really verify ; I only have a limited access to my home computer from here.


But then it runs into the same problem of renpy.showing(i) always being None, [...]
"va29i59": ( "va29i60", ),
According to the names, you are doing things backward.

The keys of the dict are the marker. Therefore the images that tell you, "hey, if this image is shown, there's possibly an image that haven't been correctly hidden".
Since it seem logical that "va29i60" is displayed after "va29i59", it should be "va29i60": ( "va29i59", ) ; "if Ren'Py is asked to show the image "va29i59", then the image "va29i60" is still visible and must be hidden".
 
  • Like
Reactions: Meushi

Meushi

Well-Known Member
Aug 4, 2017
1,146
12,751
Is it a question of "tag Versus FullyQualifiedFileName" ? On one way it's "image_1" that is used, when on the other way it's "images/whatever/image_1.ext" ?
This game explicitly defines all of it's images, then the image tags are referenced in the code:
Code:
image back = "back2.webp"
image va29i60 = "v-29-60_nvidia.png"
Since it's images, it's possibly a list of "tags" ; like in show girl idle happy. In this case, name[0] should be name = " ".join( name ). What would lead, for my example, to "girl idle happy", what is the effective name of the image, or for show girl to just "girl".
Using join works for me in this example. This game doesn't have any images with attributes, so I haven't verified if those work with this syntax yet either.
According to the names, you are doing things backward.
Yes, this was the main problem. This code worked as soon as I put the dict key : values in the right order. :WeSmart:

I don't know why I thought you'd reversed them, the explanation & code make it obvious that wasn't the case. I'll go with I was fixated on renpy.showing(i) being associated with the dict key, rather than just being a dumbass.

So now I've got solutions with option one using periodic_callbacks and option three using config.show and the name arg working.

Thanks a lot for the help.
 
Last edited: