143 lines
4.6 KiB
GDScript
143 lines
4.6 KiB
GDScript
class_name GameClock
|
|
extends Node
|
|
## Игровые дата и время.
|
|
##
|
|
## Отдельная сущность, а не поле сцены: ход времени понадобится производству,
|
|
## жителям и событиям, и сохранение обязано его восстанавливать.
|
|
##
|
|
## Пауза здесь своя, не движковая. `get_tree().paused` останавливает вообще всё
|
|
## и занята меню паузы; здесь же нужно останавливать только ход партии, оставляя
|
|
## интерфейс живым. При этом узел обычный, PAUSABLE, поэтому меню паузы
|
|
## замораживает время само собой.
|
|
|
|
## Старт партии: 1 апреля 2012 года, шесть утра.
|
|
const START := {"year": 2012, "month": 4, "day": 1, "hour": 6, "minute": 0, "second": 0}
|
|
|
|
## Сколько игровых минут проходит за секунду реального времени на скорости x1.
|
|
## Сутки при этом занимают 8 минут реального времени. Множители x2…x4
|
|
## умножают именно это значение, так что менять темп достаточно здесь.
|
|
const MINUTES_PER_SECOND := 3.0
|
|
|
|
## Доступные множители скорости.
|
|
const SPEEDS: Array[int] = [1, 2, 3, 4]
|
|
|
|
const MINUTES_PER_DAY := 24 * 60
|
|
|
|
const MONTHS: Array[String] = [
|
|
"января", "февраля", "марта", "апреля", "мая", "июня",
|
|
"июля", "августа", "сентября", "октября", "ноября", "декабря",
|
|
]
|
|
|
|
## Прошла игровая минута — сюда подключаются HUD, производство, события.
|
|
signal minute_passed(total_minutes: int)
|
|
## Сменились сутки.
|
|
signal day_passed(day_index: int)
|
|
## Изменились скорость или состояние паузы.
|
|
signal flow_changed
|
|
|
|
var running := true
|
|
var speed := 1
|
|
|
|
var _start_unix := int(Time.get_unix_time_from_datetime_dict(START))
|
|
var _elapsed_minutes := 0.0
|
|
var _last_whole_minute := 0
|
|
var _last_day := 0
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if not running:
|
|
return
|
|
_advance(delta * MINUTES_PER_SECOND * float(speed))
|
|
|
|
|
|
# --- Текущий момент ---------------------------------------------------------
|
|
|
|
## Момент как словарь Time: year, month, day, hour, minute, second.
|
|
func now() -> Dictionary:
|
|
return Time.get_datetime_dict_from_unix_time(unix_time())
|
|
|
|
|
|
func unix_time() -> int:
|
|
return _start_unix + int(_elapsed_minutes) * 60
|
|
|
|
|
|
## «1 апреля 2012»
|
|
func date_text() -> String:
|
|
var moment := now()
|
|
return "%d %s %d" % [
|
|
int(moment["day"]), MONTHS[int(moment["month"]) - 1], int(moment["year"]),
|
|
]
|
|
|
|
|
|
## «06:00»
|
|
func time_text() -> String:
|
|
var moment := now()
|
|
return "%02d:%02d" % [int(moment["hour"]), int(moment["minute"])]
|
|
|
|
|
|
# --- Управление ходом -------------------------------------------------------
|
|
|
|
func set_speed(value: int) -> void:
|
|
speed = clampi(value, SPEEDS[0], SPEEDS[SPEEDS.size() - 1])
|
|
running = true
|
|
flow_changed.emit()
|
|
|
|
|
|
func play() -> void:
|
|
if running:
|
|
return
|
|
running = true
|
|
flow_changed.emit()
|
|
|
|
|
|
func pause() -> void:
|
|
if not running:
|
|
return
|
|
running = false
|
|
flow_changed.emit()
|
|
|
|
|
|
func toggle() -> void:
|
|
running = not running
|
|
flow_changed.emit()
|
|
|
|
|
|
# --- Сохранение -------------------------------------------------------------
|
|
|
|
func save_data() -> Dictionary:
|
|
return {"minutes": _elapsed_minutes, "speed": speed, "running": running}
|
|
|
|
|
|
func load_data(data: Dictionary) -> void:
|
|
_elapsed_minutes = maxf(float(data.get("minutes", 0.0)), 0.0)
|
|
speed = clampi(int(data.get("speed", 1)), SPEEDS[0], SPEEDS[SPEEDS.size() - 1])
|
|
running = bool(data.get("running", true))
|
|
_last_whole_minute = int(_elapsed_minutes)
|
|
_last_day = _day_index()
|
|
flow_changed.emit()
|
|
minute_passed.emit(_last_whole_minute)
|
|
|
|
|
|
# --- Внутреннее -------------------------------------------------------------
|
|
|
|
func _advance(minutes: float) -> void:
|
|
_elapsed_minutes += minutes
|
|
|
|
# Сигналим по целым минутам, а не каждый кадр: подписчикам нужен такт,
|
|
# а не дробное значение.
|
|
var whole := int(_elapsed_minutes)
|
|
if whole == _last_whole_minute:
|
|
return
|
|
_last_whole_minute = whole
|
|
minute_passed.emit(whole)
|
|
|
|
var day := _day_index()
|
|
if day == _last_day:
|
|
return
|
|
_last_day = day
|
|
day_passed.emit(day)
|
|
|
|
|
|
func _day_index() -> int:
|
|
return floori(_elapsed_minutes / float(MINUTES_PER_DAY))
|