Making an Event Triggerer for a Sandbox game?

SlutsGetFucked

New Member
Apr 3, 2022
5
1
Hi there!

So, I'm currently developing a sandbox game, but because I don't want it to be a grindy mess, I want there to be a way for the player to be able to trigger a girl's event with the GUI. Now, I'm pretty much a complete novice when it comes to programming, but I've made my way through the Ren'py documentation and tried to search around google and here on F95, but I can't seem to find an exact answer to what I'm trying to do.

So currently, my plan is to have an image button up in the top corner of the screen that, on-click, will open up a menu of all of the girls sort of profile pictures, which will also be buttons that on-click will trigger the next event for that girl.

The way I've come up with to do this is to set up a variable like:

Code:
$ nextTiffEvent = tiffEventOne
and have Tiff's image to be:

Code:
jump [nextTiffEvent]
and then at the end of the the event:

Code:
$ tiffLevel += 1
$ nextTiffEvent = tiffEventTwo
So now, the imagebutton would jump to event two when clicked. The level variable is there in case I ever want one girl's event to be dependent on having seen another girl's event, so I can just set up an if statement that says if you don't have emiLevel = 3 it'll give you a message to advance Emi's story first, or something like that. I don't have plans to do that now, but I want to be prepared for that eventuality.

So my question is, will this work? And even if it does, is there a better way to do this? And if there is could you please explain it as if you were talking to a particularly stupid 6 year old? I haven't come across any other way to do this in my searches, but since I came up with this idea on my own, and I'm pretty clueless, I have a feeling that it's probably too convoluted or inefficient or just plain won't work.

I appreciate the help!
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,576
2,203
jump [nextTiffEvent] sadly doesn't work.

What you want is .

Though you'd also need to change the assignment to $ nextTiffEvent = "tiffEventOne" (note the quotes).

Beyond that, you seem to have the start of a workable system. Although I can think of a couple of improvements by using a to store the events.
(check my signature for more details on lists).

Python:
define narrator = Character(None, what_color='#FFFF66')

default tiff_progress = 0

define tiff_events = ["tiff_event_one",
    "tiff_event_two",
    "tiff_event_three",
    "the_end"]


label start:

    scene black with fade
    "*** START ***"


label tiff_event_nexus:

    jump expression tiff_events[tiff_progress]


label tiff_event_one:

        "You have started Tiff's event #1."
        $ tiff_progress += 1

        jump tiff_event_nexus


label tiff_event_two:

        "You have started Tiff's second event."
        $ tiff_progress += 1

        jump tiff_event_nexus


label tiff_event_three:

        "Welcome to Tiff's third event."
        $ tiff_progress += 1

        jump tiff_event_nexus


label the_end:

    "*** THE END ***"
    return

Where tiff_events is a of strings that contain the names of labels that can be jumped to. The script can alter the array index to pick another (or the next) label.

Though this does presume a linear progress of events. The more complicated the progression decision making, the more complicated the structure of the list(s), etc that control what happens next.


Edit:
Then to expand upon the jump expression:

Python:
    if tiff_progress < len(tiff_events):
        if renpy.has_label(tiff_events[tiff_progress]):
            jump expression tiff_events[tiff_progress]
        else:
            "Bad things happened: Tried to jump to a label that doesn't exist."
            jump the_end
    else:
        "Bad things happened: The array index was bigger than the array."
        jump the_end

Which checks to make sure that the index won't go out of bounds (index pointing to a array element that doesn't exist) as well as making sure the label that will be jumped to actually exists.

Honestly, this sort of checking probably isn't necessary. When these sort of errors creep into your game, you'll find them while writing the scripts and testing. That is... long before a player would ever encounter them.
 
Last edited:
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,299
15,167
So my question is, will this work?
Technically, and taking count of 79flavors adjustment, yes it would works.


But practically it's something different.

