TimeSys
User’s Guide
TimeSys is a collection of interrelated modules
that provide a mechanism for time-based game play, as opposed to the usual
turn-based system.
Thanks
to Michael J. Roberts for his many suggestions.
Copyright
© 2001-2004 by Kevin Forchione.
All rights reserved.
Contents
TimePiece
Properties Associated With Tracking
TimePiece
Properties Associated With Actions
TimePiece
Properties Associated With Events
Calendar
Properties Associated With Tracking
Clock
Properties Associated With Tracking
Clock
Properties Associated With Actions
Clock
Properties Associated With Events
Accessing
the Global WallClock Instance
Setting
the Date and Time Zero-point
Ways
of Issuing the stopWaiting() Message
Fuses
and Daemons Defined in Events
Special
Case Daemon Defined in Events
Special
Case Daemons Defined in Miscellaneous.
TimeSys adopts a modular approach that allows you
to tailor the degree of Time-oriented game play you desire in your game.
Specifically, there are the following levels of implementation:
The tracking modules
facilitate the functions necessary for time-oriented display in game play. Time-oriented status display will show the
passage of time in date & time terms as the player moves about the game
world.
To implement tracking you
will need to add the following modules to your game compilation after those for
the TADS 3 system and library files:
Also #include
the following header file to each of your game source files that need to
reference TimeSys functions:
After compiling and
running you should see something like the following in your game’s status line:
By default the “Wallclock” time is set to Friday, June 17, 1887
As
the PC moves around the game world time will progress in minute increments
based on the actionTime set for each action:
You
can also issue the <<time>> command, a system command that will display
the time in the game window:
The actions modules
facilitate the functions necessary for time-oriented waiting in game play. Time-oriented status display will show the
passage of time in date & time terms as the player moves about the game
world. In addition wait commands can be issued by the PC to any actor
(including itself).
To implement actions you
will need to add the following modules to your game compilation after those for
the TADS 3 system and library files:
Also #include
the following header file to each of your game source files that need to
reference TimeSys functions:
After compiling and
running you should be able to issue wait commands to either the PC or an NPC:
You
can issue many forms of wait commands such as:
>wait 5
>wait 7 minutes
>wait 1 hour
>wait 2 hours and 10 minutes
>wait until
>wait until
>wait until
The
actions will be processed and the display updated to reflect the new game time:
You
can also issue wait commands to an NPC. The command
will be executed
The
wait command is issued asynchronously to an NPC. In
this case 1 minute of time is consumed by the issue of the action command by
the PC, while Joe will wait 5 minutes.
The events modules
facilitate the functions necessary for time-oriented events in game play. Time-oriented status display will show the
passage of time in date & time terms as the player moves about the game
world and wait commands can be issued by the PC to any actor (including itself). In addition, fuses and daemons can be triggered in
a time-oriented manner.
To implement events you
will need to add the following modules to your game compilation after those for
the TADS 3 system and library files:
Also #include
the following header file to each of your game source files that need to
reference TimeSys functions:
After compiling and
running you should be able to implement and execute time-oriented fuses and
daemons:
By default the “Wallclock” time is set to Friday, June 17, 1887
TimeSys implements a variety of time-oriented fuses and
daemons. Like their TADS 3 ADV3 counterparts, these have sensory and
non-sensory versions. The chief difference is that they are implemented based
on dates and times and the game wallclock, rather
than turns.
For instance, here is the fuse
for the fire alarm displayed below:
new AlarmFuse(1887, 6, 17, 5, 10, 0);
TimeSys fuses and daemons have the ability to issue
continuous action interrupts and interrupt queries. The time the query is
issued is displayed in the status line.
If the player character
chooses to stop waiting
the query itself does not advance the game time.
The miscellaneous modules
facilitate the functions necessary for sine specialized time-oriented events in game play. Time-oriented status
display will show the passage of time in date & time terms as the player
moves about the game world and wait commands can be issued by the PC to any
actor (including itself). Fuses and daemons can be
triggered in a time-oriented manner, and the miscellaneous modules implement
specialized applications of TimeSys events.
To implement events you
will need to add the following modules to your game compilation after those for
the TADS 3 system and library files:
OR
Also #include
the following header file to each of your game source files that need to
reference TimeSys functions:
After compiling and
running you should be able to implement and execute the specialized
time-oriented events:
The TimeSysBigBenDaemon
and TimeSysSunDaemon handle specialized hour
announcement and daylight function activities.
Among there specialized
behaviors, the Big Ben daemon will summarize its hourly announcement when the
player character’s waiting period spans two or more hours. When this happens the
Big Ben daemon doesn’t query the actor for a waiting interrupt. By default the
Sun daemon does query the actor for a waiting interrupt at the start of each of
its event ranges, if the player character can see the sun.
When the player character
is waiting the Sun daemon generates two messages if the player can see the sun.
The first message is a waiting message, “While you were waiting…” and the
second is the normal, non-delayed event message, “The sun comes up…”. Also the Sun daemon controls the lighting of each and
every OutdoorRoom class location.
The Sun daemon “buffers”
its responses when the player character cannot see the sun, so that it won’t
announce its events if the player character cannot see the sun, but will announce
its events at the point at which the player character can see the sun. For
example:
Here the player character
is in the kitchen, but the sun is only visible in the backyard. . .
Four hours have passed. The
sun has come up and lit the backyard, but we don’t receive any announcement of
it until we go out into the backyard.
With delayed announcements, the Sun daemon will announce any of its events as long as soon as the actor can see the sun and anytime after the event has occurred until the next event occurs. At that point the daemon will display a “You notice…” message.
TimeSys employs very sophisticated algorithms to represent
not only the time of day, but the year, month, day, and day of the week. The TimePiece class provides an interface between the TADS 3 ADV3
Schedulable class’ gameClockTime (measured in sequential “turns”) and the Calendar, and Clock class objects that translate turns into the
appropriate calendar date and time values.
The process is
straightforward. First a zero-point is established. The zero-point is the
date/time value we give to the Schedulable game clock time value 0, which represents the start
of the story.
As the story progresses the
adv3 library automatically increments the Schedulable game clock, and TimeSys immediately adjusts the time of every instance of TimePiece to reflect the new date and time values. Every TimePiece is thus synchronized with the Schedulable game clock, though their zero-points and rate of time passage may
differ.
When a command to “wait” is
issued, the amount of time specified is converted into Schedulable game clock time units, and the actor who is to do the waiting has a
new WaitAction queued to his command queue until the waiting
period has passed or is interrupted.
When a TimeEventDaemon or TimeEventFuse is set, the amount of time specified in year,
month, day, hour, minute, second, is also converted into Schedulable game clock time units, and the daemon or fuse then proceeds to
behave like any normal turn-based daemon
or fuse.
The TimePiece
class is responsible for translating between the Schedulable game clock and its
calendar and clock delegates.
The TimePiece class defines the following methods:
setInitDateTime(calendarParms, clockParms, [rate]) – sets the initial datetime BigNumber value, the zero-point of this timepiece, and the
rate at which time passes per game turn.
The calendarParms
argument consists of a list [calendar, year, month, day]
The clockParms
argument consists of a list [clock, hh, mm, ss]
The rate argument, if
provided, should be a single integer or Bignumber
value.
getInitJdn() – returns the Julian Day Number of this timepiece’s
zero-point.
getInitClockRatio() – returns the clock ratio
time-in-seconds/seconds-per-day of this timepiece’s zero-point.
getInitDateTime() – returns the datetime BigNumber value of this timepiece’s zero-point.
getInitDateTimeString() – returns a formatted string value of this
timepiece’s initial datetime value.
setCurrDateTime(gctu) – sets the current datetime
BigNumber value of this timepiece representing the
game-clock-time-units passed.
getCurrJdn() - returns the Julian Day Number of this timepiece’s
current datetime value.
getCurrClockRatio() – returns the clock ratio
time-in-seconds/seconds-per-day of this timepiece’s current datetime
value.
getCurrDateTime() – returns the datetime BigNumber value of this timepiece’s current datetime value.
getCurrDateTimeString() – returns a formatted string value of this
timepiece’s current datetime value.
setCalendar(calendar) – sets this timepiece’s internal calendar
conversion pointer to one of the Calendar class objects.
setClock(clock) – sets this timepiece’s internal clock conversion
pointer to one of the Clock class objects.
setTimeRate(rate) – sets the ratio of minutes per
game-clock-time-unit at which time passes in the game for this timepiece. If
the rate has a fractional element, then time progresses in fractions of minutes
per game-clock-time-unit. If the rate is negative then time goes backward in
the game.
setDateVals(jdn) – sets this timepiece’s internal year, month, and day values
associated with the Julian Day Number passed as calculated by the timepiece’s
calendar conversion object.
setDayOfWeek(jdn) – sets this timepiece’s internal day-of-the-week value associated with
the Julian Day Number passed as calculated by the timepiece’s calendar
conversion object.
setTimeVals(clockRatio) – set this timepiece’s internal hour, minute, and second values
associated with the clock ratio passed as calculated by the timepiece’s clock
conversion object.
getCalendar() – returns this timepiece’s calendar conversion
object.
getClock() – returns this timepiece’s clock conversion object.
getTimeRate() – returns this timepiece’s minutes per
game-clock-unit BigNumber value.
getDateTime(year,
month, day, hour, minute, second) –
returns the datetime value computed from this
timepiece’s calendar and clock conversion of the passed date and time
arguments.
getDateVals(jdn) – returns a list [year, month, day] associated with
the Julian Day Number as calculated by this timepiece’s calendar conversion
object.
getDayOfWeek(jdn) – returns the day-of-week numeric value associated
with the Julian Day Number as calculated by this timepiece’s calendar
conversion object.
getTimeVals(clockRatio) – returns a list [hour, minute, second] associated
with the clock ratio as calculated by this timepiece’s clock conversion object.
cvtMinToGctu(minutes) – converts minutes to game-clock-time-units.
cvtGctuToMin(gctu) – converts game-clock-time-units to minutes.
cvtValsToDateTime(days,
hours, minutes, seconds, [actionTime]) – Returns the corresponding datetime
value for the days, hours, minutes, seconds passed. If passed an Action's actionTime value then this will be converted to seconds and
added to the datetime computation.
addToTime(days, hours, minutes, seconds) – Creates a clone of the timepiece then adds this
amount to the clone’s initial datetime value and recomputes all values from this new zero-point. This method
makes adjustments for any Action actionTime value. If
we add 5 minutes to a timepiece’s clock time, this method will adjust for any
action time affecting the Scheduler game clock time, so that exactly 5 minutes
will be added. Returns the clone.
subtractFromTime(days, hours, minutes, seconds) – Creates a clone of the timepiece then subtracts
this amount from the clone’s initial datetime value
and recomputes all values from this new zero-point.
This method makes adjustments for any Action actionTime
value. If we subtract 5 minutes from a timepiece’s clock time, this method will
adjust for any action time affecting the Scheduler game clock time, so that
exactly 5 minutes will be subtracted. Returns the clone.
addDateTimeToTime(dt) – Creates a clone of this timepiece then adds the datetime argument to the clone's initial datetime value (zero-point). Returns the
clone.
subtractDateTimeFromTime(dt) – Creates a clone of this timepiece then subtracts
the datetime argument from the clone's initial datetime value (zero-point). Returns the
clone.
getInitDateTimeVals() – Returns the timepiece's initial date / time value
list.
getCurrDateTimeVals() – Returns the timepiece's current date / time value
list.
getDateTimeVals(jdn, clockRatio) – Returns
the timepiece's date / time value list for the given julian
day number and clock ratio.
cvtDtToGctu(beginDt, endDt) – Returns
the game clock time unit associated with
the difference between a begining and ending datetime
The Calendar class serves
as means of converting between various date representations and Julian Day
Numbers. The Calendar class is further subclassed
into GregorianCalendar and JulianCalendar.
The class defines the
following methods:
toDayOfWeek(jdn)
– returns the day of the week as a BigNumber value
between 1 and 7 inclusive where Sunday = 1, Monday = 2, etc.
toDate(jdn)
– converts the Julian Day Number to the corresponding calendar-specific date in
a list [year, month, day].
toJulianDayNumber(year,
month, day) – converts the calendar-specific date to the corresponding Julian Day
Number.
toDateTime(year,
month, day) – returns a datetime value for the
calendar-specific date.
The Clock class serves as
means of converting between various clock time representations and clock
ratios. The Clock class is not currently subclassed.
The class defines the
following methods:
toTime(clockRatio) – converts
the clock ratio to the corresponding clock-specific time in a list [hour,
minute, second].
toClockRatio(hour,
minute, second) – converts the clock-specific time to the
corresponding clock ratio.
toDateTime(hour,
minute, second) – returns a datetime
value for the clock-specific time.
compareRatios(startClockRatio, endClockRatio) – compares the two clock
ratios. The comparison rounds both clock ratios to 5 decimal places.
If startClockRatio > endClockRatio returns
-1
If startClockRatio == endClockRatio returns
0
If startClockRatio < endClockRatio returns
1
toSeconds(dt)
– converts the datetime value to seconds.
toMinutes(dt)
– converts the datetime value to minutes. All values
are rounded to the nearest minute.
toHours(dt) – converts the datetime value
to hours. All values are rounded to the nearest hour.
secondsToNxtClockRatio(startClockRatio, endClockRatio) – computes the seconds
between 2 clock ratios. If startClockRatio == endClockRatio then secondsPerDay
value is returned.
minutesToNxtClockRatio(startClockRatio, endClockRatio) – computes the minutes
between 2 clock ratios. If startClockRatio == endClockRatio then secondsPerDay
value converted to minutes is returned. All values are rounded to the nearest
minute.
hoursToNxtClockRatio(startClockRatio, endClockRatio)
– computes the hours between 2 clock ratios. If startClockRatio
== endClockRatio then secondsPerDay
value converted to hours is returned. All values are rounded to the nearest
hour.
The WallClock
class serves as the TimePiece class object that
produces the status line display’s date and time values. It is the game’s clock and is meant to govern
the functioning of daemons, fuses, and other game events.
During the
pre-initialization stage of program compilation, an instance of WallClock class is stashed in libGlobal.wallClock, and can then be accessed through the gWallClock macro.
During preinit,
the token synchronizer adds a new tokenizer rule to
the ADV3 library’s cmdTokenizer.rules_.
The new token rule allows the tokenizer to handle
time-oriented phrases such as “
The base date and time are
stored in the WallClock instance in the form of a
date-time BigNumber instance. This serves as our
zero-point from which time progresses. The base date and time are
calendar-dependent.
Currently TimeSys supports both the Gregorian
and Julian calendars.
An author can set the zero-point
date and time by using the class’ setInitDateTime() method, or by setting the classes initDate_ and initTime_ properties. These properties are lists in the format:
initDate_ [calendar,
yyyy, mm, dd]
initTime_ [clock,
hh, mm, dd]
The calendar can be either GregorianCalendar or JulianCalendar. The clock
can be Clock. The hh value should be set with 24-hour clock values, with
The time rate is the rate at
which time passes per game clock time unit and is measured in minutes. By
default the time rate is 1 minute per game clock time unit (turns). The time
rate can be modified using the setTimeRate() method, or by overriding the initRate_ property of WallClock.
The following would create
a wallClock with an initial date of January 1, 2000,
Modify WallClock
{
initDate_ = [GregorianCalendar,
2000, 1, 1]
initTime_ = [Clock, 14, 0, 0]
initRate_ = 1
}
The base date and time can
be changed at any time by sending gWallClock the
message setInitDateTime([calendar, yyyy, mm, dd], [clock, hh, mm], [rate]).
Currently the only calendars supported are GregorianCalendar and JulianCalendar. The only clocks supported are Clock.
It would be a poor system
indeed that simply translates turns into date and time values. The Infocom
games that implemented time-based systems also allowed players to wait for specified
intervals of time. TimeSys does the same. The system
can handle commands such as:
>wait 5
>wait until
>wait until
>wait for 3
hours and 10 minutes
>wait until
>wait until
Such commands given to NPCs have the effect of putting them into wait states that
can be observed:
Entryway
This large, formal entryway is slightly
intimidating: the walls are lined with somber portraits of gray-haired men from
decades past; a medieval suit of armor, posed with a battle axe at the ready,
towers over a single straight-backed wooden chair. The front door leads back
outside to the south. A hallway leads north.
Joe is here.
>joe, wait 5
Joe waits...
>x joe
Joe is waiting.
>wait 10
Time passes...
>x joe
You see nothing unusual about Joe.
>
So Joe can be commanded to
wait, while the PC goes about his business.
The library defines a WaitingActorState that
dictates the behaviors of waiting actors. Whenever an actor obeys a command to
wait it enters this waiting state and remains in that state until waiting is
completed or terminated.
The waiting state for any
actor can be terminated by issuing the message stopWaiting(issuingActor, queryActor) to any actor. The queryActor
argument applies only to the PC, and if it is true then the request “Do you
want to continue waiting?” is issued, otherwise the waiting is terminated if
the actor allows itself to be interrupted.
Besides inserting this
message into action code, an author can also make use of specialized fuses and
daemons. Time Fuses and daemons are date/time based.
new DateTimeFuse(obj, prop, year,
month, day, hour, minute, second, true);
So, in order to create a
time event fuse set to stop me from waiting, and set for January 1st,
2002 at
new DateTimeFuse(me,
&alertStopWaiting, 2002, 1, 1, 17, 0, 0, nil);
And then code the method alertStopWaiting()
as follows:
modify me
{
alertStopWaiting()
{
stopWaiting(me, true);
}
}
Various time-based fuses
and daemons are provided in ts_events.t
TimeSys provides methods on Actor to enable an actor
to react to each phase of the waiting process.
execActionInitial(action) {}
execAction(action) {}
execActionCompleted(action) {}
execActionInterrupted(issuingActor, action) {}
Thus a message can be sent
to the actor for the first execution of a wait state, repeated executions, the
final execution, or an interrupted execution. The action involved is sent as
well, in order to differentiate between waiting and some other continuous
action.
TimeSys provides several time-oriented daemons and fuses
that allow the author to orchestrate time-oriented events.
These fuses and daemons are
specially designed to work with continuous actions and allow for the interrupt
of such actions.
DateTimeFuse –
execute when the global wallclock reaches the
specified date and time.
ClockTimeFuse –
execute when the global wallclock reaches the
specified future time. This fuse is not date-specific, and will execute within
1 day of having been set.
ClockTimeDaemon – runs
every intervalSeconds from its initial execution
time. This daemon will first execute when the global wallclock
reaches the specified date and time.
DailyDaemon – runs
1 per day from its initial execution time. This daemon will first execute when
the global wallclock reaches the specified date and
time.
TimeEventDaemon – runs
every intervalSeconds from its initial execution
time. This daemon is not date-specific, and will execute within 1 day of having
been set.
DailyTimeEventDaemon – runs
1 per day from its initial execution time. This daemon is not date-specific,
and will execute within 1 day of having been set.
There are also Sensory versions of these daemons and fuses.
These mix-in and daemon
classes allow for particularly specialized behaviors.
TimeSysIdxEventMixIn – Defines
an Indexed Event mix-in class that performs multiple events based on where the daemon
is positioned in an actor's waiting period.
CycleSenseDateTimeDaemon -- Cyclical
Sensory-context-sensitive datetime event daemon -
this is a daemon with an explicit sensory context. This is a specialized SenseDateTimeDaemon
that processes "event objects". The daemon computes which event
object to process, and then adopts the characteristics of the "event
object" and processes the methods of the event object. In addition, if the
player is not able to sense the obj_ at the time
indicated for an event object, then a delayed method is executed for the event
when the actor can sense the indicated obj_.
This daemon makes use of
the TimeSysEventObjects that allow the daemon to
process individual “event objects”, assuming the characteristics of each object
at the time it is due to execute, and allowing the daemon to “delay” its
execution through a secondary method if the actor doesn’t sense the event
object’s sensory object
TimeSysBigBenDaemon – Simulates
announcement and behaviors similar to
that of "Big Ben" in Infocom's
"Sherlock: The Riddle of the Crown Jewels"
TimeSysSunDaemon – Simulates
announcement and behaviors similar to
that of the "sun" in Infocom's
"Sherlock: The Riddle of the Crown Jewels"