Implement apocalypse mechanics: add apocalypse queue, controller, and integrate into production logic
This commit is contained in:
@@ -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
|
||||
@@ -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:
|
||||
if E.gt(target.get("apocalypse", {}).get("phase", 0), 0): return
|
||||
var basic: Dictionary = {}
|
||||
var automatic: Dictionary = {}
|
||||
for id in quantities:
|
||||
|
||||
@@ -72,6 +72,7 @@ func evolute_to_datetime(datetime: Variant, clock: Dictionary = {}, budget: RefC
|
||||
var profile_trends: int = world.profile_start()
|
||||
WorldParameters.advance(previous, next, Global.CivilizationRules, E.subtract(to_year, from_year))
|
||||
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}
|
||||
|
||||
# Publish only after every phase has finished; previous stays untouched.
|
||||
|
||||
@@ -112,6 +112,7 @@ func plan_smbh(desired_amount: Variant) -> Dictionary:
|
||||
|
||||
|
||||
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 consumed_mass: Variant = plan.amount
|
||||
var taken: Dictionary = plan.taken
|
||||
@@ -137,10 +138,12 @@ func produce_smbh(desired_amount: Variant) -> Variant:
|
||||
world.ledger.clamp_auto_resources(next)
|
||||
world.state = next
|
||||
world.presentation.refresh(world.state, world.catalog)
|
||||
world.apocalypse.check_density(world.state)
|
||||
return consumed_mass
|
||||
|
||||
|
||||
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"):
|
||||
return produce_smbh(desired_amount)
|
||||
# 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.ledger.refresh_groups(world.state)
|
||||
world.presentation.refresh(world.state, world.catalog)
|
||||
world.apocalypse.check_density(world.state)
|
||||
return max_producible
|
||||
|
||||
|
||||
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):
|
||||
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.ledger.process_entity(entity_id, to_produce)
|
||||
|
||||
world.presentation.refresh(world.state, world.catalog)
|
||||
world.apocalypse.check_density(world.state)
|
||||
return to_produce
|
||||
|
||||
|
||||
|
||||
@@ -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 automatic = preload("res://world/owniverse/automatic_production.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 environment = preload("res://world/owniverse/universe_environment.gd").new(self)
|
||||
var evolution = preload("res://world/owniverse/evolution_coordinator.gd").new(self)
|
||||
@@ -109,6 +110,7 @@ var state: Dictionary = {
|
||||
'civilization_stats': {},
|
||||
'civilization_homes': {},
|
||||
'ethics': {},
|
||||
'apocalypse': {'phase': 0, 'type': 0, 'warning': 0, 'stage': 1, 'tick': 0, 'end_ticks': 0},
|
||||
'stats': {'total_amount': {}},
|
||||
}
|
||||
|
||||
@@ -132,6 +134,10 @@ func display_clock() -> Dictionary:
|
||||
## Keep the requested clock advancing while a transaction spans render frames.
|
||||
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 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()
|
||||
frame_clock = SimulationTime.advance(frame_clock, delta, speed)
|
||||
frame_elapsed_seconds += delta
|
||||
@@ -152,6 +158,7 @@ func evolute_frame(delta: float, tree: SceneTree) -> void:
|
||||
evolution.run_frame(frame_active_clock, frame_budget)
|
||||
|
||||
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:
|
||||
_queued_productions.append({"id": id, "amount": amount, "resource": resource})
|
||||
return true
|
||||
@@ -218,10 +225,12 @@ var speed: int :
|
||||
get:
|
||||
return _requested_speed if _requested_speed != 0 else state.speed
|
||||
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:
|
||||
_requested_speed = clampi(value, 1, Global.SPEED_LIMIT)
|
||||
_requested_speed = requested_speed
|
||||
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))
|
||||
|
||||
func evolute(delta: Variant) -> void:
|
||||
|
||||
Reference in New Issue
Block a user