Event Handler with Lexer based controls

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Message
Author
User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Event Handler with Lexer based controls

#1 Post by Remix »

Event Handler

The main file ( Event Handler class, Lexer system and even an Information Screen )
01event_handler.rpy
(100.89 KiB) Downloaded 525 times


A sample script.rpy with a very basic game showcasing some of the features available
script.rpy
(11.92 KiB) Downloaded 415 times
Some Notes on the Sample Script file

Please let me know if the font size in the Info Panel (click top left button) is iffy... I think I altered font sizes in gui.rpy and did not include that



This is a drop-in script that runs itself in the background and allows inline Ren'py-esque code to manage the instantiation of the handler and subsequent registering of labels as events.

It basically allows you to do:

Code: Select all

# Minimal Sample Script using very few features and relying mostly on default values

label start:
    # Initialize the Event Handler
    event setup:
        start = "7:30 Apr 1"
        
    "It is [event_handler]"
    # this would actually output 'It is 07:30 on Monday the 1st April'
    return

label an_event:
    event register:
        hour range(8, 11)
        weekday "Monday" "Tue" "Wed"
        repeat 3
        
    "Ooh, this label has been registered as an event"
    
    event time "1 hour 30 mins"
    
    "Ooh, time passed... it is now [event_handler]"
The handler takes charge of storing and incrementing a minute value against which it can test events to determine which are valid and which are not

There are numerous facilities available to tweak the handler to just about any situation wanted, most supported through simple lexer based input, including:
  • Setup: Define start moment, the default step, weekday names, month names and lengths
  • Setup: Define input and output string formats to use as defaults

  • Event: Register conditions, including: Minutes, hours, days, months, set timespans, external functions, minimum repeat intervals, maximum repeats
  • Event: Test validity. Get all valid. Get duration until next valid. Get reasons why it failed tests.

  • Time: Advance the internal minute, manually or automatically

  • Log: Retrieve logging output and interpreted values for both the handler setup and any named label
Further information on the following topics can be found by clicking the links below:


Setting up the handler
(input variables, syntax etc)


Registering a label as an Event
(input variables, syntax, advanced features)


Testing Events


Advancing Time


Additional Features / Further Information


FAQs
(and maybe answers too)


Caveats and Todo List (an overview of what needs doing)
  • Proper translation support
    # Currently it lists 'event setup:' as a
    # translatable... Also need weekday/month
    # names to import/register using English
    # then output using pref language

  • Lexer alter event code
    # Mostly there, just not tested and could do
    # with syntax changes

  • Lexer lint sanity

  • Clean up the screen code
    # Eww, it is nasty

  • Refactor class to use event classes and
    # those event classes to use test classes
    # for sectors like time, functions, etc

  • Integrate refactoring with lexer - might
    # mean initializing in a python early though
Last edited by Remix on Wed Oct 25, 2017 7:03 am, edited 6 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#2 Post by Remix »

Setting up the handler

As shown briefly above, the handler is setup with inline Ren'py style code.
A lexer interprets the code then passes it to the handler to adjust internal values away from their defaults

The 'event setup:' line should in most cases be placed within the 'start' label

Available options are: (with inline commentary)

Code: Select all

label start:
    # Initialize the Event Handler
    
    # This block line tells Ren'py to use the lexer/parser code within the handler
    
    event setup:
    
        # An input format string using dictionary braces and keywords from the list at end
        # 
        # This string style is how we want to talk to the handler... If we want to say '12 Dec 13:45'
        # we would use "{dom} {month} {hour}:{minute}"
        # (note: each {keyword} must have a space or other character between it and the next
        
        input "{hour}:{minute} {dom} {month}"
        
        
        # Similar to above, just defining the default output format to use
        
        output "{hour:02d}:{minute:02d} on {weekday} the {dom}{dom_ordinal} of {month}"


	# The moment we want the game to start from, default is actually 0
	# (note: we can use a string in the format defined as 'input' above)
	
        start = "7:30 1 Mar"
        
        
        # The default number of minutes to step - 30 would be 30 mins (half an hour) step
        # (note: It is best to set this as high as logically makes sense in your game)
        
        step 30
        
        
        # the default event duration
        
        duration = 60 
        
        
        # changing the weekday order/start or naming them after animals (cough)
        # shorter names are taken as abbreviations for use in {weekday_abbr} moments
        # (note: The below would yield a 3 day week)
        
        weekdays:
            "Rat" "Ratday"
            "Mou" "Mouse" "Mouseday"
            "Cat" "Catday"
            
            
        # Similar to weekdays for setting months
        # Each one should include an integer for the number of days (else default used)    
        
        months:
            "CheeseMonth" "Cheese" 17
            "MilkMonth" "Milk" 18
