Modal dialog

Avatar

By rsg167 8 Aug 2015 23:36

Member · 37 comments

Hello! Is there a way to open a dialog, wait for K_RETURN, then do something else? I'd like the event to wait until it's been closed.

5HfHDwP.png
TZzSWvu.png

Avatar

By rsg167 8 Aug 2015 23:39

Member · 37 comments

For examination, here is my event.

uaP8827.png

Avatar

By ShadowApex 9 Aug 2015 07:30

Lead Developer · 374 comments

Right now there's not an elegant way to do this unfortunately, which isn't good given this scenario is going to be quite common. Some modifications might need to be made to make the dialog block subsequent actions by default or something similar. We might also be able to create a "dialog_open" condition which can check to see if a dialog window is open or not.

As a work around, you might be able to use the set_variable action and create a separate event with the battle. Something like this might work:

event1

act1 dialog SHANE: Hey. You wanna battle? Let's go!
act2 set_variable shane_talked:true
cond1 is facing_npc Maple
cond2 is button_pressed K_RETURN

event2

act1 start_battle 1
cond1 is variable_set shane_talked:true
cond2 is button_pressed K_RETURN

Avatar

By rsg167 9 Aug 2015 22:58

Member · 37 comments

wait_for_button and wait_for_secs actions would be handy in this situation. They could also be used in other situations, like cutscenes.

A straight-forward way to implement these is to have a game.tick() function that advances the game by one frame. This function would draw sprites and flip the screen normally, but not activate other events. Then, the wait_* actions would look like this.

class Core(IPlugin):
    #...
    
    def wait_for_secs(self, game, action):
        secs = float(action[1])
        # I don't know how to really get the game's FPS
        frames = int(secs * FPS)
        
        for _ in xrange(frames):
            game.tick()
    
    def wait_for_button(self, game, action):
        button = getattr(pygame, action[1])
        
        while not game.keys[button]:
            game.tick()

Last edited by rsg167 (9 Aug 2015 23:01)

Avatar

By ShadowApex 11 Aug 2015 01:37

Lead Developer · 374 comments

Right now the time that has passed between each frame (time delta) is gotten in the main_loop() method in core/tools.py. Implementing a game.tick() function exactly like your example might be a little difficult based on how the current draw and scene methods are set up. In general, you almost never want to use a while loop inside the main game loop, or you risk the game locking up. One way we might be able to implement it though is at the event engine level.

Right now the EventEngine object's check_conditions() method is called every frame as a part of the main_loop() method in core/tools.py. We could create a property in the EventEngine object that we could check to see if we should pause the processing of event conditions. Then our wait_for_secs action would set that property with a given amount of time.

Something like this maybe?:

components/event/__init__.py

class EventEngine(object):
    def __init__(self):
        self.name = "Event"
        self.current_map = None
        self.conditions = condition_methods
        self.actions = action_methods
        self.state = "running"
        self.timer = 0.0
        self.wait = 0.0
...
    def check_conditions(self, game, time_delta):
        if self.state == "running":
            for e in game.events:
                should_run = True
                ...
        elif self.state == "waiting":
            if self.timer >= self.wait:
                self.state = "running"
                self.timer = 0.0
            else:
                self.timer += dt
...

Then our wait_for_secs action would look like this:

    def wait_for_secs(self, game, action):
        secs = float(action[1])
        game.event_engine.state = "waiting"
        game.event_engine.wait = secs

Avatar

By ShadowApex 17 Aug 2015 03:50

Lead Developer · 374 comments

I implemented this to a degree in the latest commit of the development branch:
https://github.com/Tuxemon/Tuxemon/comm … 69c2b86870

Unfortunately, the way I implemented it, you won't be able to pause the event engine in the middle of executing a group of actions, since it exists outside the for cond in e['conds']: loop, but it is possible to do what you want with two separate events. I'll need to think about some other ways we could do this.


Avatar

By rsg167 18 Aug 2015 17:16

Member · 37 comments
ShadowApex wrote

I implemented this to a degree in the latest commit of the development branch:
https://github.com/Tuxemon/Tuxemon/comm … 69c2b86870

Unfortunately, the way I implemented it, you won't be able to pause the event engine in the middle of executing a group of actions, since it exists outside the for cond in e['conds']: loop, but it is possible to do what you want with two separate events. I'll need to think about some other ways we could do this.

Hmm...

