[Solved]Exception: Cannot start an interaction in the middle of an interaction

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
OPPenguin
Newbie
Posts: 6
Joined: Thu Mar 29, 2018 7:50 pm
Github: OstermanA
Contact:

[Solved]Exception: Cannot start an interaction in the middle of an interaction

#1 Post by OPPenguin » Sun Feb 28, 2021 7:13 am

First off, it's nearly 3 AM and so I apologize if at any time this makes less sense than it could. I want to get the question asked while I'm still knee-deep in it, so I don't forget anything. I will tweak or clarify when needed.

I have almost zero experience with Ren'Py itself, but have been coding professionally in various languages for a while. I'm moderately skilled with Python, though hardly an expert.

I am building a custom GUI on top of NVL mode as part of a project to port a game from RAGS to Ren'Py. The idea is that there will be graphical controls that allow the player to move around and interact with the game world. At the moment, I'm working on the compass rose. My current game script basically involves putting the control screens on frame, then pausing indefinitely while waiting for input. I'm pretty sure this is wrong, and the cause of my error, but I'm not sure what the correct way to go accomplish this is.

What I'm actually trying to accomplish is, when the game has no more text to display, it stops and waits for the player to tell it to do something, rather than just running off the end and exiting the game. My current solution obviously isn't going to work, and I suspect that it is inherently flawed in execution. What is a good way to accomplish this goal? Please note that player_gui is located on the left side of the screen, while room_gui is located on the right. I haven't found a way to put them in the same screen call, though I haven't really tried. Whatever solution is used will need to allow the player to interact with both screens, simultaneously. Bonus points if neither GUI screen will respond to player clicks while it still has text to display.

For fun, the entirety of traceback.txt is attached, but the relevant part seems to be:

Code: Select all

While running game code:
  File "game/script.rpy", line 27, in script
    pause
  File "renpy/common/000statements.rpy", line 445, in execute_pause
    renpy.pause()
  File "renpy/common/00action_other.rpy", line 537, in __call__
    rv = self.callable(*self.args, **self.kwargs)
  File "game/classes.rpy", line 47, in move
    self.location = self.location.exits.get(exit)
  File "game/classes.rpy", line 33, in location
    renpy.say(None, location.name)
  File "renpy/common/00nvl_mode.rpy", line 381, in do_display
    **display_args)
Exception: Cannot start an interaction in the middle of an interaction, without creating a new context.
The game script itself is incredibly barebones, the bulk of the logic is in the various classes and such elsewhere. This is script.rpy in its entirety:

Code: Select all

define claire = Character("Claire", kind=nvl)
define narrator = nvl_narrator
define menu = nvl_menu

init python:
    config.keymap['hide_windows'] = []
    
    player = Player()
    playerGui = PlayerGui()

label start:

    scene bg room
    show screen player_gui()
    show screen room_gui()
    
    $ player.location = prologueBedroom
    
    window show None
    
    jump roomLoop
    
label roomLoop:
    "roomLoop before"
    
    while True:
        pause
        
    "roomLoop after"
I'm not going to include room_gui as it's almost entirely placeholders, but player_gui is as follows:

Code: Select all

## Player screen ###############################################################
##
## This screen is used for player-related GUI elements
    
screen player_gui:
    frame:
        xsize 324
        yfill True
        
        padding (0, 0)
        
        background 'gui player_menu background'
        vbox:
            fixed:
                xsize 324
                ysize 405
                add "[player.avatar]" fit 'contain' align (0.5, 0.5)
                
            fixed:
                xsize 324
                ysize 331
                null
            fixed:
                xsize 324
                ysize 144
                null height 144
            frame:
                xsize 324
                ysize 200
                
                padding (0, 0)
                background 'gui player_menu compass base'
                
                if player.location.exits.get('n'):
                    imagebutton:
                        idle 'gui player_menu compass point'
                        pos (162, 40)
                        anchor (0.5, 0.5)
                        action Function(player.move, 'n')
                <snip for brevity, just the rest of the compass rose here>
Most of the interesting stuff is in classes.rpy:

