Created
January 16, 2021 19:26
-
-
Save need12648430/1371c041a485d76649661d46a61c93dc to your computer and use it in GitHub Desktop.
A special event manager for keeping persistent game time.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# when loading a save, you'd use this to simulate time since last save | |
# EventManager.simulate_time_span(LAST_UNIX_TIMESTAMP, THIS_UNIX_TIMESTAMP) | |
# for example, this will simulate the last 30 seconds before the game opened | |
# EventManager.simulate_time_span(OS.get_unix_time() - 30, OS.get_unix_time()) | |
# there is an optional third argument to that that specifies a minimum priority | |
# EventManager.simulate_time_span(LAST_UNIX_TIMESTAMP, THIS_UNIX_TIMESTAMP, Priority.UNSKIPPABLE) | |
# .. will only run unskippable events | |
# if the third argument is not specified, both skippable and unskippable events are run | |
# some examples of event types: | |
# add_event( | |
# PeriodicEvent.new( | |
# self, | |
# "periodic", | |
# Priority.UNSKIPPABLE, | |
# OS.get_unix_time(), | |
# time_in_seconds(0, 0, 5) | |
# ) | |
# ) | |
# .. runs every 5 seconds | |
# add_event( | |
# DelayedEvent.new( | |
# self, | |
# "delayed", | |
# Priority.UNSKIPPABLE, | |
# OS.get_unix_time(), | |
# time_in_seconds(0, 0, 10) | |
# ) | |
# ) | |
# .. runs 10 seconds after added, then removes itself | |
# add_event( | |
# DailyEvent.new( | |
# self, | |
# "midnight_utc", | |
# Priority.UNSKIPPABLE, | |
# time_in_seconds(4, 0, 0) | |
# ) | |
# ) | |
# .. runs every day at 4AM UTC | |
extends Node | |
enum Priority { | |
SKIPPABLE = 1, | |
UNSKIPPABLE | |
} | |
# contains TimedEvents we're monitoring | |
var events : Array = [] | |
# uses a timer for updates once the simulation is caught up | |
var timer : Timer | |
var update_frequency : float = 1 | |
# time the last update occurred | |
var last_time : int | |
func _ready () -> void: | |
# init the update timer | |
timer = Timer.new() | |
timer.wait_time = update_frequency | |
timer.one_shot = false | |
timer.autostart = true | |
timer.connect("timeout", self, "update_simulation") | |
add_child(timer) | |
last_time = OS.get_unix_time() | |
# called by timer | |
func update_simulation () -> void: | |
var current_time : int = OS.get_unix_time() | |
simulate_time_span(last_time, current_time) | |
last_time = current_time | |
# converts a length of time into seconds, convenience function | |
func time_in_seconds (hours : int, minutes : int, seconds : int) -> int: | |
return hours * 60 * 60 + minutes * 60 + seconds | |
# adds a TimedEvent to watch | |
func add_event (time_event : TimedEvent) -> void: | |
events.append(time_event) | |
# simulates a span of time | |
func simulate_time_span (start : int, end : int, minimum_priority : int = 1) -> void: | |
# delete those marked expired | |
for i in range(len(events) - 1, -1, -1): | |
if events[i].expired: | |
events.erase(events[i]) | |
var due_events : Array = [] | |
for event in events: | |
if event.priority >= minimum_priority: | |
event.populate(due_events, start, end) | |
due_events.sort_custom(self, "compare_events") | |
for event in due_events: | |
if event.timed_event.object.has_method(event.timed_event.method_name): | |
event.timed_event.object.call(event.timed_event.method_name) | |
else: | |
print("could not call method '%s' on %s, doesn't exist" % [event.timed_event.method_name, event.timed_event.object]) | |
# a comparitor for sorting by time of event | |
# not always necessary, but handy for interdependent events | |
func compare_events (a : EventInstance, b : EventInstance) -> bool: | |
return a.time < b.time | |
# a single instance of a timed event | |
class EventInstance: | |
var timed_event : TimedEvent | |
var time : int | |
var priority : int | |
func _init(timed_event : TimedEvent, time : int): | |
self.timed_event = timed_event | |
self.time = time | |
# timed event base class | |
class TimedEvent: | |
var object : Object | |
var method_name : String | |
# if expired is true, event will be deleted during next simulation | |
var expired : bool = false | |
var priority : int = EventManager.Priority.SKIPPABLE | |
func _init (object : Object, method_name : String, priority : int): | |
self.object = object | |
self.method_name = method_name | |
self.priority = priority | |
# convenience function for checking if a timestamp falls within a timespan | |
func in_span (time : int, start : int, end : int) -> bool: | |
return time >= start and time < end | |
# populates an array of eventinstances for a span of time | |
# just a stub to be filled out by child classes | |
func populate (due_events : Array, span_start : int, span_end : int) -> void: | |
pass | |
# used for delayed events, e.g. 1 hour from now, and only once | |
class DelayedEvent extends TimedEvent: | |
var start_time : int | |
var delay_length : int | |
func _init(object : Object, method_name : String, priority : int, start_time : int, delay_length : int) . (object, method_name, priority): | |
self.start_time = start_time | |
self.delay_length = delay_length | |
func populate(due_events : Array, span_start : int, span_end : int) -> void: | |
if in_span(start_time + delay_length, span_start, span_end): | |
due_events.append(EventInstance.new(self, start_time + delay_length)) | |
expired = true | |
# used for periodic events, e.g. every 15 minutes | |
class PeriodicEvent extends TimedEvent: | |
var start_time : int | |
var period_length : int | |
func _init(object : Object, method_name : String, priority : int, start_time : int, period_length : int) . (object, method_name, priority): | |
self.start_time = start_time | |
self.period_length = period_length | |
func populate (due_events : Array, span_start : int, span_end : int) -> void: | |
var remainder : int = start_time % period_length | |
var periods_in_span : int = (span_end - span_start) / period_length | |
if periods_in_span > 0: | |
var current_period : int = span_start / period_length | |
for i in range(0, periods_in_span): | |
due_events.append(EventInstance.new(self, (current_period + i) * period_length + remainder)) | |
else: | |
var next_period : int = span_start / period_length | |
var next_time : int = next_period * period_length | |
next_time += remainder | |
if in_span(next_time, span_start, span_end): | |
due_events.append(EventInstance.new(self, next_time)) | |
# used for daily events, e.g. every day at 5AM | |
class DailyEvent extends TimedEvent: | |
var time_of_day : int | |
func _init (object : Object, method_name : String, priority : int, time_of_day : int = 0) . (object, method_name, priority): | |
self.time_of_day = time_of_day | |
func populate (due_events : Array, span_start : int, span_end : int) -> void: | |
var span_of_day = EventManager.time_in_seconds(24, 0, 0) | |
var days_in_span = (span_end - span_start) / span_of_day | |
if days_in_span > 0: | |
var start_day = span_start / span_of_day | |
for day in range(start_day, start_day + days_in_span): | |
due_events.append(EventInstance.new(self, span_of_day * day + time_of_day)) | |
else: | |
var next_period : int = span_start / span_of_day | |
var next_time : int = next_period * span_of_day + time_of_day | |
if in_span(next_time, span_start, span_end): | |
due_events.append(EventInstance.new(self, next_time)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment