Dynamic events, and how to do it

I have this idea. The MC has a home base (Their apartment), that they return to often during the main story. There, many things can happen. The player can change their clothes, read letters, do some stat/story stuff, and some slice of life stuff.

But one of the most important things with the home base is that events can be triggered or started there. Having the player started events is easy. But I would like the triggered events to appear somewhat realistically and dynamically. And I am unsure how to do it well. Are lists or more advanced programming available with Choice Script?

Events will be most of the deep character/romance moments, but also some side-story stuff. Letters would need to be triggered similarly.

An example of an event might be that a character visits the main character’s house to hang out. Or a love confession. A small date. A mini-mystery. A character chews out MC for their earlier action. Stuff like that.

I think I will need:

All events have necessary constraints: (What scenes need to happen before, or constraints that makes the current scene inactive)

All events have priority levels: Numbers higher if the event/scene is important, time-sensitive, or interesting.

There needs to be something that allows this to be more dynamic… Does anyone have any ideas? Has an event system, or maybe a quest system, been implemented before in a COG?

(Imagine I have many events, and I want to have some control over how these should be triggered. The purpose of this system would be to have replayability and more dynamic relationships.)

5 Likes

A very simple method you can use is to generate an absolute trigger that overrides the default scene. I’m using this in the game I’m writing now. In my case it is based on ‘time’ (or ‘time remaining’).

My default scene is similar to yours, in that you select an option from several available, go and do some stuff, then you return back to this default scene. What you want to do is, if a particular trigger has fired, to mandate what happens next.

This is an excerpt from my (very rough) WiP

*if (Hour = "09") and (Min = "00")
    *goto_scene 6_arrival

*temp AddS

*set HoursRemaining 8 - Hour
*set MinsRemaining 60 - Min
*set AddS ""

*if (Hour = "07") and (Min = "00")
    *set HoursRemaining 2
    *set MinsRemaining 0
    *set AddS "s"
    
[b] ${Hour}:${Min} [/b]

You now have ${HoursRemaining} hour${AddS} and ${MinsRemaining} minutes until Thom and his friends pick you up to go camping. You packed your bags last night and they are by the door waiting. What would you like to do?

*if PhoneCharging = true
    Your phone has ${PhoneCharge}% charge.

*choice
    #Grab your phone and get caught up
        *goto_scene 3_1_phone

This portion of the game uses a clock (and associated ‘time remaining’) - each action takes time and the clock rolls on. At 09:00 I want to end this part of the game and move onto the next.

So the ‘default’ scene is everything under *temp AddS (there’s more than just that one option) - this will display over and over as the player selects an action, resolves it and returns to this scene. You can see an example here of them using their phone (there’s going to be 6 or so total possible actions).

The very first line is the override - if the time is 09:00 then we go to a different scene and the default scene is not played again. Of course, you can return back to this default scene at any time easily enough.

You might also be interested in how I control specific events being available:

*selectable_if ((Eaten = false) and ((MinsRemaining >= 5) or (HoursRemaining > 0))) #Go in search of food
        *goto_scene 3_2_food

When you select this option, then ‘Eaten’ is set to true - so you can’t select this option again. You also can’t select this option if less than 5 minutes are remaining (it’s 08:55).

To apply it to your game a bit more, you can use individual variables as flags. For example

*create DateWithDiana false

Here's some stuff the player did

#Here's some choices
    They pick this choice
    *Goto_scene default scene

Player picks an option in default scene
They meet Diana

#They ask Diana out
    *set DateWithDiana true
    goto default scene

Default scene has a trigger right at the top
*if DateWithDiana = true
    *goto_scene DianaDate

Default scene doesn't play
When the DianaDate scene plays, set DateWithDiana false
Then return to default scene and you'll display it as normal (unless another flag is triggered).

I’ve done a fair bit of thinking around this topic, so happy to delve into it deeper if you find this approach useful to explore.

A second method I have in mind for another game is to use a semi random process.

So at the start of the default scene you generate a random number, then compare that against a trigger number (e.g. generate number between 1 and 10, then *if number < 5 go and do something).

You can then use ‘wheels’ to really mix things up, for example:

Wheel 1 is for common events:

  • Random 1-50 (if <= 10 then do an event)

If wheel 1 misses (11-50), then spin wheel 2 - which is for adventures

  • Random 1-50 (if <= 8 then do adventure)

If wheel 2 misses (9 - 50), then spin wheel 3 - which is for romance

  • Random 1-50 (if <=12 then do romance)

If wheel 3 misses, you revert to the default scene.

You can then of course augment the randomness to ensure that certain things do happen, or increase in likelihood if they haven’t been seen in a while

Again, happy to explore in more detail if this sounds useful to you.

Edit: There’s some very simple stuff you can do if you just want a new option to appear only in certain circumstances (alongside all the other existing choices) - rather than redirecting to a whole new scene - I added an example of this to my first method.

7 Likes

Thank you for your detailed answer, Sinnie! :smiley:
I like your time method, it probably wouldn’t fit in my game, but I’m looking forward to seeing it used.
The random generator solution is interesting, but I had heard that the rand functions could cause problems? I wonder if a combination of (Goto random scene) + many constraints could achieve what I am looking for… Have you used that function? And if so, does it work well and can be customized with extra code beyond a scene list?

I think my question is whether any hidden choicescript tricks could help, and how to think around designing something like this.

Hi @Doriana-Grey. There are many approaches to achieve what you are trying to do. Random is a simple way, and it can be safe if you pay attention - mainly you need to do *random before a *page_break, or the value may change if you click on the stats. It also works with *goto.

@Nahim_Kerman discussed a problem similar to yours, here. You may use some of his ideas.
“For example. I envision this kind of code as a way for, at the beginning of each game, a set of scenes would be sorted. Each scene will an independent event that must occur just once in each session. My idea is that way you can add replay-ability to your game.”
Non repeatable strings (code example here)

There is some basic support of lists in CS, but it’s a little clunky. You can have a look here: https://choicescriptdev.fandom.com/wiki/Arrays or How to deal with arrays

Feel free to ask additional questions. Good luck on your game!

1 Like

It’s probably worth plotting out the types of conditions you want to use. Off the top of my head I can think of:

  1. Fixed boolean: You met this person, or you went to this location, or you found this item, etc.
  2. Value based trigger: You’ve collected x of this, you’ve been on 3 dates, you’ve done 5 missions
  3. Pure random: Something like I described above
  4. Moderated random: You use the random generator, but you augment it (boost or reduce odds of things happening, prevent the same thing happening twice, etc.)

From what you have said, I suspect you want a mix of all 4. The key thing is to keep it simple - you want to design a pattern which you can then slot any event chain into by creating the requisit variables.

1 and 2 above are functionally identical. You just track them in slightly different ways. Something like this:

DEFAULT SCENE

*if HasClue true
    *goto_scene InvestigateClue

*if TotalDates = 3
    *goto_scene BoatDate

*if (HasCane true) and (CrimeScenesVisited = 5)
    *goto_scene InspectorVisit

You arrive back at your apartment and consider your next move

#Check out the next crimscene
    *goto_scene Crimescene4

#Ring Diana
    *goto_scene DianaDate3

You’ll need more control variables I imagine, but this is the basis of fixed boolean and value based control

Now, how to add in randomness to either:

  1. Pick a random scene to override the default
  2. When an option is picked, pick a random scene from a pool (e.g. there’s 10 crime scenes, pick one)

I am literally creating this as I go, so let’s see what happens.

For 1, we can use this code:

*create Wheel1 0
*create Wheel2 0
*create Wheel3 0

*rand Wheel1 1 50
*if Wheel1 < 10
    Wheel 1 fires
    *finish

*rand Wheel2 1 50
*if Wheel2 < 5
    Wheel 2 fires
    *finish

*rand Wheel3 1 50
*if Wheel3 < 25
    Wheel 3 fires
    *finish

Default scene fires

You then insert what happens for each wheel. Now the problem is that the outcome is currently fixed.
How do we randomise the scene that is played?

Here is the straightforward method, which will grow and grow for every scene you have in the pool.

STARTUP:

*create Wheel1 0
*create Wheel2 0
*create Wheel3 0

*create Wheel1scene false
*create Wheel2scene false
*create Wheel3scene false
*create Wheel4scene false
*create Wheel5scene false

*rand Wheel1 1 50
*if Wheel1 < 40
    *gosub_scene wheel1
    *finish

*rand Wheel2 1 50
*if Wheel2 < 5
    Wheel 2 fires
    *finish

*rand Wheel3 1 50
*if Wheel3 < 25
    Wheel 3 fires
    *finish

Default scene fires

WHEEL 1:

*temp Wheel1
*rand Wheel1 1 5

*if (Wheel1 = 1) and (Wheel1scene = false)
    *set Wheel1scene = true
    *goto_scene wheel1scene

*if (Wheel1 = 2) and (Wheel2scene false)
    *set Wheel1scene = true
    *goto_scene wheel2scene

*if (Wheel1 = 3) and (Wheel3scene false)
    *set Wheel1scene = true
    *goto_scene wheel3scene

*if (Wheel1 = 4) and (Wheel4scene false)
    *set Wheel1scene = true
    *goto_scene wheel4scene

*if (Wheel1 = 5) and (Wheel5scene false)
    *set Wheel1scene = true
    *goto_scene wheel5scene

Again you’ll need more control variables in there I imagine - this is just the basic method.
You don’t need more than one ‘wheel’ - if you want all of your scenes to sit in the same pool.
The wheel method lets you set and augment the chance of different types of scenes occur and of course you can merge in the fixed boolean and value based conditions also.

As long as the subroutines have an exit clause/loop in case they hit scenes that can’t be played.

Following the link above, this is very similar to the solution suggested in there. Mine is very rough as I made it as I was typing - I have tested it though!

4 Likes

So this sounds like a storylet-based system. I really like this sort of design, which is why I prefer to use systems other than choicescript, because there are other frameworks that deal with storylets better. Dendry, for example, or TinyQBN in twine.

Among existing CS games, The War for the West does a similar thing. VtM: Night Road also has a hub area where you can go to different sub-areas. Among WiPs, I think The Vampire Regent has a similar design.

1 Like

Hello again!

Thanks everyone for your help, it gave me a lot to think about!
I have now created a prototype for an event system that I think might feel dynamic enough, but I still have some design questions.

I agree that events should probably have a category, (considering I am thinking of having 30-50 trigger-able events), but what I am unsure of is if it should focus on splitting these up into many scenes or if that is frowned upon? Or would it be better if it is as few as possible scenes, and then huge text files with many labels for scenes?

If I have categories, would it be interesting if it worked like a triangle system? Lets say the player has already read a character events, that makes the next event (if available) a story-event, and next a general-event (if available), and back to character events. Or would a random system work better?

And while I know some coding stuff I am a complete novice with ChoiceScript, so if any of this code is not allowed or can be done in a much simple way, please let me know! :slight_smile:

What my code does currently:
It takes in events made by “arrays”, takes their priority, gives out a list of highest priority events, remembers which event are what, and finally randomizes out one scene and sends the player there.
This could easily instead be separated into groups and then remembers the label-event-name if that is easier.

I thought this system would feel dynamic enough as there is an element of random, but clear enough with the priority system, allows for easy constraints. Perhaps on top of all this, I will add that priority levels are changed depending on character choices.

The code I have created could be used as an event/quest system, and although it does not yet have all the features I am planning, anyone is very welcome to use it! :slight_smile:

*the code will need, at this moment, five scenes called event_1, event_2, event_3, event_4, event_5, to have something to go to.

Code
*temp variableReachedChapterX
*set variableReachedChapterX true

*temp ePrio_1 8
*temp eTitle_1 "Event 1"
*temp eEvent_1 1
*temp eStateOn_1 false

*if (variableReachedChapterX = true)
	*set eStateOn_1 true

*temp ePrio_2 8
*temp eTitle_2 "Event 2"
*temp eEvent_2 2
*temp eStateOn_2 true


*temp ePrio_3 9
*temp eTitle_3 "Event 3"
*temp eEvent_3 3
*temp eStateOn_3 false

*temp ePrio_4 8
*temp eTitle_4 "Event 4"
*temp eEvent_4 4
*temp eStateOn_4 true

*temp ePrio_5 3
*temp eTitle_5 "Event 5"
*temp eEvent_5 5
*temp eStateOn_5 true


*temp priorityNumber 10
*temp NumberOfEvents 5

*temp i priorityNumber+1
*temp j 0
*temp z 0
*temp s 0


*temp p_1 0
*temp p_2 0
*temp p_3 0
*temp p_4 0
*temp p_5 0
*temp pAmount 5


*label loop1
*set i (i-1)
Priority ${i}

*goto loop2
*label loop12
*set j 0
*if (i < 2) 
	*goto testx
*goto loop1

*label loop2
*set j (j+1)
*if (eStateOn[j]) and (ePrio[j]=i)
	Adding event: ${eTitle[j]}, has priority ${ePrio[j]}
	*set s (s+1)
	*goto loop3

*label loop22
*set z 0

*if (j > (NumberOfEvents-1)) 
	*if (s>0)
		*goto randomChooseEvent
	*goto loop12
*goto loop2


*label loop3
*set z (z+1)
*set p[s] eEvent[j]
*if (z > (pAmount-1))
	*goto loop22
*goto loop3


*label randomChooseEvent

*temp randomEvent1 1
*rand randomEvent1 1 s

Test
${p_1}
${p_2}
${p_3}
${p_4}
${p_5}

*temp selectedScene p[randomEvent1]
envents in prio ${s}
rand ${randomEvent1}
selected scene ${selectedScene}

*goto_scene event[selectedScene]
*comment goes to scene "event_(number)" but could be remade to name of scene if better. 

*comment - I will change the prio number depending on choices
*comment - and if numbers are the same the events is randomized 

*comment - should there be different piles of events though?

Skärmavbild 2021-01-16 kl. 11.27.06

2 Likes

Yes, put three ``` before and after your code.

Alterantively, you can click the 'preformatted text' button in the editor
    it should be the sixth icon along
        and looks like this: </>

I tried parsing your code and it’s tricky without knowing which if statements go under which. I had a go at indenting but it isn’t coming out quite right.

Once you have it formatted up, I’ll take a proper look

1 Like

It should work and look right now! :smiley:

1 Like

You’ve tested my abstract thinking for a Saturday!
It’s a little hard to follow the code - there’s a lot of looping and iterating on arbitrary variables. Because you’ve named things i, s, j, z, etc. it’s difficult to follow what a line like ‘*if (z > (pAmount-1))’ is actually testing at any given time, without tracking back to how z was created and what its current value is and why.

I would advise, where you can, to name your variables with a practical name. It obviously helps testers and any interested parties to understand what is going on, but also, in 6 months when you revisit the code, you might find that you memory of exactly how each cog turns isn’t as good and you’ll be thankful for not having to piece it together by letter.

Something simple like this bit of code:

*set i (i-1)
Priority ${i}

Becomes more readable as:

*set CurrentPriority (CurrentPriority-1)
Priority ${CurrentPriority}

Throughout the code I can then intuitively see that all the if statements are comparing against the current priority level of the loop.

Having said that, I think I’ve cracked the flow of the code! My understanding of what the code does:

  • You define a set of events, each event has a priority level and it is either turned on or off
  • You start at the highest priority level
  • You see if there are any events with that priority level that are turned on
  • If there are, you ‘add’ it by setting p_x to the number of the event in question (1-5)
  • You then loop through all the events and check if they have the same priority level and are turned on. Only those that meet both criteria are added (to the next p_x variable)
  • If you cycle through all the events at a given priority level and none are turned on with that priority value, then you iterate to the next priority level
  • You only ‘add’ events of one priority level, so as soon as one event is added, you stop iterative on priority levels and move on to event selection
  • You then randomly pick one of the available scenes of that priority and goto that scene

Overall it looks really solid to me. My thoughts (and to answer your questions):

  • I would look to change the names of your scenes away from numbers. For the same reason as above - you’ll spend your life trying to find the right scene as you have to sift through 50 of them. I presume this is easy enough to achieve by recoding the selected scene to a text value from the number, so you pass the scene name in full to the goto command.
  • Your method can only play scenes of the same priority. Unless I am missing something, the priority system is redundant in your current design - you can never select a scene with a lower priority than the scene with the highest priority - so you may as well use your simpler system of turning scenes on and off. This all comes down to how you want your scene selector to work - if the idea is to have a series of flags to determine which scenes are eligible at any given moment and then select one, then your on/off flag is all you need. If you want to simply weight some scenes more highly than others, then I think you need to add all scenes in to the mix (providing they are turned on) and then give high priority scenes more tickets in the random lottery.
  • This ties into whether you have categories of events - it all comes down to what you want the system to do. It makes sense to give the player a diverse range of events (rather than randomly getting 3 romance scenes in a row). I see you suggested a triangle system - that makes it predictable (up to you if that’s good or bad). You can simply turn off all the events of a single type for x number of ‘spins’ to ensure that types don’t repeat.
  • in terms of the number of scenes (by which I assume you mean individual files) - I would always advise to split it up. It makes it much easier to find your way around the game, especially a code heavy one. But that’s the coder in me who likes to have modular, re-usable components. I don’t know if CS games are better to write in bigger chunks for any reason.

I’m not sure if any of that is useful to you in any way. I enjoyed unpicking your code and this post is really just me writing down what came into my head when I did it.

2 Likes

Yeah, that is what the code does! haha yes, had I known you were going to analyse the whole thing I would have tried to make the code easier to read!:sweat_smile:

About the priority: i am going to have a system that changes priority dynamically depending on if: you’ve recently met the character, or there is a relevance with an earlier recent moment. So while I understand what you are saying I would prefer to keep priority and constraints separate as they will both be complex in their own ways.

The scene names are just placeholders, everything is, kind of. But yeah, the scenes will have names on their genre probably, if I do that, and events will have an ID and title.

So you’re saying I could have up to 50 scenes (just for events)? I’m only asking because the few games codes I’ve looked at had very few scenes. Are maps for scenes a thing?

I’m just a big nerd that likes code! So don’t worry about me - I’m likely to use a similar system, so breaking yours down will just help me!

I’m not sure that the priority system does anything, as its currently implemented though. I think what you’re saying is that there’ll be system A that runs through a series of constraints and determines which scenes are valid and turns them ON (allowing them to be fed into the random selector). Then there is system B which applies some criteria to prioritise the events.

But as anything with a lower priority can never be selected, it is functionally identical to just turning it OFF. Whilst I appreciate that you might want to keep system A and B separate, they could both output to the same variable ( eStateOn_x). Then you can remove all the looping relating to the priority system. Of course, you may have additional functionality to come which will utilise the priority system - so I’ll shut up about it :slight_smile:

I also wondered about this loop. I couldn’t see what function it provided.
We pass in ‘j’ as the current value of the event that we have just ‘added’ (let’s say its scene 3).
z starts at 0.
Let’s say s is 2 (the second event we’re adding to the selector).

So we set p_2 = 3 - affirming that the second event we’re going to randomly choose from is scene 3.
Surely the loop just repeats that process over and over for the total number of possible scenes? It will never change the value of p_2 as we are not changing j (as we shouldn’t).

*label loop3
*set z (z+1)
*set p[s] eEvent[j]
*if (z > (pAmount-1))
	*goto loop22
*goto loop3

On the scenes - I also looked at another set of code and the files were huge.
In theory, if your story is mostly prose with some light choices in there that don’t do much (other than jumping to different lables), then there’s not much difference between one big file and lots of little ones. Other than you have to search in the large one for the bit you want to edit and that will take lots of time over the course of development.

For a code heavy game, you’re going to want to call subroutines and all sorts - it makes sense to split your files into logical partitions that mirror how the code works, I think. At least, that’s how I intend to approach it (I haven’t actually made a game yet).

2 Likes

:smile: :+1:

Yeah, I get what you are saying about the priority system, but I think with the future updates I am planning, the placement will make the most sense (for me). And if I playtest the event system later and it feels wrong/empty/non-varying, I could include differing amounts of priority with some changes.

I think I made loop 3 because the final list of scenes might in the end not be as many as the events—I’ll probably only have a fraction of that variable… because I am lazy. The chance of having more than, say, 15-20 events of the same priority is tiny (in my game).

Yeah, my main story won’t be very branching, that is created by what events the player unlocks mostly–so many scene-files might be better then, I personally would rather have it like that, but I hope CoG are okay with it if I later want to host it on their platform. Good luck with your game!

1 Like

Wow, that’s advanced stuff. A small suggestion: eEvent[j] is always equal to just “j”, you can probably drop eEvent and simplify. Also, make sure your randomtest and quicktest work well with your code. I have used *bug successfully to make sure my code didn’t break.

Finally, for your inspiration, this is a beautiful routine to sort priorities and similar: Seeking simple code to rank a series of stats

1 Like

Thanks! :blush: I haven’t tried tests yet, still very green with this, but hopefully I will soon.

Oooh, I thought I might need to create a sort system, really helpful thank you! :smiley:

1 Like