Code: Select all

init -1 python:
    class Player(object):
        def __init__(self, avatar='avatar prologue claire dressed', location=None):
            self._avatar = avatar
            self._location = location
        
        @property
        def avatar(self):
            return self._avatar
        
        @avatar.setter
        def avatar(self, avatar):
            self._avatar = avatar

        @property
        def location(self):
            return self._location
        
        @location.setter
        def location(self, location):
            if not isinstance(location, Room):
                raise ValueError("Expected a room, got {}".format(type(location)))
            
            # Exit triggers for current location
            if location.exit:
                location.exit()
            
            if location.nextExit:
                location.nextExit()
            
            # Move to next location and announce it
            self._location = location
            renpy.say(None, location.name)
            
            # Enter triggers for new location
            if location.enter:
                location.enter()
            
            if location.nextEnter:
                location.nextEnter()
            
            if location.examine:
                location.examine()
        
        def move(self, exit):
            if self.location.exits.get(exit):
                self.location = self.location.exits.get(exit)
    
    class PlayerGui(object):
        def __init__(self, compass='gui hive compass', logo='gui hive logo'):
            self.compass = compass
            self.logo = logo
        
        @property
        def compass(self):
            return self._compass
        
        @compass.setter
        def compass(self, compass):
            self._compass = compass
        
        @property
        def logo(self):
            return self._logo
        
        @logo.setter
        def logo(self, logo):
            self._logo = logo
    
    class Room:
        _validExits = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'u', 'd', 'i', 'o']
        
        def __init__(self, name='', description='', image='', exits=dict()):
            self.name = name
            self.description = description
            self.image = image
            
            self.exits = exits
        
        @property
        def name(self):
            return self._name
        
        @name.setter
        def name(self, name):
            self._name = name
        
        @property
        def description(self):
            return self._description
        
        @description.setter
        def description(self, description):
            self._description = description
        
        @property
        def exits(self):
            return self._exits
        
        @exits.setter
        def exits(self, exits):
            if isinstance(exits, dict):
                for k in exits.keys():
                    if k not in self._validExits:
                        raise ValueError("Direction '{}' not valid".format(k))
                self._exits = exits
                
            elif isinstance(tuple, exits) and len(exits) == 2:
                if exits[0] not in self._validExits:
                    raise ValueError("Direction '{}' not valid".format(exits[0]))
                self._exits[exits[0]] = exits[1]
                
            else:
                raise ValueError("Expecting dict or tuple (direction, room)")
                
        
        @property
        def image(self):
            return self._image
        
        @image.setter
        def image(self, image):
            self._image = image;
            
        enter = None
        nextEnter = None
        exit = None
        nextExit = None
        search = None
        def examine(self):
            # Say the location description(s)
            if isinstance(self.description, list):
                for i in self.description:
                    renpy.say(None, i)
            else:
                renpy.say(None, self.description)
The rest of the interesting stuff is in rooms.rpy:

Code: Select all

init python:
    
    prologueBedroom = Room(
        name='Bedroom',
        description='Your bedroom. It took a long time to get it just how you like it!',
        image='room prologue bedroom'
    )
    
    prologueBathroom = Room(
        name='Bathroom',
        description=['Your bathroom. Pretty awesome really, with a great view.',
            'You\'re going to replace the bath with a shower cubicle soon. You really don\'t like bathing, showers are just faster, more economical and less icky.'
        ],
        image='room prologue bathroom'
    )
    
    prologueBedroom.exits = {'s': prologueBathroom}
    prologueBathroom.exits = {'n': prologueBedroom}
Attachments
classes.rpy
(4.13 KiB) Downloaded 1 time
gui.rpy
(15.83 KiB) Downloaded 1 time
rooms.rpy
(688 Bytes) Downloaded 1 time
screens.rpy
(45.42 KiB) Downloaded 1 time
script.rpy
(529 Bytes) Not downloaded yet
traceback.txt
(4.23 KiB) Not downloaded yet
Last edited by OPPenguin on Sun Feb 28, 2021 9:40 pm, edited 1 time in total.