As you said yourself, there will be moment when "one girl's event [will] be dependent on having seen another girl's event". It's not a guess, it's a certainty. You can't develop a full story with more than one secondary character without those characters stories being interlaced, and sometimes interdependent. And the issue you'll have to face come from both the interdependence and from the interlacing.


The interdependence first.

There will be moment when there will be no next event for a girl, because the said next event need for the player to have done this/that before ; Tiffany will only have her jealousy scene after you goes to see "that movie" with Isabella instead of going see it with her. But it's not the only dependency issue you'll have to face. There will be events that will be conditioned to the MC personal actions.
If a girl is romantic, she can perfectly say that she'll agree to a second date only after the MC send her flowers ; something that will only happen when the said MC will have enough money. This while the movie night at home scene can only happen once the MC bought a TV. And so on.

Handling this mean that you can't just have a list of events, like shown by 79flavors. For each event you also need to have a condition telling you if the event can be triggered or not.
This isn't an unsolvable issue but, as hinted by 79flavors, it need a more complex structure. You'll need two information in the events list ; firstly the label in charge for that event, secondly a string representing the condition triggering it.
Based on 79flavors code:
Python:
# Note the quotation marks for both the label *and* the condition.
define tiff_events = [ 
    # The event is not conditioned.  
    ( "tiff_event_one", "True", "" )
    # The event can only happen after the Isabella second event.
    ( "tiff_event_two", "isa_progress == 2", "main_nexus" )
    # The event need that the MC bought a computer.
    ( "tiff_event_three", "have_computer is True", "tiff_event_three_fail" )
    ( "the_end", "True", "" ) ]


# Some label common to every girls where the game continue.
label main_nexus:
    [...]


label tiff_event_nexus:

    # Get the label and condition for the next event.
    $ event_label, event_condition, exit_gate = tiff_events[tiff_progress]

    # Test the condition, and branch to the event only if it's True.
    if eval( event_condition ):
        #  If the label do not exist, then the player have seen all it have to see so far.
        # Send him back to the main part of the game.
        if not renpy.has_label( event_label ):
            "You reached the actual end for Tiffany story. More will come with the next update."
            jump main_nexus
        else:
            jump expression event_label
    # If the condition isn't met, play the exit gate event.
    else:
        if not renpy.has_label( event_label ):
            "You reached the actual end for Tiffany story. More will come with the next update."
            jump main_nexus
        else:
            jump expression exit_label


label tiff_event_three_fail:
    MC "Hmm, I should probably buy a computer before I met her again."
    jump main_nexus
The magic rely on the function. It take a string representing some valid python code, evaluate its result, and return that result. In the present case, the string MUST be what you would write after an if.
if day == 'Monday' and know_about_movie_theater is True: would then become "day == 'Monday' and know_about_movie_theater is True.


But, as I said, there's also an issue with the interlacing. And this is a way bigger issue, because it impact the constancy of your story.

The secondary characters will not live in different worlds, they will interact with the MC and together. And the way those interaction will happen depend on what have been done, or not, by the player. But by forcing the events, you'll mess with this.
Python:
label whatever:

    [...]
    TIFF "So, Isabella, I heard that you'll have your first date with [MC_name] ?"
    if brought_flower_Tiffany_date is False:
        TIFF "That you haven't thought to buy some flowers for our date, I can live with this. But Isabella is a romantic girl, you better not forget for her date !"
   [...]
Strictly speaking, Isabella and Tiffany first date aren't dependent, the player can play them in the order he want. The story make it that Tiffany date will happen first, but with your forced event system, the player can make it happen in the opposite order.
In this case, like the date with Tiffany haven't happened yet, the brought_flower_Tiffany_date flag will be False. Therefore Tiffany will complain about something that haven't happened yet.
Worse, if the player already bought the flowers for Tiffany first date, she will complained about something that would not have happened.