All values are optional, though if you are going to just use defaults then still call 'event setup:' with 'pass' indented within the block

Any ' = ' in the variable name [space] value lines is basically ignored, so is optional


Available {names} to use in input / output values:
(be sensible with input... think like a machine)

Code: Select all

        'month',        # month name (long)
        'month_abbr',   # month name (abbreviation)
        'dom',          # day of month 
        'dom_ordinal',  # day of month ordinal, e.g 'st' for 1 = 1st
        'dow',          # day of week as numeric
        'dow_ordinal',  # day of week ordinal, e.g 'nd' for 2 = 2nd
        'week',         # week as numeric
        'week_ordinal', # week ordinal, e.g 'rd' for 3 = 3rd
        'weekday',      # weekday name (long)
        'weekday_abbr', # weekday name (abbreviation)
        'day',          # day since game start as numeric
        'day_ordinal',  # game day ordinal, e.g 'th' for 4 = 4th
        'hour',         # hour, 0-23 generally
        'hour12',       # hour as 1-12
        'ampm',         # 'am' or 'pm' to go with hour12
        'minute'        # minute numeric, 0-59 generally
Last edited by Remix on Sat Oct 21, 2017 5:44 pm, edited 6 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#3 Post by Remix »

Registering a label as an Event

As shown briefly above, the label is registered as an event with inline Ren'py style code.
A lexer interprets the code then passes it to the handler to record the values against its list of events

The 'event register:' line should be inside (indented) within the label it applies to

Available options are: (with inline commentary)

Code: Select all

label some_event:
    
    # This block line tells Ren'py to use the lexer/parser code within the handler
    # to register the relevant label
    
    event register:
    
        # Any minutes (of any hour) the event can start
        # Input can be: 
        # space separated integers, 0 15 30
        # a list of integers, [ 0, 15, 30 ]
        # a python range, range(0, 31, 15)
        # or even ranges in list or space separated
        
        minute 0 30
        
        
        # Any hours the event can start
        # same input style as above 
        
        hour range(8, 11) range(15, 19)
        
        
        # Any set day the event can run
        # (big note: day is a count from day 1 to the end, it is NOT day of month 'dom' )
        # same input style as above 
        
        # day [7,12,28,37]
        
        
        # Any named weekday the event can run 
        # (searches long and abbreviated names)
        # Also supports text word ' to '... "Fri to Sun"
        
        weekday "Monday" "Wed"
        
        
        # Day of Month 'dom'
        # same input style as minute/hour
        
        dom range(1, 6) range(12, 20)
        
        
        # Any month the event is valid
        # same input style as weekday
        
        month "Apr" "March"


	# Between(s) are independent to any values set 
	# above.
	# They basically tell the event it is available between a start
	# and end moment
	#
	# Between(s) are useful for things like new moons, where they
	# might occur on the 5th of one month, then the 2nd of the next
	#
	# You may prefer to just use between(s) and ignore the above options totally
	
        between:
            "01:00 Mar 1 to 18:00 March 8"
            "10:30 Apr 10" "12:00 Apr 10"
            "12:00 Apr 17" "14:30 Apr 17"
            
           
        # Duration for this event if not default
        
        duration 90
        
        
        # A time span that must elapse between repeats of this event
        
        interval "3 day 16 hour 30 min"
        
        
        # The maximum number of times this event can run
        
        repeat 3
        
        
        # User function(s) to call
        # 
        # multiple functions can be added
        # They should simply return True or False upon success or failure
        #
        # syntax
        # function name [space] optional *args in a list [space if needed] optional **kwargs in a dict
        
        function:
            my_user_func [ alice, 21 ] { 'arg' : alice, 'arg2' : 'some value' }
            
            
        # any unknown keyword is just stored as a list in 
        # event_handler.events[ label name ]['args']['unknown_keyword'] = [ list of vals ]
        
        unknown_keyword some_variable {'dict' : 'test'}
        caption "Text for my button"
        other_unknown 166 ['a','list'] 
Further information on functions and unknown keywords is in:

Additional Features / Further Information
Last edited by Remix on Sat Oct 21, 2017 4:30 pm, edited 2 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#4 Post by Remix »

Testing Events

There are a number of ways of testing events within the handler system:

Dot notation syntax - easy within Ren'py string interpolation if needed:

Code: Select all

# a list of available labels for the current game minute

event_handler.labels


# within a label, testing itself - returns True or False

event_handler.valid
Most testing and manipulation of any returned data will likely be done in python though.
The main functions of use would be

Code: Select all

# test a label name versus a minute (or None if just current minute) - return True or False

event_handler.test_event( label_name, [ optional minute value ] )


# similar to above, just returns a list of fail reasons - more process intense though 

event_handler.test_event_reasons( label_name, [ optional minute value ] )


# get the game minute that the next event (or next different list of events) occurs
# returns -1 on fail

event_handler.get_next_event_minute( minute=None, different=False )


# how many minutes until next event
# negative means fail/none found

event_handler.get_minutes_until_next_event( minute=None, different=False )


# a list of available labels for a passed minute (or current game minute)

event_handler.get_label_names_for_minute(  minute=None )
From a label name (or iterating a list of them) you can access the held data by calling something like

Code: Select all

label_event_dictionary = event_handler.events[ label_name ]
The dictionary should make reasonable sense, a blank default would be

Code: Select all

            event_handler.events[ label name ] = {
                'minute' : [],
                'hour' : [],
                'day' : [],
                'weekday' : [],
                'dom' : [],
                'month' : [],
                'between' : [],
                'range' : [],
                'args' : {}
            }
Though you may notice extra key:value pairs for 'function', 'visit' etc
Most store their data in minute or list index fashion, so 'weekday' might hold [0,3,4]
rather than Mon, Thu, Fri

A more human readable version of the event data can be seen in:

Additional Features / Further Information
Last edited by Remix on Wed Oct 25, 2017 7:11 am, edited 4 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#5 Post by Remix »

Advancing Time

Advancing time can be done inline with a simple 'event time [optional args]' line

Code: Select all

label some_event:
    event register:
        duration 120
        
    "Time: [event_handler]" # just calling that outputs current time in 'output' format

    # advance by 30 mins
    event time 30 

    "Time: [event_handler]"

    # Advance with text
    # supports int [space] minute(s), min(s), hour(s), day(s), week(s) - "2 week 3 hour 1 day 45 min"
    # (note: It should be a multiple of step)
    event time "15 minutes" 
    
    "Time: [event_handler]"
    
    # inside a label that is an event, adding no args to the line
    # will try to add a number of minutes to total the event duration
    #
    # so with the 30 and 15 above, this would add 75 more minutes
    #
    # It will default to event duration otherwise 
    event time
    
    "Time: [event_handler]"
Outside an event label, the basic
event time [integer]
event time [string duration]
will work fine

Alternatively, python calls to
event_handler.advance_time(minutes=None, label=None)
will do as expected

Event note:
Labels that are events will have the minute logged if called as a new access (basically if their duration has passed since last access) thus allowing the handler to test 'repeat' and 'interval'
This applies to both inline 'event time [arg]' calls and any python calls that pass a label name
Last edited by Remix on Sat Oct 21, 2017 5:22 pm, edited 2 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#6 Post by Remix »

Additional Features / Further Information

If you do not want to have to type 'event_handler' every time you want to reference the handler object, just find:
default_event_handler_reference = 'event_handler'
(around line 91 in current version in 01event_handler.rpy) and change it to what you want... e.g.
default_event_handler_reference = 'evie'
would allow access using calls such as ' if evie.valid: do stuff '

Event Processing Overview

When an event is registered to the handler, code runs that interprets all the 'minute', 'hour', 'day', 'dom', 'weekday' and 'month' values, then iterates the game cycle and determines pairs of [start, end] game minutes where the event is available timewise. Further tests of the event will just test those 'range' pairs rather than doing tests against all the associated time parts. This helps speed inline testing while playing the game.

The 'between' values are interpreted independent to the 'range' values. For an event to be available on a certain minute it only needs pass either the 'range' test or the 'between' test.
* the event would still need to pass any function tests to be valid though

Event - User Functions

To enable the handler to work with more advanced systems and variable conditions, events can be registered with function calls.
Functions written by the user are expected to simply return True or False, basically each test of the event will call the time based tests first, then each registered function and only yield a True 'pass result' if everything passed.

Just prior to calling each registered function, the handler py_eval's the *args list and **kwargs dictionary, so non string enclosed variable will be pre-interpreted, for example:

Code: Select all

        function:
            my_user_func [ alice, 21 ] { 'arg' : alice }
would effectively call

Code: Select all

my_user_func( alice, 21, arg=alice )
If alice had been set to a value (or an object) that value would be available within the function

Code: Select all

init python:
    def my_user_func( person, test_age, arg=None ):
        return person >= test_age
        
default alice = 19

.... within some label some where
    "[event_handler.valid]" # False - effectively calling my_user_func( 19, 21, arg=19 )
    $ alice += 1
    "[event_handler.valid]" # False (still only 20)
    $ alice += 1
    "[event_handler.valid]" # True - (now 21) my_user_func( 21, 21, arg=21 ) tests 21 >= 21 and returns True
As the list and dictionary are py_eval'ed as their object type, you could pass in strings and use conditional logic within those strings to py_eval sets of conditions, e.g.

Code: Select all

init python:
    def pass_all_tests( *args ):
        for arg in args:
            if not renpy.python.py_eval( arg ):
                return False
        return True
        
    def pass_one_test( *args ):
        for arg in args:
            if renpy.python.py_eval( arg ):
                return True
        return False