User avatar
Alex
Lemma-Class Veteran
Posts: 2981
Joined: Fri Dec 11, 2009 5:25 pm
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#2 Post by Alex » Sun Feb 28, 2021 7:35 am

Not sure, but at first try to replace renpy.say with renpy.notify (https://www.renpy.org/doc/html/other.html#renpy.notify).

Also try

Code: Select all

$ ui.interact()
instead of

Code: Select all

    while True:
        pause
https://www.renpy.org/doc/html/screen_p ... i.interact

OPPenguin
Newbie
Posts: 6
Joined: Thu Mar 29, 2018 7:50 pm
Github: OstermanA
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#3 Post by OPPenguin » Sun Feb 28, 2021 7:46 am

Alex wrote:
Sun Feb 28, 2021 7:35 am
Not sure, but at first try to replace renpy.say with renpy.notify (https://www.renpy.org/doc/html/other.html#renpy.notify).

Also try

Code: Select all

$ ui.interact()
instead of

Code: Select all

    while True:
        pause
https://www.renpy.org/doc/html/screen_p ... i.interact
Replacing pause with ui.interact() doesn't seem to do anything at all. The game pauses on "roomLoop before" and after, but not on the interact.

Replacing renpy.say with renpy.notify prevents the crash, but of course doesn't put the text on the NVL window as I would like. Do you have any other suggestions that I should try?

As I said, I think my entire approach to this is flawed. If anyone has a better idea, I'd be happy to give it a go.

User avatar
m_from_space
Veteran
Posts: 302
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#4 Post by m_from_space » Sun Feb 28, 2021 9:28 am

I don't know if I understand you correctly.

But maybe something like this works:

Code: Select all

screen waitforinput():
    # include both screens into this one
    use player_gui
    use room_gui
    ...

label start:
    ...
    while (something):
        ...
        # this automatically stops control flow until "Return()" is called within the waitforinput screen
        call screen waitforinput 
        ...
    ...

OPPenguin
Newbie
Posts: 6
Joined: Thu Mar 29, 2018 7:50 pm
Github: OstermanA
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#5 Post by OPPenguin » Sun Feb 28, 2021 10:54 am

m_from_space wrote:
Sun Feb 28, 2021 9:28 am
I don't know if I understand you correctly.

But maybe something like this works:

Code: Select all

screen waitforinput():
    # include both screens into this one
    use player_gui
    use room_gui
    ...

label start:
    ...
    while (something):
        ...
        # this automatically stops control flow until "Return()" is called within the waitforinput screen
        call screen waitforinput 
        ...
    ...
    
No, that had no effect at all. Same problem. I may have found a solution, but it's now nearly 7 AM and I really, really need to sleep so I'll test it properly... later.

The changes I made were to use:

Code: Select all

label roomLoop:
    $ renpy.notify('Entered roomLoop') # To ensure my loop wasn't running out of control
    $ ui.interact()
    
    jump roomLoop
and

Code: Select all

action Function(renpy.call, label="movePlayer", exit='s')
and

Code: Select all

label movePlayer(exit=None):
    if exit and player.location.exits.get(exit):
        $ player.location = player.location.exits.get(exit)
    
    return
For some reason, action Call() throws an error, something about the label not being defined. No idea what's happening, there. Using action Function(renpy.call, label="movePlayer", exit='s') seems a bit hacky, but it also seems to work.

Now I just need to find a way to lock controls while the game is displaying dialogue. That's a problem for future OPPenguin, though.

User avatar
Alex
Lemma-Class Veteran
Posts: 2981
Joined: Fri Dec 11, 2009 5:25 pm
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#6 Post by Alex » Sun Feb 28, 2021 12:09 pm

OPPenguin wrote:
Sun Feb 28, 2021 10:54 am

Now I just need to find a way to lock controls while the game is displaying dialogue. That's a problem for future OPPenguin, though.
Yet another try...))
If your control screen is always shown onscreen, you can make buttons insensible by setting their property, like

Code: Select all

