netfishing/world/world_time_service.gd

176 lines
4.6 KiB
GDScript

class_name WorldTimeService
extends Node
signal time_changed(time_hours: float, phase: Phase)
signal phase_changed(phase: Phase)
signal authoritative_time_set(time_hours: float)
signal natural_time_advanced(hours: float)
enum Phase {
DAWN,
DAY,
DUSK,
NIGHT,
}
const HOURS_PER_DAY: float = 24.0
const REAL_SECONDS_PER_CYCLE: float = 60.0 * 60.0
const HOURS_PER_REAL_SECOND: float = HOURS_PER_DAY / REAL_SECONDS_PER_CYCLE
const DAY_START_HOUR: float = 8.0
const NIGHT_START_HOUR: float = 20.0
const TRANSITION_HALF_HOURS: float = 0.5
const DAWN_START_HOUR: float = DAY_START_HOUR - TRANSITION_HALF_HOURS
const DAWN_END_HOUR: float = DAY_START_HOUR + TRANSITION_HALF_HOURS
const DUSK_START_HOUR: float = NIGHT_START_HOUR - TRANSITION_HALF_HOURS
const DUSK_END_HOUR: float = NIGHT_START_HOUR + TRANSITION_HALF_HOURS
const DEFAULT_START_HOUR: float = DAY_START_HOUR
var _time_hours: float = DEFAULT_START_HOUR
var _persistent_time_hours: float = DEFAULT_START_HOUR
var _persistence_tracking_enabled: bool = false
var _running: bool = false
var _phase: Phase = Phase.DAWN
var _last_emitted_clock_minute: int = floori(DEFAULT_START_HOUR * 60.0)
func _ready() -> void:
set_process(false)
func _process(delta: float) -> void:
advance_time(delta)
func begin_session(start_hour: float = DEFAULT_START_HOUR) -> void:
_running = true
set_process(true)
_set_time_hours(start_hour, true)
func end_session() -> void:
_running = false
set_process(false)
_set_time_hours(DEFAULT_START_HOUR, true)
func advance_time(real_seconds: float) -> void:
if not _running or real_seconds <= 0.0:
return
var advanced_hours := real_seconds * HOURS_PER_REAL_SECOND
_set_time_hours(
_time_hours + advanced_hours,
false,
)
natural_time_advanced.emit(advanced_hours)
func synchronize_time(authoritative_time_hours: float) -> void:
if not is_finite(authoritative_time_hours):
return
_set_time_hours(authoritative_time_hours, true)
func set_authoritative_time(time_hours: float) -> bool:
if (
not is_finite(time_hours)
or time_hours < 0.0
or time_hours >= HOURS_PER_DAY
):
return false
_set_time_hours(time_hours, true)
authoritative_time_set.emit(_time_hours)
return true
func set_persistence_tracking_enabled(enabled: bool) -> void:
if _persistence_tracking_enabled == enabled:
return
if _persistence_tracking_enabled:
_persistent_time_hours = _time_hours
_persistence_tracking_enabled = enabled
func restore_persistent_time_hours(time_hours: float) -> bool:
if (
not is_finite(time_hours)
or time_hours < 0.0
or time_hours >= HOURS_PER_DAY
):
return false
_persistent_time_hours = time_hours
if _persistence_tracking_enabled:
_set_time_hours(_persistent_time_hours, true)
return true
func get_persistent_time_hours() -> float:
return _persistent_time_hours
func get_time_hours() -> float:
return _time_hours
func get_phase() -> Phase:
return _phase
func is_night_period() -> bool:
return _time_hours < DAY_START_HOUR or _time_hours >= NIGHT_START_HOUR
func is_transition() -> bool:
return _phase in [Phase.DAWN, Phase.DUSK]
func get_clock_text() -> String:
return format_clock_time(_time_hours)
static func phase_for_hour(time_hours: float) -> Phase:
var hour: float = _normalized_hour(time_hours)
if hour >= DAWN_START_HOUR and hour < DAWN_END_HOUR:
return Phase.DAWN
if hour >= DAWN_END_HOUR and hour < DUSK_START_HOUR:
return Phase.DAY
if hour >= DUSK_START_HOUR and hour < DUSK_END_HOUR:
return Phase.DUSK
return Phase.NIGHT
static func format_clock_time(time_hours: float) -> String:
var hour: float = _normalized_hour(time_hours)
var total_minutes: int = floori(hour * 60.0)
var hour_24: int = floori(float(total_minutes) / 60.0)
var minute: int = total_minutes % 60
var hour_12: int = hour_24 % 12
if hour_12 == 0:
hour_12 = 12
return "%d:%02d %s" % [
hour_12,
minute,
"am" if hour_24 < 12 else "pm",
]
func _set_time_hours(time_hours: float, force_emit: bool) -> void:
var normalized: float = _normalized_hour(time_hours)
var next_phase: Phase = phase_for_hour(normalized)
var phase_was_changed: bool = next_phase != _phase
var clock_minute: int = floori(normalized * 60.0)
var clock_minute_changed: bool = (
clock_minute != _last_emitted_clock_minute
)
_time_hours = normalized
_phase = next_phase
if _persistence_tracking_enabled:
_persistent_time_hours = normalized
if phase_was_changed:
phase_changed.emit(_phase)
if force_emit or clock_minute_changed or phase_was_changed:
_last_emitted_clock_minute = clock_minute
time_changed.emit(_time_hours, _phase)
static func _normalized_hour(time_hours: float) -> float:
return fposmod(time_hours, HOURS_PER_DAY)