This too could be handled, by using a three state semaphore (None the value is irrelevant, False the action didn't happened, True the action happened), but it would obviously make the code be more and more complicated:
Python:
label whatever:

    [...]
    TIFF "So, Isabella, I heard that you'll have your first date with [MC_name] ?"
    if brought_flower_Tiffany_date is False:
        TIFF "That you haven't thought to buy some flowers for our date, I can live with this. But Isabella is a romantic girl, you better not forget for her date !"
    elif brought_flower_Tiffany_date is None:
       TIFF "What a lucky girl you are, I haven't had mine yet. Hmmm, I already see him, walking a flower bouquet in hand."
   [...]


I have a feeling that it's probably too convoluted or inefficient or just plain won't work.
It can works. I don't have name that cross my mind right now, but few are doing something more or less similar.
But, as I said, it imply more works and need that you have a perfect view over the progression of your game and how the different girl story lines interact with each others.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,576
2,203
And this...

You can't develop a full story with more than one secondary character without those characters stories being interlaced, and sometimes interdependent. And the issue you'll have to face come from both the interdependence and from the interlacing.

[...]

... is pretty much what I meant when I say "The more complicated the progression decision making, the more complicated the structure of the list(s), etc that control what happens next".


Personally, I've had in my head for a while a system that acts as a event queue (which could double as a quest tracker), where each event has it's own dependencies and triggers. Then at the start of each "time period", the game randomly picks from the list of currently available events. The player choices still impact what events are made available, but the player is never forced to do the "move to location-A... nothing to do here, move to location-B... nothing to do here... etc" grind so inherent to open world games. In effect... if there is an active event, the player can do it. If there isn't, the player can unlock one.

The problem with such systems is designing one that can cope with an ongoing development cycle. What happens when you add a new event between "TiffEventOne" and "TiffEventTwo" ? What happens when the player reaches the end of a currently available list of Tiff events, but you know that further ones will be added in the future. What happens when you move an event or change it's dependencies or remove one completely? It's a minefield.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,299
15,167
The problem with such systems is designing one that can cope with an ongoing development cycle. What happens when you add a new event between "TiffEventOne" and "TiffEventTwo" ? What happens when the player reaches the end of a currently available list of Tiff events, but you know that further ones will be added in the future. What happens when you move an event or change it's dependencies or remove one completely? It's a minefield.
Because you have it doubling as quest tracker. Make the two "independent", and you can, relatively easily, have something flexible enough.
I used quotation marks because the event will obviously depend on the quest tracker, but through its condition, not through its code.


Story constancy put aside, you just need to change the way you picture the series of events. It must not be like a list, but like a dictionary.
This imply more complex conditions, because you can't just assume that something happened. Something like:
Python:
allEvents = [ ( "girl_love >= 5", "first_date" ),
           ( "own_TV is True", "netflix_and_chill" ) ]
can not be strictly translated. If the player buy the TV before he've raised the love enough, the movie night will happen before the first date, what would be wrong.
But it can become:
Python:
allEvents = { "first_date": "girl_love >= 5",
           "netflix_and_chill": "first_date is True and own_TV is True" }
Side note: I used a dict as demonstration, it don't need to effectively be one.

If you add a priority level to this, you become relatively free do to whatever you want, adding/removing all the events you want without breaking the game:
Python:
init python:

    #  A class is not strictly needed, it just make things easier.
    class Event( renpy.python.RevertableObject ):

        #  2 is the median priority.
        def __init__( self, label, condition="True", priority=2 ):
            self.condition = condition
            self.label = label
            self.priority = priority

        #  Will return True is the condition are met, False else.
        def triggered( self ):
            return eval( self.condition )

    #  Return the next event to proceed, if there's one.
    def nextEvent():

        # Browse the event list, ordered by priority.
        # I have a doubt and can't test, the list possibly need to be reversed.
        for event in sorted( allEvents, key = lambda x: x.priority ):
            #  First event triggered will be played.
            if event.triggered:
                #  Remove it since it will not serve anymore.
                allEvents.remove( event )
                return event.label
         return None


#  Event available when the game starts.
default allEvent = [ Event( "met_Tiffany" ), 
                     Event( "first_date", "tiff_meet is True" ),
                     #  Due to its higher priority, MC's sister will joke about him having a
                     # girlfriend *before* the second date. This despite the two events
                     # having the exact same condition.
                     Event( "sister_sarcasm", "date_count == 1 and sister_at_home is True", 3 ),
                     Event( "second_date", "date_count == 1" ),
                     [...]
                     # Normal priority here, because it happen in the regular.
                     Event( "phone_call", "1 <= date_count < 2" ) ),
                     [...] ]