....    
    event register:    
        function:
            pass_all_tests [ 'alice >= 21', 'location == "bat cave" or money >= 100' ]
            pass_one_test [ 'alice >= 22', 'location == "spidey lair" ]
As seen above, multiple functions can be added to the event.
The logic dictates that All must pass for an event to be valid though

Event - Unknown Keywords

Todo:...

Code: Select all

        # any unknown keyword is just stored as a list in 
        # event_handler.events[ label name ]['args']['unknown_keyword'] = [ list of vals ]
        
        unknown_keyword some_variable {'dict' : 'test'}
        caption "Text for my button"
        other_unknown 166 ['a','list'] 

Readable Logs
Other Stuff
Last edited by Remix on Fri Nov 03, 2017 10:54 am, edited 5 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#7 Post by Remix »

Some notes on the Sample Script file

Please let me know if the font size in the Info Panel (click top left button) is iffy... I think I altered font sizes in gui.rpy and did not include that

The sample script is just a very basic example of registering labels with a few user functions to test against location, affection and money.
It should hopefully show an idea of how to register a label with a function test and how that is processed within the handler.

At the top left is a button to open the Info Panel
This allows stepping through pseudo minutes of the game to test event availability
Various information areas are available within the panel
(drag it using top bar, scroll info area with drag or mouse)
It might be quite useful while developing a game, just to check an event register makes sense
Last edited by Remix on Wed Oct 25, 2017 7:03 am, edited 3 times in total.
Frameworks & Scriptlets:

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#8 Post by Remix »

Frequently Asked Questions

I will keep filling in the blanks ... slowly
Frameworks & Scriptlets:

advance2
Newbie
Posts: 8
Joined: Thu Apr 12, 2018 5:10 pm
Contact:

Re: Event Handler with Lexer based controls

#9 Post by advance2 »

Can you post full sample code for

viewtopic.php?t=45886

a morning, noon, evening & night cycle with Weekdays,

to make it so when the character enters a room an event starts, then, once that even is finished he can go in the same room and they'll be another even, but i only want the events to start during a set time of the day ( Morning, Noon ect ect )

using image maps.

Muchos Gracias.

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#10 Post by Remix »

The event handler as posted is designed to accommodate broader and more varied systems with events taking variable durations though could reasonably be tweaked to do what you need by defaulting to a 6 hour time increment and biasing your time of day on that.

The event_handler.get_hour_of_day() method would return the hour and then you interpret that against your cycles - e.g. if it returns 0 you call that night, 6 is morning, 12 noon, 18 evening then back to 0.

For the rooms thing I'd personally use a missing_label callback, pull apart the label name, test events that include that name and then jump to any valid/available event as required. That way would avoid the need to write the same type code for each location.

I've never (nor will I ever) use image maps...
For ImageButtons I'd generally have them placed with an action pointing at a label jump using a name that invokes the missing label callback, probably with a conditional for sensitivity such as If( len([k for k in event_handler.labels if 'label_ref' in k]), Jump('label_ref_missing') )
Frameworks & Scriptlets:

Darcoria
Newbie
Posts: 5
Joined: Fri Jul 28, 2017 8:42 am
Contact:

Re: Event Handler with Lexer based controls

#11 Post by Darcoria »

Thank you!
I had seen this event handler somewhere else (think you also posted it there).
It left me wondering how it worked, thanks for the heads up about it!

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#12 Post by Remix »

I am in the process of fully updating it so, if possible, I advise holding back a while.

The updated version sits on python dateutil which allows a cleaner approach to handling time and makes internationalization easier to integrate.
The Events are their own objects with 'tests' added as sub-objects through a factory processed by the lexer (so you could add a LocationTest object and the lexer would use it for Events that had a line like ... location "bedroom" ...)
I am also slimming down the init part by just having start and output as variables which removes a lot of bloat code.
Frameworks & Scriptlets:

LuckyT
Newbie
Posts: 2
Joined: Tue Jul 24, 2018 8:11 am
Contact:

Re: Event Handler with Lexer based controls

#13 Post by LuckyT »

It looks very interesting. Any progress info?

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Event Handler with Lexer based controls

#14 Post by Remix »

Just moved from the framework design part to the testing part, which in layman's terms means "maybe half way or a bit further" ...
The last month though has been busy for me with other things so I might get to spend more time on this and progress faster.

Initially I will be running an implementation in a game for an acquaintance so will be focusing on features relevant to that. Basically, translation support and dateutils rrule features will be on hold for a while.
When it does come time to add the recipe to the cookbook I will also likely start a new thread (so this one can quietly fade away).
Will add a post pointing at the new recipe when I do though.
Frameworks & Scriptlets:

Wataru2
Newbie
Posts: 12
Joined: Sat Sep 29, 2018 11:04 am
Contact:

Re: Event Handler with Lexer based controls

#15 Post by Wataru2 »

Thank you for making this! It's very easy to learn and grasp the controls. However when I try implementing into my own format, I'm running into some confusion.

I have a free roam area set up, using screen language, I basically let the players roam in between different rooms. I got the event time increments to work, but I'm wondering how I'd be able to show screen randomevent01 under a time condition with your system. I tried using an if statement to say if hour = 10, so on and so forth but no results... Is there a way to get the hour as a variable? Then use it as a condition to trigger a show screen?

Thank you.

Post Reply

Who is online

Users browsing this forum: No registered users