Ren'Py Anyone have a tried & true, basic, modular event handler that kicks in after moving to new location?

hakarlman

Engaged Member
Jul 30, 2017
2,097
3,274
In code, you've moved to a new location. Now you need to check for events, of which there can be multiple event types that vary.

This is pseudo code of what I'm looking for.

Code:
label start:
      # navigation movement code returns alley50
      jump alley50
     return

label alley50:
    "you've arrived at alley 50.. you look around..."      
     
     #check for events      
      returnedEventLabel = checkForEvent("alley50")
    
        if returnedEventLabel not blank  then
            jump returnedEventLabel  #event50
      else
           #no events, player doesn't see anything going on and chooses to leave via navigation
      return

label event50:
      "You're being ambushed!!! shit!!"
      return

init python:
      # this is sloppy i know, but just want to show the flow
      checkForEvents {
                conditionList = (event01: label: event50, condition, condition, condition,
                                                    event02: label: event51,  condition, condition, condition,
                                                    event03:  label: event52, condition, condition, condition,
               eventLabel = docheckAndReturnEventLabel
              return eventLabel

       }
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,363
15,281
This is pseudo code of what I'm looking for.
The most complicated part is the one hidden behind "checkForEvent("alley50")".

For the Ren'py part itself, the code should looks like:
Python:
label alley50:

    #  Check the event before the  "welcome" part. You can
    # want the event to in fact take place while the player
    # is moving to this place, but never reach it.
    returnedEventLabel = checkForEvent("alley50")
 
    # If an event is triggered, play the event
    if not returnedEventLabel is None:
        jump expression returnedEventLabel

    # Else go to the default code
    "you've arrived at alley 50.. you look around..."    
    [...]

As for the python part, there's really many way to do this.

The one that is probably the most efficient, while still being not this complicated, is to rely on Python's eval instruction. It's an instruction that will evaluate the string given to it, and return a value corresponding to this evaluation. On Ren'py console:
Code:
a = "abc"
b = "def"
eval( "a + b" )
This will return "abcdef".
Note the quotation marks on eval's parameter. It expect a string representing the code you want to evaluate, not directly the code itself.
This mean that if you extend a little this example:
Code:
a = "abc"
b = "def"
abcdef="Here I Am, J.H."
eval( a + b )
This time it will return "Here I Am, J.H.".
You haven't asked eval to evaluate the value of "a + b" (that is "abcdef"), but the value of the variable which name correspond to the concatenation of "a" and "b", therefore the variable named "abcdef".

I know, it's a little complicated, but Ren'py console is also here to make you experiment and then help you understand.


In the context of an event handler, eval permit to get ride of a big if structure, by putting the conditions that you would use in this structure, into strings that can be easily evaluated.
Imagine that you've this if structure:
Code:
if day == 5 and MC_strength < 7 and girl_love < 10:
    jump event1_bad
elif day == 5 and MC_strength < 7 and girl_love >= 10:
    jump event1_saved
elif day == 5 and MC_strength > 7:
    jump event1_good
You replace it by eval with this code:
Python:
#   All the possible couples "conditions" & "label".
#  The label is put first for a better readability.
all_events = [ ( "event1_bad", "day == 5 and MC_strength < 7 and girl_love < 10" ),
      ( "event1_saved", "day == 5 and MC_strength < 7 and girl_love >= 10" ),
      ( "event1_good", "day == 5 and MC_strength > 7" ),
     ]

def eventHandler():
    # iterate through all the couples.
    for label, trigger in all_events:
        # If the trigger, therefore the condition, is /True/...
        if eval( trigger ) is True:
            # You triggered the event, return it's label.
            return label
This is the same than the if structure above.
Note that the string to pass to eval is not difficult to find. It's what you would have wrote after the if/elif.


In your particular case, what you need looks more like this :
Code:
#  This time, use a dictionary to separate the couples "label" & "conditions"
# into different groups corresponding to the actual location.
all_events = {
    "alley50": [  ( "event1_bad", "day == 5 and MC_strength < 7 and girl_love < 10" ),
            ( "event1_saved", "day == 5 and MC_strength < 7 and girl_love >= 10" ),
            ( "event1_good", "day == 5 and MC_strength > 7" ),
           ],
    "otherLocation": [ ( "someLabel", "a condition whatever" ),
           ( "anotherLabel", "another condition" ),
          ],
     }

def eventHandler( location ):
    #  Iterate through the couples corresponding to
    # the actual location.
    for label, trigger in all_events[location]:
        if eval( trigger ) is True:
            return label
It's wrote on the fly, so there's perhaps some issue, but nothing that should be difficult to solve.

But as I said, it's not the only way to do it, just the one that personally I found the best compromise between efficiency, readability and maintenance, while staying relatively easy to use.
 

hakarlman

Engaged Member
Jul 30, 2017
2,097
3,274
The most complicated part is the one hidden behind "checkForEvent("alley50")".

For the Ren'py part itself, the code should looks like:
Python:
label alley50:

    #  Check the event before the  "welcome" part. You can
    # want the event to in fact take place while the player
    # is moving to this place, but never reach it.
    returnedEventLabel = checkForEvent("alley50")

    # If an event is triggered, play the event
    if not returnedEventLabel is None:
        jump expression returnedEventLabel

    # Else go to the default code
    "you've arrived at alley 50.. you look around..."   
    [...]

As for the python part, there's really many way to do this.

The one that is probably the most efficient, while still being not this complicated, is to rely on Python's eval instruction. It's an instruction that will evaluate the string given to it, and return a value corresponding to this evaluation. On Ren'py console:
Code:
a = "abc"
b = "def"
eval( "a + b" )
This will return "abcdef".
Note the quotation marks on eval's parameter. It expect a string representing the code you want to evaluate, not directly the code itself.
This mean that if you extend a little this example:
Code:
a = "abc"
b = "def"
abcdef="Here I Am, J.H."
eval( a + b )
This time it will return "Here I Am, J.H.".
You haven't asked eval to evaluate the value of "a + b" (that is "abcdef"), but the value of the variable which name correspond to the concatenation of "a" and "b", therefore the variable named "abcdef".

I know, it's a little complicated, but Ren'py console is also here to make you experiment and then help you understand.


In the context of an event handler, eval permit to get ride of a big if structure, by putting the conditions that you would use in this structure, into strings that can be easily evaluated.
Imagine that you've this if structure:
Code:
if day == 5 and MC_strength < 7 and girl_love < 10:
    jump event1_bad
elif day == 5 and MC_strength < 7 and girl_love >= 10:
    jump event1_saved
elif day == 5 and MC_strength > 7:
    jump event1_good
You replace it by eval with this code:
Python:
#   All the possible couples "conditions" & "label".
#  The label is put first for a better readability.
all_events = [ ( "event1_bad", "day == 5 and MC_strength < 7 and girl_love < 10" ),
      ( "event1_saved", "day == 5 and MC_strength < 7 and girl_love >= 10" ),
      ( "event1_good", "day == 5 and MC_strength > 7" ),
     ]

def eventHandler():
    # iterate through all the couples.
    for label, trigger in all_events:
        # If the trigger, therefore the condition, is /True/...
        if eval( trigger ) is True:
            # You triggered the event, return it's label.
            return label
This is the same than the if structure above.
Note that the string to pass to eval is not difficult to find. It's what you would have wrote after the if/elif.


In your particular case, what you need looks more like this :
Code:
#  This time, use a dictionary to separate the couples "label" & "conditions"
# into different groups corresponding to the actual location.
all_events = {
    "alley50": [  ( "event1_bad", "day == 5 and MC_strength < 7 and girl_love < 10" ),
            ( "event1_saved", "day == 5 and MC_strength < 7 and girl_love >= 10" ),
            ( "event1_good", "day == 5 and MC_strength > 7" ),
           ],
    "otherLocation": [ ( "someLabel", "a condition whatever" ),
           ( "anotherLabel", "another condition" ),
          ],
     }

def eventHandler( location ):
    #  Iterate through the couples corresponding to
    # the actual location.
    for label, trigger in all_events[location]:
        if eval( trigger ) is True:
            return label
It's wrote on the fly, so there's perhaps some issue, but nothing that should be difficult to solve.

But as I said, it's not the only way to do it, just the one that personally I found the best compromise between efficiency, readability and maintenance, while staying relatively easy to use.
This worked fantastic. The way you set it up via dictionary like that, brilliant.