Implement apocalypse mechanics: add apocalypse queue, controller, and integrate into production logic

This commit is contained in:
Kirill
2026-10-18 00:05:53 +03:00
parent f53181b9ad
commit 3047ef5a85
12 changed files with 393 additions and 7 deletions
+2
View File
@@ -30,6 +30,7 @@ var Templates: Dictionary = {}
var LifeRules: Dictionary = {} var LifeRules: Dictionary = {}
var CivilizationRules: Dictionary = {} var CivilizationRules: Dictionary = {}
var ExtraParams: Dictionary = {} var ExtraParams: Dictionary = {}
var ApocalypseQueue: Dictionary = {}
var Music: Dictionary = {} var Music: Dictionary = {}
var Effects: Dictionary = {} var Effects: Dictionary = {}
@@ -92,6 +93,7 @@ func _ready() -> void:
OwniverseItems = OwniverseItemLoader.get_data() OwniverseItems = OwniverseItemLoader.get_data()
Templates = OwniverseTemplateLoader.get_data() Templates = OwniverseTemplateLoader.get_data()
CivilizationRules = OwniverseCivilizationRuleLoader.get_data() CivilizationRules = OwniverseCivilizationRuleLoader.get_data()
ApocalypseQueue = ApocalypseQueueLoader.get_data()
Music = OwniverseAudioLoader.get_music() Music = OwniverseAudioLoader.get_music()
Effects = OwniverseAudioLoader.get_effects() Effects = OwniverseAudioLoader.get_effects()
+9 -1
View File
@@ -250,8 +250,16 @@ static func _validate_payload(data: Variant, speed_limit: int, stages: Array, en
# Optional additions to the current format. Saves predating indicators keep # Optional additions to the current format. Saves predating indicators keep
# their quantities and receive neutral defaults from Owniverse on load. # their quantities and receive neutral defaults from Owniverse on load.
for field in saved: for field in saved:
if (not E.contains(required, field) and not E.contains(["world_parameters", "trends"], field)): if (not E.contains(required, field) and not E.contains(["world_parameters", "trends", "apocalypse"], field)):
return "unsupported state structure" return "unsupported state structure"
if saved.has("apocalypse"):
var apocalypse: Variant = saved.apocalypse
if not apocalypse is Dictionary: return "invalid state.apocalypse"
for key in ["phase", "stage", "tick", "end_ticks"]:
if not _number(apocalypse.get(key), true): return E.add("invalid state.apocalypse.", key)
for key in ["type", "warning"]:
if not _number(apocalypse.get(key), true, false): return E.add("invalid state.apocalypse.", key)
if E.lt(apocalypse.phase, 0) or E.gt(apocalypse.phase, 2): return "invalid apocalypse phase"
if saved.has("world_parameters"): if saved.has("world_parameters"):
if ((not _number_map(saved.world_parameters)) or E.ne(saved.world_parameters.size(), WorldParameters.defaults().size())): if ((not _number_map(saved.world_parameters)) or E.ne(saved.world_parameters.size(), WorldParameters.defaults().size())):
return "invalid state.world_parameters" return "invalid state.world_parameters"
+144
View File
@@ -0,0 +1,144 @@
class_name ApocalypseQueueLoader
static func get_data() -> Dictionary:
return CSVLoader.load(DATA)
#region Owniverse Items
const DATA := """
Num|Shortcut|Stage|Screen|Tick|Desc
1|LUB|1|3|50|Вселенский пузырь
2|LGW|1|3|5|Великая стена
3|LGF|1|3|5|Галактическая нить
4|LSC|1|3|5|Сверхскопление
5|LGG|1|3|5|Группа галактик
6|GSG|2|3|5|Гигантская спиральная галактика
7|GSL|2|3|5|Большая спиральная галактика
8|GSM|2|3|5|Средняя спиральная галактика
9|GSS|2|3|5|Малая спиральная галактика
10|GSD|2|3|5|Карликовая спиральная галактика
11|GEG|2|3|5|Гигантская эллиптическая галактика
12|GEL|2|3|5|Большая эллиптическая галактика
13|GEM|2|3|5|Средняя эллиптическая галактика
14|GES|2|3|5|Малая эллиптическая галактика
15|GED|2|3|5|Карликовая эллиптическая галактика
16|SCG|2|3|5|Гигантское звёздное скопление
17|SCL|2|3|5|Большое звёздное скопление
18|SCM|2|3|5|Среднее звёздное скопление
19|SCS|2|3|5|Малое звёздное скопление
20|SCD|2|3|5|Рассеянное звёздное скопление
21|C4.4|3|4|20|Цивилизация 4.4
22|C4.3|3|4|0|Цивилизация 4.3
23|C4.2|3|4|0|Цивилизация 4.2
24|C4.1|3|4|0|Цивилизация 4.1
25|C4.0|3|4|0|Цивилизация 4.0
26|C4|3|4|0|Цивилизация типа 4
27|C3.9|4|4|5|Цивилизация 3.9
28|C3.8|4|4|0|Цивилизация 3.8
29|C3.7|4|4|0|Цивилизация 3.7
30|C3.6|4|4|0|Цивилизация 3.6
31|C3.5|4|4|0|Цивилизация 3.5
32|C3.4|4|4|0|Цивилизация 3.4
33|C3.3|4|4|0|Цивилизация 3.3
34|C3.2|4|4|0|Цивилизация 3.2
35|C3.1|4|4|0|Цивилизация 3.1
36|C3.0|4|4|0|Цивилизация 3.0
37|C3|4|4|0|Цивилизация типа 3
38|C2.9|5|4|5|Цивилизация 2.9
39|C2.8|5|4|0|Цивилизация 2.8
40|C2.7|5|4|0|Цивилизация 2.7
41|C2.6|5|4|0|Цивилизация 2.6
42|C2.5|5|4|0|Цивилизация 2.5
43|C2.4|5|4|0|Цивилизация 2.4
44|C2.3|5|4|0|Цивилизация 2.3
45|C2.2|5|4|0|Цивилизация 2.2
46|C2.1|5|4|0|Цивилизация 2.1
47|C2.0|5|4|0|Цивилизация 2.0
48|C2|5|4|0|Цивилизация типа 2
49|HG|6|2|5|Гипергигант
50|SG|6|2|5|Сверхгигант
51|BG|6|2|5|Голубой гигант
52|YG|6|2|5|Жёлтый гигант
53|RHG|6|2|5|Красный гипергигант
54|RSG|6|2|5|Красный сверхгигант
55|WYD|6|2|5|Бело-жёлтый карлик
56|YD|6|2|5|Жёлтый карлик
57|OD|6|2|5|Оранжевый карлик
58|RD|6|2|5|Красный карлик
59|HRG|6|2|5|Огромный красный гигант
60|LRG|6|2|0|Большой красный гигант
61|MRG|6|2|0|Средний красный гигант
62|NRG|6|2|0|Обычный Красный гигант
64|SRG|6|2|0|Малый красный гигант
63|RG|6|2|0|Красный гигант
65|HN|6|2|0|Гиперновая
66|BSN|6|2|0|Яркая сверхновая
67|SN|6|2|0|Сверхновая
68|DSN|6|2|0|Тусклая сверхновая
69|HGRB|6|2|0|Гипер гамма-всплеск
70|LGRB|6|2|0|Большой гамма-всплеск
71|GRB|6|2|0|Гамма-всплеск
72|SGRB|6|2|0|Малый гамма-всплеск
73|ASTN|6|2|5|Активная звёздная колыбель
74|MC|6|2|5|Молекулярное облако
75|STN|6|2|5|Звёздная колыбель
76|G|6|2|5|Глобула
77|N|6|2|5|Туманность
78|HBD|7|2|5|Горячий бурый карлик
79|BD|7|2|0|Бурый карлик
80|LWD|7|2|0|Большой белый карлик
81|NWD|7|2|0|Обычный белый карлик
82|SWD|7|2|0|Малый белый карлик
83|TWD|7|2|0|Крошечный белый карлик
84|WD|7|2|0|Белый карлик
85|CBD|7|2|0|Холодный бурый карлик
86|MGT|7|2|0|Магнетар
87|LNS|7|2|0|Большая нейтронная звезда
88|SNS|7|2|0|Малая нейтронная звезда
89|NS|7|2|5|Нейтронная звезда
90|BH60|7|2|5|Чёрная дыра 60
91|BH40|7|2|0|Чёрная дыра 40
92|BH30|7|2|0|Чёрная дыра 30
93|BH25|7|2|0|Чёрная дыра 25
94|BH|7|2|0|Чёрная дыра
95|C1.9|8|4|5|Цивилизация 1.9
96|C1.8|8|4|0|Цивилизация 1.8
97|C1.7|8|4|0|Цивилизация 1.7
98|C1.6|8|4|0|Цивилизация 1.6
99|C1.5|8|4|0|Цивилизация 1.5
100|C1.4|8|4|0|Цивилизация 1.4
101|C1.3|8|4|0|Цивилизация 1.3
102|C1.2|8|4|0|Цивилизация 1.2
103|C1.1|8|4|0|Цивилизация 1.1
104|C1.0|8|4|0|Цивилизация 1.0
105|C1|8|4|0|Цивилизация типа 1
106|C0.9|8|4|5|Цивилизация 0.9
107|C0.8|8|4|0|Цивилизация 0.8
108|C0.7|8|4|0|Цивилизация 0.7
109|C0.6|8|4|0|Цивилизация 0.6
110|C0.5|8|4|0|Цивилизация 0.5
111|C0.4|8|4|0|Цивилизация 0.4
112|C0.3|8|4|0|Цивилизация 0.3
113|C0.2|8|4|0|Цивилизация 0.2
114|C0.1|8|4|0|Цивилизация 0.1
115|C0.0|8|4|0|Цивилизация 0.0
116|C0|8|4|0|Цивилизация типа 0
117|EON5|8|4|5|Эон 5: Кайнозой
118|EON4|8|4|0|Эон 4: Мезозой
119|EON3|8|4|0|Эон 3: Палеозой
120|EON2|8|4|0|Эон 2: Протерозой
121|EON1|8|4|0|Эон 1: Архей
122|LP|9|4|5|Живая
123|HP|9|4|0|Обитаемая
124|RP|9|4|5|Каменистая
125|AB|9|4|0|Астероиды
126|GG|9|4|0|Газовый гигант
127|Met|10|1|5|Металлы
128|He|10|1|0|Гелий
129|H|11|1|5|Водород
130|SMBH|12|2|5|СМЧД
131|S|12|1|5|Пространство
"""
#endregion
@@ -0,0 +1 @@
uid://8bh387weuy7a
+4 -2
View File
@@ -177,9 +177,11 @@ Ct не получает часы. Инициализация отсутству
- `1e-5 < density < 0.9`: обычная шкала, `ln(density × 1e5) / ln(9e4)`; - `1e-5 < density < 0.9`: обычная шкала, `ln(density × 1e5) / ln(9e4)`;
- `density ≥ 0.9`: шкала сжатия, `(density − 0.9) × 10`. - `density ≥ 0.9`: шкала сжатия, `(density − 0.9) × 10`.
В Unity в первой формуле было `−7`; это ошибка, не позволяющая указателю двигаться. Здесь исправлено на `+7`. Переключение шкалы меняет и заголовок/описание индикатора. Само наступление апокалипсиса индикатор не запускает. В Unity в первой формуле было `−7`; это ошибка, не позволяющая указателю двигаться. Здесь исправлено на `+7`. Переключение шкалы меняет и заголовок/описание индикатора.
Общая рамка `Background/WarningBorder` получает цвета Unity `WarningUI` в формате RGBA: при плотности ≤1e-5 — `C040407F`, ≤1e-6 — `DF2020BF`, ≤1e-7 — `FF0000FF`; при плотности ≥0,9 — `40A0C07F`, ≥0,97 — `20B0DFBF`, ≥1 — `00C0FFFF`. В нормальном диапазоне рамка прозрачна. Она обновляется вместе с индикаторами, не перехватывает нажатия и снимается при восстановлении плотности. Необратимая последовательность апокалипсиса Unity сюда не входит. Общая рамка `Background/WarningBorder` получает цвета Unity `WarningUI` в формате RGBA: при плотности ≤1e-5 — `C040407F`, ≤1e-6 — `DF2020BF`, ≤1e-7 — `FF0000FF`; при плотности ≥0,9 — `40A0C07F`, ≥0,97 — `20B0DFBF`, ≥1 — `00C0FFFF`. В нормальном диапазоне рамка прозрачна. Она обновляется вместе с индикаторами и не перехватывает нажатия.
Пороговые состояния управляют запуском апокалипсиса: при `density ≤ bigrip3` начинается Big Rip, при `density ≥ bigcrunch3` — Big Crunch. Последовательность берётся из `Global.ApocalypseQueue`, сортируется по `Num` и использует указанные `Stage`, `Screen` и `Tick`. Каждые 0,1 секунды активные объекты выбранной страницы теряют половину количества (для остатков до 5 — по одному). На время последовательности скорость фиксируется на 1, ручное и автоматическое создание блокируется, а экран переключается по очереди. Big Rip пропускает `S`, Big Crunch — `SMBH`; остаток энергии постепенно переводится в соответствующий объект. После 20 тиков с нулевой энергией апокалипсис получает фазу завершения, симуляция останавливает время. Фаза, тип события, предупреждение и позиция в очереди сохраняются в состоянии вселенной.
### Горючесть, взрывчатость и агрессия ### Горючесть, взрывчатость и агрессия
+43 -1
View File
@@ -100,6 +100,12 @@ var active_actions_dirty: bool = true
var active_actions_elapsed: float = 0.0 var active_actions_elapsed: float = 0.0
var clock_display_elapsed: float = 0.0 var clock_display_elapsed: float = 0.0
var interface_sounds_enabled: bool = false var interface_sounds_enabled: bool = false
var apocalypse_elapsed: float = 0.0
var apocalypse_ui_phase: int = -1
var apocalypse_screen: int = -1
@onready var time_minus_button: TextureButton = $World/TopMenu/TimeMinus
@onready var time_plus_button: TextureButton = $World/TopMenu/TimePlus
@onready var apocalypse_music: OwniverseMusicPlayer = $"../MusicPlayer"
var owniverse:Owniverse = Owniverse.new() var owniverse:Owniverse = Owniverse.new()
var owniverse_items: Dictionary[String, OwniverseItem] = {} var owniverse_items: Dictionary[String, OwniverseItem] = {}
@@ -274,6 +280,7 @@ func _swipe_to_nearest_page() -> void:
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
if E.eq(owniverse.state.get("apocalypse", {}).get("phase", 0), 1): return
if event is InputEventScreenDrag: if event is InputEventScreenDrag:
#effects_player.play_effect("swipe") #effects_player.play_effect("swipe")
stage_page_scroller.scroll_horizontal = clamp( stage_page_scroller.scroll_horizontal = clamp(
@@ -299,7 +306,7 @@ func _set_active_page_actions() -> void:
#var amount = owniverse.get_available_to_produce(item_id) #var amount = owniverse.get_available_to_produce(item_id)
#var amount = owniverse.get_available_to_produce(entity) #var amount = owniverse.get_available_to_produce(entity)
var amount = owniverse.production.calculate_max_producible(entity) var amount = (0 if E.gt(owniverse.state.get("apocalypse", {}).get("phase", 0), 0) else owniverse.production.calculate_max_producible(entity))
#print(item_id, " : ", amount) #print(item_id, " : ", amount)
if item_id == "SMBH": if item_id == "SMBH":
var preview: Callable = func(requested): var preview: Callable = func(requested):
@@ -398,6 +405,7 @@ func _refresh_universe_ui() -> void:
func _process(delta: float) -> void: func _process(delta: float) -> void:
owniverse.evolute_frame(delta, get_tree()) owniverse.evolute_frame(delta, get_tree())
_process_apocalypse(delta)
clock_display_elapsed += delta clock_display_elapsed += delta
if clock_display_elapsed >= 0.1: if clock_display_elapsed >= 0.1:
clock_display_elapsed = fmod(clock_display_elapsed, 0.1) clock_display_elapsed = fmod(clock_display_elapsed, 0.1)
@@ -431,6 +439,7 @@ func _process(delta: float) -> void:
func _change_speed(delta: int) -> void: func _change_speed(delta: int) -> void:
if E.eq(owniverse.state.get("apocalypse", {}).get("phase", 0), 1) and delta != 0: return
var previous_speed: int = owniverse.speed var previous_speed: int = owniverse.speed
owniverse.speed = owniverse.speed + delta owniverse.speed = owniverse.speed + delta
@@ -458,6 +467,39 @@ func _change_speed(delta: int) -> void:
g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed + 1)) g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed + 1))
func _process_apocalypse(delta: float) -> void:
var apocalypse: Dictionary = owniverse.state.get("apocalypse", {})
var phase: int = int(apocalypse.get("phase", 0))
if phase != apocalypse_ui_phase:
apocalypse_ui_phase = phase
if phase == 1:
apocalypse_music.play_apocalypse()
owniverse.speed = 1
_change_speed(0)
elif phase == 2:
owniverse.speed = 1
_change_speed(0)
time_minus_button.disabled = phase == 1
time_plus_button.disabled = phase == 1
active_actions_dirty = true
if phase == 1:
_set_active_page_actions()
if phase != 1:
apocalypse_elapsed = 0.0
return
apocalypse_elapsed += delta
if owniverse.frame_busy or owniverse.production_queue_busy:
return
while apocalypse_elapsed >= OwniverseApocalypseController.TICK_SECONDS and E.eq(owniverse.state.apocalypse.phase, 1):
apocalypse_elapsed -= OwniverseApocalypseController.TICK_SECONDS
var result: Dictionary = owniverse.apocalypse.process_tick(owniverse.state)
if int(result.screen) >= 0 and int(result.screen) != apocalypse_screen:
apocalypse_screen = int(result.screen)
stage_page_scroller.scroll_horizontal = apocalypse_screen * PAGE_WIDTH
if bool(result.finished):
_change_speed(0)
func _on_topmenu_button_pressed(screen_name: String) -> void: func _on_topmenu_button_pressed(screen_name: String) -> void:
var current_screen: String = "" var current_screen: String = ""
for screen_id in screens: for screen_id in screens:
+169
View File
@@ -0,0 +1,169 @@
extends "res://world/owniverse/simulation_module.gd"
class_name OwniverseApocalypseController
const E = preload("res://world/owniverse/math/exact.gd")
const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
const TICK_SECONDS: float = 0.1
const END_WAIT_TICKS: int = 20
var _schedule: Array = []
func defaults() -> Dictionary:
return {"phase": 0, "type": 0, "warning": 0, "stage": 1, "tick": 0, "end_ticks": 0}
func check_density(target: Dictionary) -> void:
var apocalypse: Dictionary = target.get("apocalypse", defaults())
if E.gt(apocalypse.get("phase", 0), 0): return
var density: Variant = world.environment.stellar_environment(target).density
var parameters: Dictionary = WorldParameters.extra_params()
var warning: int = 0
if E.ge(density, parameters.bigcrunch3): warning = 3
elif E.ge(density, parameters.bigcrunch2): warning = 2
elif E.ge(density, parameters.bigcrunch1): warning = 1
elif E.le(density, parameters.bigrip3): warning = -3
elif E.le(density, parameters.bigrip2): warning = -2
elif E.le(density, parameters.bigrip1): warning = -1
apocalypse.warning = warning
target.apocalypse = apocalypse
if E.eq(warning, 3):
start(target, 3)
elif E.eq(warning, -3):
start(target, -3)
func start(target: Dictionary, event_type: int) -> void:
var apocalypse: Dictionary = target.get("apocalypse", defaults()).duplicate(true)
if E.gt(apocalypse.phase, 0): return
apocalypse.phase = 1
apocalypse.type = event_type
apocalypse.stage = 1
apocalypse.tick = 0
apocalypse.end_ticks = 0
target.apocalypse = apocalypse
target.speed = 1
target.speed_in_year = 1
_schedule = _make_schedule("SMBH" if event_type > 0 else "S")
func process_tick(target: Dictionary) -> Dictionary:
var result: Dictionary = {"screen": -1, "finished": false}
var apocalypse: Dictionary = target.get("apocalypse", defaults())
if E.ne(apocalypse.get("phase", 0), 1): return result
if _schedule.is_empty(): _schedule = _make_schedule("SMBH" if E.gt(apocalypse.type, 0) else "S")
apocalypse.tick = E.add(apocalypse.tick, 1)
var stage_rows: Array = []
for row: Dictionary in _schedule:
if E.eq(row.stage, apocalypse.stage): stage_rows.append(row)
if stage_rows.is_empty():
var next_stage: Variant = INF
for row: Dictionary in _schedule:
if E.gt(row.stage, apocalypse.stage): next_stage = E.minimum(next_stage, row.stage)
if E.finite(next_stage):
apocalypse.stage = E.integer(next_stage)
apocalypse.tick = 0
else:
_process_final_energy(target, apocalypse)
if E.eq(apocalypse.end_ticks, END_WAIT_TICKS):
apocalypse.phase = 2
result.finished = true
target.apocalypse = apocalypse
return result
var stage_complete: bool = true
for row: Dictionary in stage_rows:
if E.gt(row.tick, apocalypse.tick):
stage_complete = false
continue
var id: String = row.id
if not world.catalog.entities.has(id): continue
if E.gt(world.ledger.physical_amount(target, id), 0):
stage_complete = false
result.screen = int(row.screen)
_destroy_fraction(target, id)
if stage_complete:
var next_stage: Variant = INF
for row: Dictionary in _schedule:
if E.gt(row.stage, apocalypse.stage): next_stage = E.minimum(next_stage, row.stage)
if E.finite(next_stage):
apocalypse.stage = E.integer(next_stage)
apocalypse.tick = 0
else:
_process_final_energy(target, apocalypse)
if E.eq(apocalypse.end_ticks, END_WAIT_TICKS):
apocalypse.phase = 2
result.finished = true
target.apocalypse = apocalypse
world.ledger.refresh_groups(target)
world.ledger.clamp_auto_resources(target)
world.presentation.refresh(target, world.catalog)
return result
func _make_schedule(skipped_id: String) -> Array:
var rows: Array = Global.ApocalypseQueue.values()
rows.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a.Num) < int(b.Num))
var result: Array = []
var stage: int = -1
var tick: int = 0
for source: Dictionary in rows:
if source.Shortcut == skipped_id: continue
var row_stage: int = int(source.Stage)
if row_stage != stage:
stage = row_stage
tick = 0
tick += int(source.Tick)
result.append({"id": str(source.Shortcut), "stage": row_stage, "screen": int(source.Screen), "tick": tick})
return result
func _destroy_fraction(target: Dictionary, id: String) -> void:
var entity: OwniverseEntity = world.catalog.entities[id]
var physical: Variant = world.ledger.physical_amount(target, id)
if E.le(physical, 0): return
var amount: Variant = E.round_value(E.divide(physical, 2)) if E.gt(physical, 5) else (1 if E.ge(physical, 1) else physical)
amount = E.minimum(physical, E.maximum(0, amount))
if E.le(amount, 0): return
var fraction: Variant = E.clamp_value(E.divide(amount, physical), 0, 1)
if E.gt(entity.destroy_level, 0): world.stellar.destroy_planets(target, id, fraction, entity.destroy_level, "apocalypse")
var products: Dictionary = world.stellar.evolution_products(target, entity, amount, {"explosiveness": WorldParameters.effective(target, "Exp")})
var normalized: Dictionary = {}
for output in products:
normalized[output] = E.divide(products[output], amount)
var incoming: Dictionary = world.ledger.transfer_evolution(target, {id: fraction}, {id: normalized})
world.batches.scale_batches(target, id, E.subtract(1, fraction))
var terminal: String = "SMBH" if E.gt(target.apocalypse.type, 0) else "S"
var terminal_yield: Variant = 0
var new_owners: Dictionary = {}
if normalized.is_empty():
terminal_yield = E.multiply(amount, E.add(1, E.maximum(0, entity.mass))) if E.contains(["Star", "BH"], entity.group_as) else amount
for output in normalized:
if E.eq(output, "blast"):
var environment: Dictionary = world.environment.stellar_environment(target)
world.stellar.apply_blast(target, E.multiply(normalized[output], amount), environment)
continue
if not world.catalog.entities.has(output): continue
var output_amount: Variant = E.maximum(0, products[output])
if E.le(output_amount, 0): continue
output_amount = world.ledger.credit(target, output, output_amount)
var output_entity: OwniverseEntity = world.catalog.entities[output]
if E.gt(output_entity.lifespan, 0) or not output_entity.life_rule.is_empty():
world.batches.append_batch(target, output, output_amount, target.time.datetime, target.time.datetime)
new_owners[output] = E.add(new_owners.get(output, 0), output_amount)
world.ledger.accept_transfers(target, incoming)
world.ledger.bind_new_owners(target, new_owners)
if E.gt(terminal_yield, 0): world.ledger.credit(target, terminal, terminal_yield)
world.ledger.round_object_allocations(target)
func _process_final_energy(target: Dictionary, apocalypse: Dictionary) -> void:
var energy: Variant = E.maximum(0, target.energy)
if E.gt(energy, 0):
var amount: Variant = E.round_value(E.divide(energy, 2)) if E.gt(energy, 5) else (1 if E.ge(energy, 1) else energy)
amount = E.minimum(energy, amount)
target.energy = E.maximum(0, E.subtract(energy, amount))
world.ledger.credit(target, "SMBH" if E.gt(apocalypse.type, 0) else "S", amount)
apocalypse.end_ticks = 0
else:
apocalypse.end_ticks = int(apocalypse.end_ticks) + 1
@@ -0,0 +1 @@
uid://u5hs757qtn8c
+1
View File
@@ -14,6 +14,7 @@ func yearly_products(entity: OwniverseEntity) -> Dictionary:
func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Dictionary, distribution: Dictionary, from_year: Variant, to_year: Variant) -> void: func produce_for_period(previous: Dictionary, target: Dictionary, quantities: Dictionary, distribution: Dictionary, from_year: Variant, to_year: Variant) -> void:
if E.gt(target.get("apocalypse", {}).get("phase", 0), 0): return
var basic: Dictionary = {} var basic: Dictionary = {}
var automatic: Dictionary = {} var automatic: Dictionary = {}
for id in quantities: for id in quantities:
+1
View File
@@ -72,6 +72,7 @@ func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefC
var profile_trends: int = world.profile_start() var profile_trends: int = world.profile_start()
WorldParameters.advance(previous, next, Global.CivilizationRules, E.subtract(to_year, from_year)) WorldParameters.advance(previous, next, Global.CivilizationRules, E.subtract(to_year, from_year))
world.profile_end("trends", profile_trends) world.profile_end("trends", profile_trends)
world.apocalypse.check_density(next)
next.time = clock if not clock.is_empty() else {"datetime": datetime, "year": to_year} next.time = clock if not clock.is_empty() else {"datetime": datetime, "year": to_year}
# Publish only after every phase has finished; previous stays untouched. # Publish only after every phase has finished; previous stays untouched.
+7 -1
View File
@@ -112,6 +112,7 @@ func plan_smbh(desired_amount: Variant) -> Dictionary:
func produce_smbh(desired_amount: Variant) -> Variant: func produce_smbh(desired_amount: Variant) -> Variant:
if E.gt(world.state.get("apocalypse", {}).get("phase", 0), 0): return 0
var plan: Dictionary = plan_smbh(desired_amount) var plan: Dictionary = plan_smbh(desired_amount)
var consumed_mass: Variant = plan.amount var consumed_mass: Variant = plan.amount
var taken: Dictionary = plan.taken var taken: Dictionary = plan.taken
@@ -137,10 +138,12 @@ func produce_smbh(desired_amount: Variant) -> Variant:
world.ledger.clamp_auto_resources(next) world.ledger.clamp_auto_resources(next)
world.state = next world.state = next
world.presentation.refresh(world.state, world.catalog) world.presentation.refresh(world.state, world.catalog)
world.apocalypse.check_density(world.state)
return consumed_mass return consumed_mass
func produce(item_id: String, desired_amount: Variant) -> Variant: func produce(item_id: String, desired_amount: Variant) -> Variant:
if E.gt(world.state.get("apocalypse", {}).get("phase", 0), 0): return 0
if E.eq(item_id, "SMBH"): if E.eq(item_id, "SMBH"):
return produce_smbh(desired_amount) return produce_smbh(desired_amount)
# Use special production for S and H # Use special production for S and H
@@ -171,10 +174,12 @@ func produce(item_id: String, desired_amount: Variant) -> Variant:
world.stellar.create_planets(world.state, item_id, max_producible, world.state.time.datetime, world.state.time.datetime, metallicity) world.stellar.create_planets(world.state, item_id, max_producible, world.state.time.datetime, world.state.time.datetime, metallicity)
world.ledger.refresh_groups(world.state) world.ledger.refresh_groups(world.state)
world.presentation.refresh(world.state, world.catalog) world.presentation.refresh(world.state, world.catalog)
world.apocalypse.check_density(world.state)
return max_producible return max_producible
func produce_hs(entity_id: String, value: Variant) -> Variant: func produce_hs(entity_id: String, value: Variant) -> Variant:
if E.gt(world.state.get("apocalypse", {}).get("phase", 0), 0): return 0
if E.le(value, 0): if E.le(value, 0):
return 0 return 0
@@ -186,7 +191,8 @@ func produce_hs(entity_id: String, value: Variant) -> Variant:
world.state.energy = E.subtract(world.state.energy, to_produce) world.state.energy = E.subtract(world.state.energy, to_produce)
world.ledger.process_entity(entity_id, to_produce) world.ledger.process_entity(entity_id, to_produce)
world.presentation.refresh(world.state, world.catalog)
world.apocalypse.check_density(world.state)
return to_produce return to_produce
+11 -2
View File
@@ -12,6 +12,7 @@ var ledger = preload("res://world/owniverse/object_ledger.gd").new(self)
var production = preload("res://world/owniverse/manual_production.gd").new(self) var production = preload("res://world/owniverse/manual_production.gd").new(self)
var automatic = preload("res://world/owniverse/automatic_production.gd").new(self) var automatic = preload("res://world/owniverse/automatic_production.gd").new(self)
var stellar = preload("res://world/owniverse/stellar_rules.gd").new(self) var stellar = preload("res://world/owniverse/stellar_rules.gd").new(self)
var apocalypse = preload("res://world/owniverse/apocalypse_controller.gd").new(self)
var life = preload("res://world/owniverse/life_evolution.gd").new(self) var life = preload("res://world/owniverse/life_evolution.gd").new(self)
var environment = preload("res://world/owniverse/universe_environment.gd").new(self) var environment = preload("res://world/owniverse/universe_environment.gd").new(self)
var evolution = preload("res://world/owniverse/evolution_coordinator.gd").new(self) var evolution = preload("res://world/owniverse/evolution_coordinator.gd").new(self)
@@ -109,6 +110,7 @@ var state: Dictionary = {
'civilization_stats': {}, 'civilization_stats': {},
'civilization_homes': {}, 'civilization_homes': {},
'ethics': {}, 'ethics': {},
'apocalypse': {'phase': 0, 'type': 0, 'warning': 0, 'stage': 1, 'tick': 0, 'end_ticks': 0},
'stats': {'total_amount': {}}, 'stats': {'total_amount': {}},
} }
@@ -132,6 +134,10 @@ func display_clock() -> Dictionary:
## Keep the requested clock advancing while a transaction spans render frames. ## Keep the requested clock advancing while a transaction spans render frames.
func evolute_frame(delta: float, tree: SceneTree) -> void: func evolute_frame(delta: float, tree: SceneTree) -> void:
if not is_finite(delta) or delta < 0.0 or not is_finite(delta * pow(10.0, speed - 1)): return if not is_finite(delta) or delta < 0.0 or not is_finite(delta * pow(10.0, speed - 1)): return
if E.ge(state.get("apocalypse", {}).get("phase", 0), 2):
frame_clock = state.time.duplicate()
frame_elapsed_seconds = 0.0
return
if frame_clock.is_empty(): frame_clock = state.time.duplicate() if frame_clock.is_empty(): frame_clock = state.time.duplicate()
frame_clock = SimulationTime.advance(frame_clock, delta, speed) frame_clock = SimulationTime.advance(frame_clock, delta, speed)
frame_elapsed_seconds += delta frame_elapsed_seconds += delta
@@ -152,6 +158,7 @@ func evolute_frame(delta: float, tree: SceneTree) -> void:
evolution.run_frame(frame_active_clock, frame_budget) evolution.run_frame(frame_active_clock, frame_budget)
func request_production(id: String, amount: Variant, resource: bool = false) -> bool: func request_production(id: String, amount: Variant, resource: bool = false) -> bool:
if E.gt(state.get("apocalypse", {}).get("phase", 0), 0): return false
if frame_busy or production_queue_busy: if frame_busy or production_queue_busy:
_queued_productions.append({"id": id, "amount": amount, "resource": resource}) _queued_productions.append({"id": id, "amount": amount, "resource": resource})
return true return true
@@ -218,10 +225,12 @@ var speed: int :
get: get:
return _requested_speed if _requested_speed != 0 else state.speed return _requested_speed if _requested_speed != 0 else state.speed
set(value): set(value):
var requested_speed: int = clampi(value, 1, Global.SPEED_LIMIT)
if E.eq(state.get("apocalypse", {}).get("phase", 0), 1): requested_speed = 1
if frame_busy: if frame_busy:
_requested_speed = clampi(value, 1, Global.SPEED_LIMIT) _requested_speed = requested_speed
return return
state.speed = clampi(value, 1, Global.SPEED_LIMIT) state.speed = requested_speed
state.speed_in_year = E.power(10, E.subtract(state.speed, 1)) state.speed_in_year = E.power(10, E.subtract(state.speed, 1))
func evolute(delta: Variant) -> void: func evolute(delta: Variant) -> void: