Working on updating from Ren’Py version 6.99 to 7.22, which has loads of optimizations. First feature to fix: menus appearing in NVL mode (the large textbox that covers half the screen)! Thumbnail art by Discord member Dally.


NVL Menu

Screenshot in-game with Markus on right and text on left showing an NVL-mode menu

NVL menu example

Prior to 7.22, we had to override a built-in function to get the NVL menu to display properly. Without proper display, it would do something silly like showing the ADV-styled menu even during NVL mode.

Choices are hidden behind the NVL window

NVL menu disappeared and the choices are floating free-form

Those are just two such examples of bad menu behavior.

Overridden function below, adapted from a code snippet found somewhere on the Internet.

    # def menu(items, interact=True, screen="choice"):
    #     if use_nvl_menu:
    #         #overwriting the NVL_MENU FUNCTION BUT WITHIN THE NORMAL ONE
    #         renpy.mode('nvl_menu')

    #         if nvl_list is None:
    #             store.nvl_list = []

    #         screen = None

    #         if renpy.has_screen("nvl_choice"):
    #             screen = "nvl_choice"
    #         elif renpy.has_screen("nvl"):
    #             screen = "nvl"

    #         if screen is not None:

    #             widget_properties, dialogue = nvl_screen_dialogue()
    #             for i in dialogue:
    #                 print(i)

    #             rv = renpy.display_menu(
    #                 items,
    #                 widget_properties=widget_properties,
    #                 screen=screen,
    #                 scope={"dialogue": dialogue},
    #                 window_style=style.nvl_menu_window,
    #                 choice_style=style.nvl_menu_choice,
    #                 choice_chosen_style=style.nvl_menu_choice_chosen,
    #                 choice_button_style=style.nvl_menu_choice_button,
    #                 choice_chosen_button_style=style.nvl_menu_choice_chosen_button,
    #                 type="nvl",
    #             )

    #             for label, val in items:
    #                 if rv == val:
    #                     choice = label
    #             choice = "{u}" + str(choice) + "{/u}"
    #             mc.add_history(kind="adv", who="", what=choice)
    #             return rv

    #         # Traditional version.
    #         ui.layer("transient")
    #         ui.clear()
    #         ui.close()

    #         ui.window(style=__s(style.nvl_window))
    #         ui.vbox(style=__s(style.nvl_vbox))

    #         for i in nvl_list:
    #             if not i:
    #                 continue

    #             who, what, kw = i
    #             rv = renpy.show_display_say(who, what, **kw)

    #         renpy.display_menu(items, interact=False,
    #                            window_style=__s(style.nvl_menu_window),
    #                            choice_style=__s(style.nvl_menu_choice),
    #                            choice_chosen_style=__s(style.nvl_menu_choice_chosen),
    #                            choice_button_style=__s(style.nvl_menu_choice_button),
    #                            choice_chosen_button_style=__s(style.nvl_menu_choice_chosen_button),
    #                            )

    #         ui.close()

    #         roll_forward = renpy.roll_forward_info()

    #         rv = ui.interact(roll_forward=roll_forward)
    #         renpy.checkpoint(rv)

    #         for label, val in items:
    #             if rv == val:
    #                 choice = label
    #         choice = "{u}" + str(choice) + "{/u}"
    #         mc.add_history(kind="adv", who="", what=choice)
    #         return rv
    #     else:
    #         newitems = []
    #         for label, val in items:
    #             if val == None:
    #                 narrator(label, interact=False)
    #             else:
    #                 newitems.append((label, val))

    #         rv = renpy.display_menu(newitems, interact=interact, screen=screen)

    #         # # logging menu choice label.
    #         for label, val in items:
    #             if rv == val:
    #                 choice = label
    #         choice = "{u}" + str(choice) + "{/u}"
    #         mc.add_history(kind="adv", who="", what=choice)
    #         return rv

    # def nvl_screen_dialogue():
    #     """
    #      Returns widget_properties and dialogue for the current NVL
    #      mode screen.
    #      """

    #     widget_properties = {}
    #     dialogue = []

    #     for i, entry in enumerate(nvl_list):
    #         if not entry:
    #             continue

    #         who, what, kwargs = entry

    #         if i == len(nvl_list) - 1:
    #             who_id = "who"
    #             what_id = "what"
    #             window_id = "window"

    #         else:
    #             who_id = "who%d" % i
    #             what_id = "what%d" % i
    #             window_id = "window%d" % i

    #         widget_properties[who_id] = kwargs["who_args"]
    #         widget_properties[what_id] = kwargs["what_args"]
    #         widget_properties[window_id] = kwargs["window_args"]

    #         dialogue.append((who, what, who_id, what_id, window_id))

    #     return widget_properties, dialogue

Whenever we wanted to use an NLV-styled menu, we would set the flag use_nvl_menu=True and then have to remember to set it back to False after the player has made a choice (this step could be automated…).

Now, 7.22 has native-support for NVL-menus within the default menu function. That means that our previous overriden menu function acts unexpectedly (everything breaks again). To use the new native NVL-menu, we do:

    nn "A few moments later, he leaned towards me, this time sniffing close to my neck."
    $_window_hide(trans=None) #forces the nvl menu to show up without blinking; window hide would make it blink b/c of using default window hide trans
    menu(nvl=True):
        "What are you—":
            window show #makes it so that the  window doesn't blink since it is technically not showing yet
            nvl clear
        "Get the hell away from—":
            window show
            nvl clear
            $ renpy.pause(0.4, hard=False)
            mn "Heh, heh. A little on edge, aren't we?"
            nvl clear
    $ renpy.pause(0.4, hard=False)
    show markus 1_nvl with Dissolve (0.2)

There are a couple issues in the above version, namely with all the window blinking. You may recall this Blinking effect in a previous version of the game (or maybe in one of our other games–it’s a bit difficult to remember exactly where it appeared previously). The window blinks because it’s technically in a hidden state, and then a default transition says to Hide the window, which makes it appear before applying the hiding transition.

Default transitions:

config.window_show_transition = Dissolve(.2)
config.window_hide_transition = Dissolve(.2)

$_window_hide(trans=None) overrides the default transition to make it so that nothing happens (which keeps the current NVL screen up in its current/earlier state).

If we were only using NVL mode in the entire game, then we could switch the transition code to something like:

config.window_show_transition = None
config.window_hide_transition = None

But we switch back and forth between the two dialogue layouts, so we made a small function for changing the transition type based on which sort of layout we’re in. It’s a bit similar to having to set the use_nvl_menu flag before.

init python:
    def n_mode(mode):
        if mode == "nvl":
            config.window_show_transition = None
            config.window_hide_transition = None
            config.empty_window = nvl_show_core
        else:
            config.window_show_transition = Dissolve(.2)
            config.window_hide_transition = Dissolve(.2)
            config.empty_window = _default_empty_window

Now, whenever we switch between ADV and NVL modes, we just have to remember to add the line $n_mode("nvl") or $n_mode("adv").

You may notice that after both choices, we still have to manually add the line window show to prevent yet another blinking problem. One way around that is to define a “callback” function that is run as soon as a player makes a choice. That’s still on the TO-DO list though, so we won’t be talking about how to do that this time around!

Game dev poem

Where have all the artists gone?
Why won’t the code cooperate;
Where did I leave off in that script?
This GUI is not first rate…

Oh, woooes of a dev,
Oh, oh, oh, wooooes of a dev…
Assets are overdue, my VA missed their cue,
Ohhh the woooooes of a dev!

Late last night, a bug arose;
When it was fixed, our new build crashed!
I couldn’t find a proper font;
My new sprite files all got trashed…

This bug request’s not for our game;
Why won’t this mobile build install?
The OP makes my laptop crash;
These background files are all too small…

Oh, woooes of a dev,
Oh, oh, oh, woooes of a dev…
Last update broke our code, these files won’t upload,
Ohhh the woooooes of a dev!

Oh, woooes of a dev,
Oh, oh, oh, woooes of a dev…
Someday it all will end, and then we’ll start again,
Ohhh the woooooes of a dev!
– glasses garden

Inspired by: