Ren'Py Have an idea for a time system, don't know how to write the code

Fancy Dan

New Member
Aug 2, 2018
13
4
Hi, absolute beginner to coding here, learning from scratch and would appreciate some input :)

I've been reading a lot about different time system approaches and studied the code for a few games. I know how I want to handle it, just not sure how the code would go.

What I want to do is have the days of the week correspond to numbers 1-7 (or 0-6 if that's better?). I.e. something like day 1 = Monday for future display purposes. I can probably do this with define or if statements, but is there a more direct solution? Like saying 1-7 = monday, tuesday etc.

I want each day to have 3 phases corresponding to numbers 1-3 (or 0-2 if it's better). Like time 1 = Morning, time 2 = Afternoon, time 3 = Evening. Same question as above.

The way I imagine this working is that I would trigger a +1 to the time count after each event or with a time skip button. Meaning I'd put something like $ time += 1 after every event that's supposed to advance time.

Now comes what's probably the tricky part: How can I make it so that the max possible number for time is 3 and if it passes the number it loops back to 1? Plus, how do I make it so that each time it loops back it adds +1 to day? (I'm assuming I can use the same method to loop the days when they reach a max value of 7. )

P. S. I plan to trigger events at locations based on if/else checks related to time and day values.
 

mickydoo

Fudged it again.
Game Developer
Jan 5, 2018
2,446
3,547
One way (simplest)

Python:
default phase = 0

label time_system:
    $ phase += 1
    if phase >= 3:
        $ phase = 0
return

In your script -

Player gets up
call time_system

Player has lunch
call time_system

Player faps before bed
call time_system
 
Apr 24, 2020
192
257
You're thinking of the modulus operation as it gives you the remainder of a division.

Here's my take on it:
Python:
default timekeeper = Timekeeper()

class Timekeeper(object):
    def __init__(self):
        self.day = 0
        self.phase = 0
        
        self.daysPerWeek = 7
        self.phasesPerDay = 3
        
    def ProgressTime(self, amount = 1):
        self.phase += amount
        
        self.day += self.phase // self.phasesPerDay
        self.phase %= self.phasesPerDay
        self.day %= self.daysPerWeek
You can then increase time by using timekeeper.ProgressTime(). It will progress time by one phase if the brackets are left empty, otherwise it will increase by the amount you add.
You can get the current day or phase with timekeeper.day and timekeeper.phase respectively.

Do note that the loops will not start at 1, but at 0. This means monday is day 0 and morning is phase 0. You can always just add 1, when reading the values, to make them conform to existing code, however I recommend just leaving them at 0 as it might make some future math easier.
 

Fancy Dan

New Member
Aug 2, 2018
13
4
Thanks guys, this is exactly what I needed! You're awesome :)

I'll start with mickydoo's suggestion because it's something I can understand at my current level. InCompleteController and 79flavors, both those solutions seem much more elegant, but a little advanced for me. If something happens, I don't think I'd be able to debug or adapt anything without breaking the code. I'll keep them in mind for the future if this one isn't enough - thanks again!! :D
 

SiddyJJ

Newbie
Apr 3, 2021
24
15
Thanks for this guys. I need a time/date thing in my game too and the examples here are better than what I came up with!
 
  • Like
Reactions: mickydoo

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,131
14,812
To complete the list of possibilities, a pure Ren'py version:

Code:
define periodsOfDay = [ "Morning", "Afternoon", "Evening" ]
define daysOfWeek = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ]

default period = 0
default day = 0
default periodAsString = periodsOfDay[period]
default dayAsString =daysOfWeek[day]

label advancePeriod:
    $ period += 1
    if period ==3:
        jump advanceDay
    jump timeInString

label advanceDay:
    $ day += 1
    $ day %= 7
    $ period = 0
    jump timeInString

label timeInString:
    $ periodAsString = periodsOfDay[period]
    $ dayAsString = daysOfWeek[day]
    return

label start:
    "You spend some time talking with her."
    call advanceStep
    # Period is now "Afternoon"
    "Time for a ballad in the park."
    call advanceStep
    # Period is now "Evening" 
    "You go to sleep early."
    call advanceDay
    "We are [day] [period], what will you do today ?"
 
Last edited:
  • Like
Reactions: hakarlman

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,559
2,175
My current iteration on this theme actually uses the inbuilt date and time functions from python. With a bit of tweaking to make it easier for devs who aren't going to want to go down to that level.

The thought was pretty much "why am I doing it manually, when python already has all this covered?"

Python:
init python:
    import datetime

init python:

    def advance_period():

        set_game_hour(hour=periods[store.period])
        set_time_fields()


    def goto_next_morning():

        set_game_hour(hour=periods[0])
        set_time_fields()


    def set_game_hour(hour):

        if hour > store.gamedate.hour:                         # changing hour to today or tomorrow?
            store.gamedate = store.gamedate.replace(hour=hour)
        else:
            store.gamedate = store.gamedate.replace(hour=hour)
            store.gamedate = store.gamedate + datetime.timedelta(days=1)


    def set_time_fields():

        if store.gamedate.hour >= periods[0] and store.gamedate.hour < periods[1]:
            store.period = 1
        elif store.gamedate.hour >= periods[1] and store.gamedate.hour < periods[2]:
            store.period = 2
        elif store.gamedate.hour >= periods[2] and store.gamedate.hour < periods[3]:
            store.period = 3
        else:
            store.period = 4

        store.hour = int(store.gamedate.hour)                   # 0 to 23
        store.day = int(store.gamedate.day)                     # 1 to 31
        store.weekday = int(store.gamedate.weekday())           # Mon=0, Sun=6
        store.dayname = store.gamedate.strftime("%a").upper()   # MON, TUE, etc.

define periods = [8, 13, 18, 23, 8]     # The 5th time period is there to wrap around to "tomorrow".
default gamedate = datetime.datetime(2020, 03, 06, 8, 0, 0)  # start at 8am on Friday 6th March 2020. The period will be calculated based on the current hour.

default period = 0    # 1 = morning, 2 = afternoon, 3 = evening, 4 = night
default hour = 0
default day = 0
default weekday = 0
default dayname = ""

label start:

    $ set_time_fields()

# later...

    if something == True:
        $ advance_period()
    else:
        $ goto_next_morning()

I've currently only got 3 functions for altering the game date/time, then a 4th (set_time_fields()) which keeps
all the simple variables updated to match the value stored in gamedate.

The end result is that the developer can simply access period or hour or day or dayname without worrying how they are kept up to date or learning the python functions needed.

Meanwhile, for a more experienced developer, the system is flexible enough to allow for other functions like advance_by_hour() or even advance_by_minute - since the underlying datetime held in the gamedate variable is microsecond accurate and advancing time using the underlying python functions will honor things like leap years, etc.

In effect, an inexperienced dev can treat it like a black box and use it as is, whereas a more experience dev can use it as a starting point to do almost anything.

Realistically, I wrote this as a proof-of-concept idea - it probably needs expanding a little to really become useful. But for anyone who can follow the code as is... perhaps it can inspire you to do better.
 
  • Like
Reactions: hakarlman

kin-kun

Active Member
Modder
Jul 10, 2020
963
2,286
The thought was pretty much "why am I doing it manually, when python already has all this covered?"
Just stopped by to mention this is a very important lesson.

Most people will layer code on top of code and then hack at it till it works. Well, sort of works. Most of the time.

Then they spend hours/days/weeks trying to find all the corner cases where things go wrong.

Instead a minimalist approach means you don't have to maintain that code over time. It is a very important to learn this early on.

That said, if you're working with time, not just dates, I suggest you try to keep in UTC to avoid DST issues. Leaving the timezone unset can lead to undesirable, inconsistent behavior depending on the user setup.
 
Apr 24, 2020
192
257
To complete the list of possibilities, a pure Ren'py version:
I'm not sure if I'm seeing this correctly, but why use mod 6 on days? I assume that the index number for day 7 was on your mind at that point.

The thought was pretty much "why am I doing it manually, when python already has all this covered?"
Yeah, I started that way as well. However, I found there was far too many special case scenarios that I had to account for to make things work.
 
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,131
14,812
I'm not sure if I'm seeing this correctly, but why use mod 6 on days? I assume that the index number for day 7 was on your mind at that point.
Yeah :( I was probably thinking something like "the max value will be 6", and stupidly wrote this 6.

Thanks, edited the code to correct this.
 

Bummenphist

Member
Jul 21, 2018
184
312
My current iteration on this theme actually uses the inbuilt date and time functions from python. With a bit of tweaking to make it easier for devs who aren't going to want to go down to that level.

The thought was pretty much "why am I doing it manually, when python already has all this covered?"

Python:
init python:
    import datetime

init python:

    def advance_period():

        set_game_hour(hour=periods[store.period])
        set_time_fields()


    def goto_next_morning():

        set_game_hour(hour=periods[0])
        set_time_fields()


    def set_game_hour(hour):

        if hour > store.gamedate.hour:                         # changing hour to today or tomorrow?
            store.gamedate = store.gamedate.replace(hour=hour)
        else:
            store.gamedate = store.gamedate.replace(hour=hour)
            store.gamedate = store.gamedate + datetime.timedelta(days=1)


    def set_time_fields():

        if store.gamedate.hour >= periods[0] and store.gamedate.hour < periods[1]:
            store.period = 1
        elif store.gamedate.hour >= periods[1] and store.gamedate.hour < periods[2]:
            store.period = 2
        elif store.gamedate.hour >= periods[2] and store.gamedate.hour < periods[3]:
            store.period = 3
        else:
            store.period = 4

        store.hour = int(store.gamedate.hour)                   # 0 to 23
        store.day = int(store.gamedate.day)                     # 1 to 31
        store.weekday = int(store.gamedate.weekday())           # Mon=0, Sun=6
        store.dayname = store.gamedate.strftime("%a").upper()   # MON, TUE, etc.

define periods = [8, 13, 18, 23, 8]     # The 5th time period is there to wrap around to "tomorrow".
default gamedate = datetime.datetime(2020, 03, 06, 8, 0, 0)  # start at 8am on Friday 6th March 2020. The period will be calculated based on the current hour.

default period = 0    # 1 = morning, 2 = afternoon, 3 = evening, 4 = night
default hour = 0
default day = 0
default weekday = 0
default dayname = ""

label start:

    $ set_time_fields()

# later...

    if something == True:
        $ advance_period()
    else:
        $ goto_next_morning()

I've currently only got 3 functions for altering the game date/time, then a 4th (set_time_fields()) which keeps
all the simple variables updated to match the value stored in gamedate.

The end result is that the developer can simply access period or hour or day or dayname without worrying how they are kept up to date or learning the python functions needed.

Meanwhile, for a more experienced developer, the system is flexible enough to allow for other functions like advance_by_hour() or even advance_by_minute - since the underlying datetime held in the gamedate variable is microsecond accurate and advancing time using the underlying python functions will honor things like leap years, etc.

In effect, an inexperienced dev can treat it like a black box and use it as is, whereas a more experience dev can use it as a starting point to do almost anything.

Realistically, I wrote this as a proof-of-concept idea - it probably needs expanding a little to really become useful. But for anyone who can follow the code as is... perhaps it can inspire you to do better.
Ok, so I copy all of this code into a file timedate.rpy except for the bits after label start, which goes into thehead of my script.rpy. From another coder I managed to make a screen that will show stats, so I add this to it:

screen gameUI:
text "$[gold]" yalign 0.005 xalign 0.01 size 27
text "hour [hour]:" yalign 0.005 xalign 0.10 size 27
text "period [period]:" yalign 0.005 xalign 0.20 size 27
text "day [day]:" yalign 0.005 xalign 0.32 size 27
text "dayname [dayname]:" yalign 0.005 xalign 0.44 size 27
But it only displays the code as 0
but what do I need to do to your code in order to get the time and days to show up?
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,559
2,175
Ok, so I copy all of this code into a file timedate.rpy except for the bits after label start, [...]

But it only displays the code as 0

If I had to guess, you haven't included the line $ set_time_fields() before you've displayed your screen.

That function loads all the data from the inbuilt time functions into more "user friendly" variables. If you didn't run it, then the values will still be their defaults (zero).
You only need to do it once, soon after the label start:, as all the other functions I wrote to do stuff like increment time, etc. each invoke it anytime they change the underlying values to refresh the user friendly copies.

I will however say that this is just one method of handling time - and honestly, probably one of the more over-engineered variants. If it works for you and you understand how it's doing stuff... great. If not, there are other more simplified versions I've posted from time to time here on F95. None are perfect or even complete. Each is intended to be a starting point, not a complete and total solution (though perhaps I should write one of those eventually).
 

Bummenphist

Member
Jul 21, 2018
184
312
If I had to guess, you haven't included the line $ set_time_fields() before you've displayed your screen.

That function loads all the data from the inbuilt time functions into more "user friendly" variables. If you didn't run it, then the values will still be their defaults (zero).
You only need to do it once, soon after the label start:, as all the other functions I wrote to do stuff like increment time, etc. each invoke it anytime they change the underlying values to refresh the user friendly copies.

I will however say that this is just one method of handling time - and honestly, probably one of the more over-engineered variants. If it works for you and you understand how it's doing stuff... great. If not, there are other more simplified versions I've posted from time to time here on F95. None are perfect or even complete. Each is intended to be a starting point, not a complete and total solution (though perhaps I should write one of those eventually).
It probably is too complex for me at this stage, but it is displaying correctly now that I moved it into the right spot. I'm going to bed now, but I'll be back to check the

if something == True:
$ advance_period()
else:
$ goto_next_morning()
code. Thanks for your help so far.
 

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
811
1,806
It probably is too complex for me at this stage, but it is displaying correctly now that I moved it into the right spot. I'm going to bed now, but I'll be back to check the

if something == True:
$ advance_period()
else:
$ goto_next_morning()
code. Thanks for your help so far.
If you want a really easy to follow tutorial on use lists in Ren'py to make calendar and time loops, I would suggest watching this guy. All his videos are easy to follow along and they were some of the first lessons I did to get started in this, especially since I had no Python experience.

 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,559
2,175
[...] but I'll be back to check the

if something == True:

Yeah. That's not really likely how it would be used. I just wanted to highlight that you could do either.

More realistically, you'd probably use $ advance_period() at the end of an event. Or at a common point that is invoked at the end of each event. Some like... "visit the mall" -> "advance time". "eat a meal" -> "advance time". That sort of thing.
$ goto_next_morning() is almost certainly going to be linked to some sort of [SLEEP] button or similar mechanic.