c42940369a
- Updated `enforce_capacity` function to accept an optional `physical` parameter, allowing for more flexible handling of physical amounts. - Modified `initialize_life_batches` to return physical amounts after enforcing capacity, improving data flow. - Enhanced `try_production` to utilize shared physical amounts, reducing redundant calculations. - Adjusted `stellar_environment` to accept an optional `quantities` parameter, streamlining its usage. - Added new test results for physical totals to validate performance improvements across various scenarios.
208 lines
9.8 KiB
GDScript
208 lines
9.8 KiB
GDScript
extends "res://world/owniverse/simulation_module.gd"
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
const CivilizationState = preload("res://world/owniverse/civilization_state.gd")
|
|
const QuantityMath = preload("res://world/owniverse/quantity_math.gd")
|
|
|
|
func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant, automatic: bool = false, physical: Dictionary = {}) -> bool:
|
|
if (E.le(amount, 0) or E.ne(amount, E.floor_value(amount))):
|
|
return false
|
|
var trial: Dictionary = target.duplicate()
|
|
trial.amount = target.amount.duplicate()
|
|
trial.binds = target.binds.duplicate(true)
|
|
trial.auto_resources = target.auto_resources.duplicate()
|
|
trial.batches = target.batches.duplicate()
|
|
# Physical object totals do not change while resources are withdrawn from
|
|
# this transaction. Keep one snapshot instead of scanning every resource.
|
|
var shared_physical: bool = not physical.is_empty()
|
|
if not shared_physical: physical = world.ledger.physical_amounts(trial)
|
|
var next_physical: Dictionary = physical.duplicate() if shared_physical else {}
|
|
var copied_batches: Dictionary = {}
|
|
for resource in entity.cost:
|
|
var cost: Variant = E.round_value(E.multiply(E.maximum(0, entity.cost[resource]), amount))
|
|
if (automatic and E.lt(trial.auto_resources.get(resource, 0), cost)):
|
|
return false
|
|
var taken: Dictionary = world.ledger.take_available(trial, resource, cost)
|
|
var total: Variant = 0
|
|
for quantity in taken.values():
|
|
total = E.add(total, quantity)
|
|
if E.ne(total, cost):
|
|
return false
|
|
if automatic:
|
|
trial.auto_resources[resource] = E.maximum(0, E.subtract(trial.auto_resources.get(resource, 0), cost))
|
|
for id in taken:
|
|
if shared_physical and next_physical.has(id): next_physical[id] = E.subtract(next_physical[id], taken[id])
|
|
# Isolate only batches actually touched by this transaction.
|
|
if (trial.batches.has(id) and (not copied_batches.has(id))):
|
|
trial.batches[id] = trial.batches[id].duplicate(true)
|
|
copied_batches[id] = true
|
|
var factor: Variant = (E.subtract(1, E.divide(taken[id], physical[id])) if E.gt(physical.get(id, 0), 0) else 0)
|
|
world.batches.scale_batches(trial, id, factor)
|
|
if (not world.ledger.bind_new_owners(trial, {entity.id: amount}, true)):
|
|
return false
|
|
world.ledger.refresh_groups(trial)
|
|
world.ledger.clamp_auto_resources(trial)
|
|
for field in ["amount", "binds", "batches", "auto_resources"]:
|
|
if trial.has(field):
|
|
target[field] = trial[field]
|
|
if shared_physical: physical.merge(next_physical, true)
|
|
return true
|
|
|
|
|
|
func smbh_sources() -> Dictionary:
|
|
var masses: Dictionary = {}
|
|
for id in world.catalog.entities:
|
|
var entity: OwniverseEntity = world.catalog.entities[id]
|
|
if (E.eq(entity.count_as, "BH") and E.gt(entity.mass, 0)):
|
|
masses[id] = E.multiply(E.floor_value(E.maximum(0, world.state.amount.get(id, 0))), entity.mass)
|
|
return masses
|
|
|
|
|
|
func smbh_available() -> Variant:
|
|
var mass: Variant = 0
|
|
for value in smbh_sources().values():
|
|
mass = E.add(mass, value)
|
|
return E.floor_value(mass)
|
|
|
|
|
|
func plan_smbh(desired_amount: Variant) -> Dictionary:
|
|
var empty: Dictionary = {"amount": 0, "taken": {}}
|
|
if (E.le(desired_amount, 0) or (not E.finite(desired_amount))):
|
|
return empty
|
|
var total_mass: Variant = smbh_available()
|
|
if E.le(total_mass, 0):
|
|
return empty
|
|
var requested: Variant = E.minimum(E.floor_value(desired_amount), total_mass)
|
|
if E.le(requested, 0):
|
|
return empty
|
|
var masses: Dictionary = smbh_sources()
|
|
var distribution: Dictionary = QuantityMath.normalize_distribution(masses)
|
|
var taken: Dictionary = {}
|
|
var order: Array = []
|
|
var consumed_mass: Variant = 0
|
|
for id in distribution:
|
|
var expected: Variant = E.mul_div(requested, distribution[id], world.catalog.entities[id].mass)
|
|
taken[id] = E.minimum(E.floor_value(expected), E.floor_value(world.state.amount[id]))
|
|
consumed_mass = E.add(consumed_mass, E.multiply(taken[id], world.catalog.entities[id].mass))
|
|
order.append({"id": id, "remainder": E.subtract(expected, taken[id])})
|
|
order.sort_custom(func ( a : Dictionary , b : Dictionary ) -> bool : return (E.gt(a.remainder, b.remainder) if E.ne(a.remainder, b.remainder) else E.lt(str(a.id), str(b.id))))
|
|
# Consume whole BH only; all excess mass becomes SMBH, never disappears.
|
|
for candidate : Dictionary in order:
|
|
if E.ge(consumed_mass, requested):
|
|
break
|
|
var id: String = candidate.id
|
|
if E.lt(taken[id], E.floor_value(world.state.amount[id])):
|
|
taken[id] = E.add(taken[id], 1)
|
|
consumed_mass = E.add(consumed_mass, world.catalog.entities[id].mass)
|
|
if (E.le(consumed_mass, 0) or E.ne(consumed_mass, E.floor_value(consumed_mass))):
|
|
return empty
|
|
return {"amount": consumed_mass, "taken": taken}
|
|
|
|
|
|
func produce_smbh(desired_amount: Variant) -> Variant:
|
|
var plan: Dictionary = plan_smbh(desired_amount)
|
|
var consumed_mass: Variant = plan.amount
|
|
var taken: Dictionary = plan.taken
|
|
if E.le(consumed_mass, 0):
|
|
return 0
|
|
var next: Dictionary = world.state.duplicate(true)
|
|
var physical: Dictionary = world.ledger.physical_amounts(world.state)
|
|
for id in taken:
|
|
if E.le(taken[id], 0):
|
|
continue
|
|
next.amount[id] = E.subtract(next.amount[id], taken[id])
|
|
var fraction: Variant = E.divide(taken[id], physical[id])
|
|
world.batches.scale_batches(next, id, E.subtract(1, fraction))
|
|
# Release the consumed owners' share of any resources they held.
|
|
for resource in next.binds.get(id, {}):
|
|
var released: Variant = E.multiply(next.binds[id][resource], fraction)
|
|
next.binds[id][resource] = E.subtract(next.binds[id][resource], released)
|
|
next.amount[resource] = E.add(next.amount.get(resource, 0), released)
|
|
world.ledger.credit(next, "SMBH", consumed_mass)
|
|
world.batches.append_batch(next, "SMBH", consumed_mass, world.state.time.datetime, world.state.time.datetime)
|
|
world.ledger.round_object_allocations(next)
|
|
world.ledger.refresh_groups(next)
|
|
world.ledger.clamp_auto_resources(next)
|
|
world.state = next
|
|
world.presentation.refresh(world.state, world.catalog)
|
|
return consumed_mass
|
|
|
|
|
|
func produce(item_id: String, desired_amount: Variant) -> Variant:
|
|
if E.eq(item_id, "SMBH"):
|
|
return produce_smbh(desired_amount)
|
|
# Use special production for S and H
|
|
if (E.eq(item_id, "S") or E.eq(item_id, "H")):
|
|
return produce_hs(item_id, desired_amount)
|
|
|
|
var entity: OwniverseEntity = world.catalog.entities[item_id]
|
|
|
|
if E.le(desired_amount, 0):
|
|
return 0
|
|
|
|
var max_producible = E.floor_value(calculate_max_producible(entity, desired_amount))
|
|
var budget: Dictionary = CivilizationState.stellar_budget(world, world.state, Global.CivilizationRules)
|
|
max_producible = CivilizationState.admit_transition(budget, Global.CivilizationRules, "", item_id, max_producible)
|
|
|
|
if E.le(max_producible, 0):
|
|
return 0
|
|
|
|
var metallicity: Variant = world.environment.stellar_environment(world.state).metallicity
|
|
if (not try_production(world.state, entity, max_producible)):
|
|
return 0
|
|
|
|
# Add produced entities
|
|
world.ledger.process_entity(item_id, max_producible)
|
|
world.batches.add_entity_to_batch(item_id, max_producible, world.state.time.datetime)
|
|
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)
|
|
return max_producible
|
|
|
|
|
|
func produce_hs(entity_id: String, value: Variant) -> Variant:
|
|
if E.le(value, 0):
|
|
return 0
|
|
|
|
var to_produce: Variant = E.floor_value(value)
|
|
to_produce = E.minimum(to_produce, E.floor_value(world.state.energy))
|
|
|
|
if E.le(to_produce, 0):
|
|
return 0
|
|
|
|
world.state.energy = E.subtract(world.state.energy, to_produce)
|
|
world.ledger.process_entity(entity_id, to_produce)
|
|
|
|
return to_produce
|
|
|
|
|
|
func production_step(entity: OwniverseEntity) -> Variant:
|
|
return QuantityMath.production_step(E.add(entity.cost.values(), entity.bind.values()))
|
|
|
|
|
|
func calculate_max_producible(entity: OwniverseEntity, desired_amount: Variant = null) -> Variant:
|
|
if E.eq(entity.id, "SMBH"):
|
|
return plan_smbh(smbh_available() if desired_amount == null else desired_amount).amount
|
|
var requirements: Dictionary = {}
|
|
for resource in entity.cost:
|
|
requirements[resource] = E.maximum(0, entity.cost[resource])
|
|
for resource in entity.bind:
|
|
requirements[resource] = E.add(requirements.get(resource, 0), E.maximum(0, entity.bind[resource]))
|
|
var has_requirements: bool = false
|
|
for quantity in requirements.values():
|
|
if E.gt(quantity, 0):
|
|
has_requirements = true
|
|
break
|
|
# No recipe means unavailable for production, not unlimited free objects.
|
|
if (not has_requirements):
|
|
return 0
|
|
var available: Variant = INF if desired_amount == null else E.maximum(0, desired_amount)
|
|
for resource in requirements:
|
|
if E.gt(requirements[resource], 0):
|
|
available = E.minimum(available, E.floor_value(E.divide(world.ledger.available(world.state, resource), requirements[resource])))
|
|
var concrete: Dictionary = world.ledger.expand_requirements(world.state, requirements)
|
|
for resource in concrete:
|
|
if E.gt(concrete[resource], 0):
|
|
available = E.minimum(available, E.floor_value(E.divide(world.ledger.available(world.state, resource), concrete[resource])))
|
|
var step: Variant = production_step(entity)
|
|
return (E.multiply(E.floor_value(E.divide(available, step)), step) if E.finite(step) else 0)
|