+1 vote
by (330 points)

I'm trying to build a game that I freely admit is way, way beyond my coding abilities, so some of my questions may get a bit ... complicated.  wink

I want to create a calendar for a sci-fi game that begins in August, 2027 and ends some time in May or June, 2028. I then need (or at least want) to be able to set some events to specific weekdays (e.g., on Thursdays the character has a meeting with his immediate superior), some to the number of days that have passed (on day 92 the player's home base is attacked, for instance), and some to specific dates (mostly holiday events: Hallowe'en, Thanksgiving, Christmas, Easter, etc). How would I do this? Is it even possible to do all four at once?

I'd also like to be able to divide each day into six unequal parts (early morning, morning, lunchtime, early afternoon, afternoon, evening, night) and could use some assistance with this as well.

Allow me to say before I get any answers that I've read the available posts on building a calendar and they are mostly incomprehensible to me. Hey, I said this project was way beyond my current (and possibly future) coding skills!  laugh

1 Answer

0 votes
by (8.6k points)

You're going to develop several systems here, and each if explained fully would break the answer length limit. The date part is actually the easiest and if you don't care for flexibility, you could implement it around this idea: First, define some global constants detailing your start date (1st August 2027) and time step (4 hours - one-sixth of a full day) in milliseconds in the init portion of the game. The time steps here are "constant time" even if they don't correspond to constant periods in the game for ease of calculation.

<<set setup.startDate = new Date("2027-08-01T00:00Z")>>
<<set setup.timeStep = 4 * 3600 * 1000>>

Also somewhere in there, define your starting point as a simple number of time steps passed. Let's say we start at $time = 17 (night of the 3rd August 2027).

<<set $time = 17>>

You need a few widgets or macros or code snippets to make do a few date manipulations now. Advance time is the easiest:

<<run $time ++>>

Advance to the next midnight (beginning of the day) simply means adding 6 to the time step, then removing the remainder after dividing by four.

<<set $time = $time + 6 - ($time % 6)>>

Advance to the next lunchtime (as an example) is slightly more complicated, but still rather easy - You change what your reminder calculation is

<<set $time = $time + 6 - (($time - 2) % 6)>>

Displaying the time is a two-step process. First you create a new Date object based on the setup.startDate one plus the timeStep multiplied by the amount of time passed (in $time), then you use whatever format you want to display it. In the example, I used toUTCString() for simplicity's sake.

<<set _curDate = new Date(setup.startDate.getTime() + setup.timeStep * $time)>>
<<= _curDate.toUTCString()>>

Of course, this displays the "actual" hours, which might not be what you want ("early morning" is displayed as 00:00, "morning" as 04:00 and so on). You could put the period names into a constant array and query those for a quick fix. In this case, the names go into the init section of your game (a note: You write about six time periods, but list seven; I trimmed it down to six):

<<set setup.timePeriods = ["early morning", "morning", "lunchtime", "afternoon", "evening", "night"]>>

The display code looks only slightly differently. You probably want to put it in some widget, too.

<<widget "time">>
<<set _curDate = new Date(setup.startDate.getTime() + setup.timeStep * $time)>>
<<= _curDate.toUTCString().replace(/ \d\d:\d\d:\d\d .*/, ", ")>><<= setup.timePeriods[$time % 6]>>
<</widget>>

Just use it as <<time>> from then on wherever you need it.

Checking which time of the day it is can be done with $time directly, again using the "reminder after division" operator. For example, if I wanted to see if it is currently morning ...

<<if ($time % 6) == 1>>
    ... it's morning!
<</if>>

For the other information, you'll need to create a _curDate object as above, then use its methods.

  • The day of the week can be queried with _curDate.getUTCDay() (0 = Sunday, 1 = Monday, ..., 6 = Saturday).
  • The day of the month is queried with _curDate.getUTCDate().
  • The month is queried with (_curDate.getUTCMonth() + 1) (months are 0-based, not 1-based in JavaScript)
  • The year is queried with _curDate.getUTCFullYear()

For the calculation of the "moving" holidays like Easter, you will either need to pre-calculate when it falls in 2028 (it's 16th April, by the way) and hard-code it, or if you want a more flexible system implement your own calculation. Same for things like moon phases. Others can be more easily hardcoded based on the day-of-the-year, though keep in mind that 2028 is a leap year.

Event handling is a whole topic for itself - not to mention, it's very game-specific. Dividing the day into a number that's not a clean divisor of 86400 (seconds in day), like seven, is slightly more complicated for the _curDate calculation, but thankfully not particularly hard. There's also some optimisation possible, like creating a setup.curDate read-only attribute which would calculate it all automatically, but that might be premature and could lead to unmaintainable by you code.

...