label hub:

    #  If there's an event, play it, else do some generic default action.
    $ nextLabel = nextEvent()
    if nextLabel:
        jump expression nextLabel
    else:
        "Woud you fancy a nap while the girls are busy not caring about you ?"
Let's say that there's more events before the second date, you can later have:
Python:
#  Event available when the game starts.
default allEvent = [ Event( "met_Tiffany" ), 
                     Event( "first_date", "tiff_meet is True" ),
                     Event( "sister_sarcasm", "date_count == 1 and sister_at_home is True", 3 ),
                     Event( "second_date", "date_count == 1" ),
                     [...]
                     # Normal priority here, because it happen in the regular.
                     Event( "phone_call", "1 <= date_count < 2" ) ),
                     [...] ]


label after_load:

    if saved_version == "0.1":
        #  High priority here, to ensure that it will be triggered before
        # the event possibly still waiting to happen before the second date.
        $ allEvents.append( Event( "phone_call", "1 <= date_count < 2", 5 ) )
   [...]

label phone_call:
    TIFF "I was thinking about our first date..."
    [...]
Like the events aren't strictly ordered, whatever how many events the player already had after the first date, the phone call will happen. This to the condition that it's still relevant, and therefore that the second date haven't happened yet.
Yet, strictly speaking in this particular case it's not really necessary, she can think and talk about the first date even after the second.

There will be players who have had the second date, and therefore will not have the phone call.
But the scene wouldn't be relevant for them anyway. And it's what will have happened if the game wasn't based on events.
But for players who haven't had the second date, they'll get the phone call. Something that probably wouldn't have happened if the game wasn't based on event, because the jump would have been added into a label they already passed.

If you rely on a quest tracker, you can even have a system with a double condition:
Python:
init pyhon:
    class Event( renpy.python.RevertableObject ):

        #  2 is the median priority.
        def __init__( self, label, quest="True", condition="True", priority=2 ):
            self.quest = quest
            self.condition = condition
            self.label = label
            self.priority = priority

        #  Will return True is the condition are met, False else.
        def triggered( self ):
            return eval( self.quest) and eval( self.condition )

default allEvent = [ Event( "met_Tiffany" ), 
                     Event( "first_date", "tiff_quest == 1", "tiff_meet is True" ),
                     # No more need to test how many date or whatever, if the quest isn't at the
                     # right step, all the condition aren't met.
                     Event( "sister_sarcasm", , "tiff_quest == 2", "sister_at_home is True", 3 ),
                     Event( "second_date", "tiff_quest == 2" ) ]
Despite what my example show, this can really shorten the conditions, especially after a long time into the game.
The step 5 for the quest can need that TV_night, coffee_date and learn_massage are all True. This would have to be defined in the condition, and would now be replaced by tiff_quest == 5.

Plus, having them as two different condition can lower the number of events to test:
Python:
    class Event( renpy.python.RevertableObject ):
        [...]
        #  Will return True is the quest step is correct
        def questLevel( self ):
            return eval( self.quest)


    def nextEvent():

        #  Only events that are relevant for their /quest/ condition will be in the list.
        #  Still possibly need to reverse the list.
        for event in sorted( [ e for e in allEvents if e.questLevel ], key = lambda x: x.priority ):
            if event.triggered:
                allEvents.remove( event )
                return event.label
         return None