If only actions could “pause” and then “resume” on the next frame. Something like coroutines. Python has generators, which do this, albeit clumsily.

Well, what if we turn events into generators? They would yield instructions for the main “thread” to handle. One instruction would be resume-next-frame, which would tell the event manager to store the generator and resume it the next frame.

Given the following event:

cond1    is button_pressed K_RETURN
act1     dialog ... Woah.
act2     dialog You need to train, man.

If the condition succeeds, it will generate the following instructions:

>>> g = run_event(my_event)
>>> g.next()
('dialog', '... Woah.')
>>> g.next()
('resume-next-frame', None)
>>> g.next()
('resume-next-frame', None)

It will keep giving us ('resume-next-frame', None) until the dialog is closed, like this:

>>> g.next()
('resume-next-frame', None)
>>> game.state.dialog_window.visible = False
>>> g.next()
('dialog', 'You need to train, man.')
>>> g.next()
('resume-next-frame', None)

Last edited by rsg167 (18 Aug 2015 17:19)

Avatar

By rsg167 19 Aug 2015 14:03

Member · 37 comments

Actually, my above suggestion is needlessly complex.

Events can just yield a dummy value (True) if they need to pause until the next frame.

core/components/event/actions/core.py

def dialog(self, game, action):
    text = str(action[1])
    text = text.replace("${{name}}", game.player1.name)

    # Open the dialog
    game.state.dialog_window.visible = True
    game.state.dialog_window.text = text

    # Yield True until the window is closed
    while game.state.dialog_window.visible:
        yield True

When EventEngine receives True, it will save the generator inside the event being processed. The event would then look like this:

{
    'acts': [...],
    'conds': [...],
    'saved': <generator object at 0x0230AD50>
}

Next frame, it will see e['saved'] and easily resume it. It's a weird use of generators, but at least we wouldn't have to deal with OS threads!

Avatar

By ShadowApex 19 Aug 2015 23:22

Lead Developer · 374 comments

Wouldn't the while game.state.dialog_window.visible hang the main loop if we're iterating over it? I'm not intimately familiar with generators in Python, but I wouldn't be opposed to using them. I think being able to pause and resume execution of individual events until conditions are met is a good idea. I'm not sure exactly how you were planning on implementing it though. Do you have any branches that tests that implementation out?

On a related note, I made some changes to how button presses are handled by some events. Previously, the button_pressed condition was looking at game.keys to see if a particular key is being pressed. If you held the key down for longer than a frame though, the condition would keep returning True, which would cause an event to trigger over and over again. Instead, I'm now handling these key press events upon KEYUP, so it will only happen once.

After making that change, I could successfully make two events which would trigger a dialog followed by a battle:

event1
whjcGg2.png

event2
0THAmxu.png

result
4rMtOzx.gif


Avatar

By rsg167 25 Aug 2015 13:18

Member · 37 comments

I tried implementing my generators idea, but the code got really weird and I never got it working.

We'll have to find another way, haha. We might draw inspiration from the scripting language used in Earthbound: http://starmen.net/mother2/gameinfo/script/

Avatar

By ShadowApex 26 Aug 2015 04:54

Lead Developer · 374 comments

That's unfortunate sad. I'll keep thinking of some ways we might be able to better handle these kinds of events. The Earthbound scripting looks interesting, especially the different dialog options. Right now there's not any sort of real dialog choice events or shop events. Might be worth checking out.


Avatar

By josepharaoh99 12 Nov 2015 18:54

Champion · 295 comments

Is there any way to have multiple text dialogs in a row? You know, people talk back and forth and you press enter to bring the next text box?


Multiple Media Producer
Jesus is God! http://www.upci.org/search

Avatar

By ShadowApex 13 Nov 2015 00:33

Lead Developer · 374 comments
josepharaoh99 wrote

Is there any way to have multiple text dialogs in a row? You know, people talk back and forth and you press enter to bring the next text box?

I haven't tested other possible ways to do this, but one way could be to use the set_variable action to indicate that the first dialog has been read. Then you can create a second event with the variable_set condition to check to see if the first dialog was already displayed.

Here's an example of how that might look:

Event 1

cond1    is button_pressed K_RETURN
act1     dialog How are you?
act2     set_variable dialog1_read:True

Event 2

cond1    is button_pressed K_RETURN
cond2    is variable_set dialog1_read:True
act1     dialog I'm fine, thanks!