mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 17:56:32 +01:00
261 lines
8 KiB
Python
261 lines
8 KiB
Python
"""
|
|
The gametime module handles the global passage of time in the mud.
|
|
|
|
It also supplies some useful methods to convert between
|
|
in-mud time and real-world time as well allows to get the
|
|
total runtime of the server and the current uptime.
|
|
"""
|
|
|
|
import time
|
|
from calendar import monthrange
|
|
from datetime import datetime, timedelta
|
|
|
|
from django.conf import settings
|
|
from evennia import DefaultScript
|
|
from evennia.server.models import ServerConfig
|
|
from evennia.utils.create import create_script
|
|
|
|
# Speed-up factor of the in-game time compared
|
|
# to real time.
|
|
|
|
TIMEFACTOR = settings.TIME_FACTOR
|
|
IGNORE_DOWNTIMES = settings.TIME_IGNORE_DOWNTIMES
|
|
|
|
|
|
# Only set if gametime_reset was called at some point.
|
|
GAME_TIME_OFFSET = ServerConfig.objects.conf("gametime_offset", default=0)
|
|
|
|
# Common real-life time measure, in seconds.
|
|
# You should not change this.
|
|
|
|
# these are kept updated by the server maintenance loop
|
|
SERVER_START_TIME = 0.0
|
|
SERVER_RUNTIME_LAST_UPDATED = 0.0
|
|
SERVER_RUNTIME = 0.0
|
|
|
|
# note that these should not be accessed directly since they may
|
|
# need further processing. Access from server_epoch() and game_epoch().
|
|
_SERVER_EPOCH = None
|
|
_GAME_EPOCH = None
|
|
|
|
# Helper Script dealing in gametime (created by `schedule` function
|
|
# below).
|
|
|
|
|
|
class TimeScript(DefaultScript):
|
|
"""Gametime-sensitive script."""
|
|
|
|
def at_script_creation(self):
|
|
"""The script is created."""
|
|
self.key = "unknown scr"
|
|
self.interval = 100
|
|
self.start_delay = True
|
|
self.persistent = True
|
|
|
|
def at_repeat(self):
|
|
"""Call the callback and reset interval."""
|
|
callback = self.db.callback
|
|
if callback:
|
|
callback()
|
|
|
|
seconds = real_seconds_until(**self.db.gametime)
|
|
self.restart(interval=seconds)
|
|
|
|
# Access functions
|
|
|
|
|
|
def runtime():
|
|
"""
|
|
Get the total runtime of the server since first start (minus
|
|
downtimes)
|
|
|
|
Args:
|
|
format (bool, optional): Format into a time representation.
|
|
|
|
Returns:
|
|
time (float or tuple): The runtime or the same time split up
|
|
into time units.
|
|
|
|
"""
|
|
return SERVER_RUNTIME + time.time() - SERVER_RUNTIME_LAST_UPDATED
|
|
|
|
|
|
def server_epoch():
|
|
"""
|
|
Get the server epoch. We may need to calculate this on the fly.
|
|
|
|
"""
|
|
global _SERVER_EPOCH
|
|
if not _SERVER_EPOCH:
|
|
_SERVER_EPOCH = ServerConfig.objects.conf("server_epoch", default=None) \
|
|
or time.time() - runtime()
|
|
return _SERVER_EPOCH
|
|
|
|
|
|
def uptime():
|
|
"""
|
|
Get the current uptime of the server since last reload
|
|
|
|
Args:
|
|
format (bool, optional): Format into time representation.
|
|
|
|
Returns:
|
|
time (float or tuple): The uptime or the same time split up
|
|
into time units.
|
|
|
|
"""
|
|
return time.time() - SERVER_START_TIME
|
|
|
|
|
|
def portal_uptime():
|
|
"""
|
|
Get the current uptime of the portal.
|
|
|
|
Returns:
|
|
time (float): The uptime of the portal.
|
|
"""
|
|
from evennia.server.sessionhandler import SESSIONS
|
|
return time.time() - SESSIONS.portal_start_time
|
|
|
|
|
|
def game_epoch():
|
|
"""
|
|
Get the game epoch.
|
|
|
|
"""
|
|
game_epoch = settings.TIME_GAME_EPOCH
|
|
return game_epoch if game_epoch is not None else server_epoch()
|
|
|
|
|
|
def gametime(absolute=False):
|
|
"""
|
|
Get the total gametime of the server since first start (minus downtimes)
|
|
|
|
Args:
|
|
absolute (bool, optional): Get the absolute game time, including
|
|
the epoch. This could be converted to an absolute in-game
|
|
date.
|
|
|
|
Returns:
|
|
time (float): The gametime as a virtual timestamp.
|
|
|
|
Notes:
|
|
If one is using a standard calendar, one could convert the unformatted
|
|
return to a date using Python's standard `datetime` module like this:
|
|
`datetime.datetime.fromtimestamp(gametime(absolute=True))`
|
|
|
|
"""
|
|
epoch = game_epoch() if absolute else 0
|
|
if IGNORE_DOWNTIMES:
|
|
gtime = epoch + (time.time() - server_epoch()) * TIMEFACTOR
|
|
else:
|
|
gtime = epoch + (runtime() - GAME_TIME_OFFSET) * TIMEFACTOR
|
|
return gtime
|
|
|
|
|
|
def real_seconds_until(sec=None, min=None, hour=None,
|
|
day=None, month=None, year=None):
|
|
"""
|
|
Return the real seconds until game time.
|
|
|
|
Args:
|
|
sec (int or None): number of absolute seconds.
|
|
min (int or None): number of absolute minutes.
|
|
hour (int or None): number of absolute hours.
|
|
day (int or None): number of absolute days.
|
|
month (int or None): number of absolute months.
|
|
year (int or None): number of absolute years.
|
|
|
|
Returns:
|
|
The number of real seconds before the given game time is up.
|
|
|
|
Example:
|
|
real_seconds_until(hour=5, min=10, sec=0)
|
|
|
|
If the game time is 5:00, TIME_FACTOR is set to 2 and you ask
|
|
the number of seconds until it's 5:10, then this function should
|
|
return 300 (5 minutes).
|
|
|
|
|
|
"""
|
|
current = datetime.fromtimestamp(gametime(absolute=True))
|
|
s_sec = sec if sec is not None else current.second
|
|
s_min = min if min is not None else current.minute
|
|
s_hour = hour if hour is not None else current.hour
|
|
s_day = day if day is not None else current.day
|
|
s_month = month if month is not None else current.month
|
|
s_year = year if year is not None else current.year
|
|
projected = datetime(s_year, s_month, s_day, s_hour, s_min, s_sec)
|
|
|
|
if projected <= current:
|
|
# We increase one unit of time depending on parameters
|
|
days_in_month = monthrange(s_year, s_month)[1]
|
|
days_in_year = sum(monthrange(s_year, m + 1)[1] for m in range(12))
|
|
if month is not None:
|
|
projected += timedelta(days=days_in_year)
|
|
elif day is not None:
|
|
projected += timedelta(days=days_in_month)
|
|
elif hour is not None:
|
|
projected += timedelta(days=1)
|
|
elif min is not None:
|
|
projected += timedelta(seconds=3600)
|
|
else:
|
|
projected += timedelta(seconds=60)
|
|
|
|
# Get the number of gametime seconds between these two dates
|
|
seconds = (projected - current).total_seconds()
|
|
return seconds / TIMEFACTOR
|
|
|
|
|
|
def schedule(callback, repeat=False, sec=None, min=None,
|
|
hour=None, day=None, month=None, year=None):
|
|
"""
|
|
Call a callback at a given in-game time.
|
|
|
|
Args:
|
|
callback (function): The callback function that will be called. Note
|
|
that the callback must be a module-level function, since the script will
|
|
be persistent.
|
|
repeat (bool, optional): Defines if the callback should be called regularly
|
|
at the specified time.
|
|
sec (int or None): Number of absolute game seconds at which to run repeat.
|
|
min (int or None): Number of absolute minutes.
|
|
hour (int or None): Number of absolute hours.
|
|
day (int or None): Number of absolute days.
|
|
month (int or None): Number of absolute months.
|
|
year (int or None): Number of absolute years.
|
|
|
|
Returns:
|
|
script (Script): The created Script handling the sceduling.
|
|
|
|
Examples:
|
|
schedule(func, min=5, sec=0) # Will call 5 minutes past the next (in-game) hour.
|
|
schedule(func, hour=2, min=30, sec=0) # Will call the next (in-game) day at 02:30.
|
|
"""
|
|
seconds = real_seconds_until(sec=sec, min=min, hour=hour,
|
|
day=day, month=month, year=year)
|
|
script = create_script("evennia.utils.gametime.TimeScript",
|
|
key="TimeScript", desc="A gametime-sensitive script",
|
|
interval=seconds, start_delay=True,
|
|
repeats=-1 if repeat else 1)
|
|
script.db.callback = callback
|
|
script.db.gametime = {
|
|
"sec": sec,
|
|
"min": min,
|
|
"hour": hour,
|
|
"day": day,
|
|
"month": month,
|
|
"year": year,
|
|
}
|
|
return script
|
|
|
|
|
|
def reset_gametime():
|
|
"""
|
|
Resets the game time to make it start from the current time. Note that
|
|
the epoch set by `settings.TIME_GAME_EPOCH` will still apply.
|
|
|
|
"""
|
|
global GAME_TIME_OFFSET
|
|
GAME_TIME_OFFSET = runtime()
|
|
ServerConfig.objects.conf("gametime_offset", GAME_TIME_OFFSET)
|