screen test_scr():
    button:
        sensitive can_click
        # all the button's stuff

label my_lbl:
    $ can_click = False
    "blah-blah-blah"
    $ can_click = True
    $ ui.interact()
https://www.renpy.org/doc/html/screens.html#button

Human Bolt Diary
Regular
Posts: 109
Joined: Fri Oct 11, 2013 12:46 am
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#7 Post by Human Bolt Diary » Sun Feb 28, 2021 12:56 pm

This approach will never work. Using a while loop inside ren'py to wait for user input is never a good idea. Here's what you do:

Code: Select all

label roomLoop:
    "roomLoop before"
    
    while True:
        pause
        
    "roomLoop after"
Get rid of this completely.


Call your gui screen instead of showing it. Then return from the interaction to continue your script.

Code: Select all

label start:

    scene bg room

    $ player.location = prologueBedroom
   
    "Before entering room"
   
    call room
    
    "After exiting room"
   
label room:
    show screen player_gui()
    call screen room_gui()
    return
https://www.renpy.org/doc/html/screens. ... all-screen

Code: Select all

    class Room:
        _validExits = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'u', 'd', 'i', 'o']
        
        def __init__(self, name='', description='', image='', exits=dict()):
            self.name = name
            self.description = description
            self.image = image
            
            self.exits = exits
https://www.youtube.com/watch?v=UEF961u6RTQ
https://docs.python-guide.org/writing/gotchas/

Code: Select all

        @property
        def compass(self):
            return self._compass
        
        @compass.setter
        def compass(self, compass):
            self._compass = compass
Don't prematurely add setter and getter methods to your code. There's no benefit.

OPPenguin
Newbie
Posts: 6
Joined: Thu Mar 29, 2018 7:50 pm
Github: OstermanA
Contact:

Re: Exception: Cannot start an interaction in the middle of an interaction

#8 Post by OPPenguin » Sun Feb 28, 2021 8:13 pm

Human Bolt Diary wrote:
Sun Feb 28, 2021 12:56 pm
This approach will never work. Using a while loop inside ren'py to wait for user input is never a good idea.
The problem is, this user interface takes up nearly half the screen, and having it constantly flash in and out of existence looks bad. I want it permanently visible, but not always interactable. I think I've got a handle on how to do that now, in my previous post.
I knew but had forgotten this. Thank you for the reminder. Fixing it is simple, but finding the bug later would have been an absolute pain. :)
Human Bolt Diary wrote:
Sun Feb 28, 2021 12:56 pm
Don't prematurely add setter and getter methods to your code. There's no benefit.
You aren't wrong. The code you highlighted is from a very early prototype of the interface and currently serves no function at all. It'll go away as part of cleanup when I get the thing working and optimize the code.

Here's what I'm doing that actually works. Using $ ui.interact() seems to correctly pause while waiting for input, without blocking other calls. Calling a label to do the actual move and returning seems to avoid the "interaction in the middle of another interaction" issue, which is good. Setting sensitive to False as the first part of the handler label, and true at the end means that the GUI won't respond to inputs while it's in the middle of something else, which is also good. On the whole, this appears to get the job done.

Code: Select all

label start:

    scene bg room
    show screen player_gui()
    show screen room_gui()
    
    $ player.location = prologueBedroom
    
    window show None
    
    $ guiSensitive = True
    
    jump roomLoop
    
label roomLoop:
    $ ui.interact()
    jump roomLoop

Code: Select all

                if player.location.exits.get('s'):
                    transform:
                        rotate 180.0
                        imagebutton:
                            idle 'gui player_menu compass point'
                            pos (162, 160)
                            anchor (0.5, 0.5)
                            action Function(renpy.call, label="movePlayer", exit='s')
                            sensitive guiSensitive

Code: Select all

label movePlayer(exit=None):
    $ guiSensitive = False

    if exit and player.location.exits.get(exit):
        $ player.location = player.location.exits.get(exit)
    
    $ guiSensitive = True
    return
This appears to accomplish everything I want it to.

Post Reply

Who is online

Users browsing this forum: mold.FF