Refactor global.gd to update SPEED_LIMIT constant from 4 to 5

Enhance stage.gd debug output in _on_topmenu_button_pressed function for better state tracking; comment out unnecessary print statements.

Update owniverse.gd to introduce new state properties: batch_origins, distribution, and auto_resources; refactor evolute function for improved state management and batch processing.

Revise state.gd to clean up debug print statements in add function for better readability and maintainability.
This commit is contained in:
Kirill
2026-09-13 21:09:42 +03:00
parent b44f92aacf
commit c22b21a537
4 changed files with 711 additions and 143 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
extends Node
const SPEED_LIMIT: int = 4
const SPEED_LIMIT: int = 5
var Settings: Dictionary = {
"filename": "save-0001",
+12 -2
View File
@@ -315,12 +315,22 @@ func _change_speed(delta: int) -> void:
func _on_topmenu_button_pressed(ButtonName) -> void:
print("Debug: ", owniverse.state)
print("Debug → ")
#, owniverse.state)
for id in owniverse.state.batches:
var s = owniverse.state.batches[id].size()
if s != 0:
print(id, " → ", s)
#, owniverse.state.batches[id])
if id == 'HG':
print(owniverse.state.batches[id])
#var s: float = 1E15
#var z = 1.0
#for i in range(30):
#print(i, " | ", z, " | ", " | ", s - z)
#z *= 10
print("-------")
func _on_unlock_and_save_timeout() -> void:
@@ -356,7 +366,7 @@ func save_data() -> void:
game_data["state"] = owniverse.state
SaveManager.save_data("owniverse", "qwerty", game_data)
print("saved ", game_data.version, "\n\n\n")
#print("saved ", game_data.version, "\n\n\n")
func init_enitities() -> Dictionary:
+692 -135
View File
@@ -29,8 +29,11 @@ var state: Dictionary = {
'speed_in_year': 1,
'energy': 1E14,
'batches': {},
'batch_origins': {},
'binds': {},
'amount': {},
'distribution': {},
'auto_resources': {},
'stats': {
'total_amount': {},
}
@@ -44,36 +47,617 @@ var speed: int:
return state.speed
set(value):
state.speed = clampi(value, 1, Global.SPEED_LIMIT)
state.speed_in_years = pow(10, state.speed - 1)
print(state.speed, " | ", state.speed_in_years)
state.speed_in_year = pow(10.0, state.speed - 1)
func evolute(delta: float):
func evolute(delta: float) -> void:
if delta <= 0.0 or not is_finite(delta):
return
var new_time: Dictionary = _add_time_delta(delta)
# produce and evolute
state.time = new_time
#new_owniverse.state = state
#new_owniverse.stats = stats
#new_owniverse.entities = entities
#.locked_entities = locked_entities
var previous: Dictionary = state
var next: Dictionary = previous.duplicate_deep()
next.speed = clampi(int(previous.speed), 1, Global.SPEED_LIMIT)
next.speed_in_year = pow(10.0, next.speed - 1)
var datetime: float = previous.time.datetime + delta * next.speed_in_year
var from_year: float = floorf(previous.time.year)
var to_year: float = floorf(datetime)
if to_year > from_year:
var quantities: Dictionary = _physical_amounts(previous)
var distribution: Dictionary = _normalize_distribution(quantities)
next.distribution = distribution
_produce_for_period(previous, next, quantities, distribution, from_year, to_year)
_evolve_batches(next, to_year)
_refresh_groups(next)
_clamp_auto_resources(next)
next.time = {"datetime": datetime, "year": to_year}
# Publish only after every phase has finished; previous stays untouched.
new_state = next
state = next
for id in next.amount:
if next.amount[id] != previous.amount.get(id, 0.0):
items_to_update[id] = next.amount[id]
func _add_time_delta(delta: float) -> Dictionary:
var new_datetime = state.time.datetime + delta * state.speed_in_year
#
#new_owniverse.state.year = floorf(new_owniverse.state.datetime)
var new_time:= {
'datetime': new_datetime,
'year': floorf(new_datetime),
}
#print(new_time)
return new_time
var new_datetime: float = state.time.datetime + delta * pow(10.0, speed - 1)
return {"datetime": new_datetime, "year": floorf(new_datetime)}
func _is_group(id: String) -> bool:
if entity_groups.has(id):
return true
for entity: OwniverseEntity in entities.values():
if entity.group_as == id:
return true
return false
func _physical_amounts(source: Dictionary) -> Dictionary:
var amounts: Dictionary = {}
for id in entities:
if not _is_group(id):
amounts[id] = maxf(0.0, source.amount.get(id, 0.0))
for bindings: Dictionary in source.binds.values():
for id in bindings:
if amounts.has(id):
amounts[id] += maxf(0.0, bindings[id])
return amounts
func _members(id: String) -> Array:
if not _is_group(id):
return [id]
var members: Array = []
for key in entities:
if entities[key].group_as == id:
members.append(key)
return members
func _available(source: Dictionary, id: String) -> float:
var amount: float = 0.0
for member in _members(id):
amount += maxf(0.0, source.amount.get(member, 0.0))
return amount
# Resolve group references to concrete types, proportional to free quantities.
func _expand_requirements(source: Dictionary, requirements: Dictionary) -> Dictionary:
var expanded: Dictionary = {}
for id in requirements:
var weights: Dictionary = {}
for member in _members(id):
weights[member] = source.amount.get(member, 0.0)
weights = _normalize_distribution(weights)
for member in weights:
expanded[member] = expanded.get(member, 0.0) + maxf(0.0, requirements[id]) * weights[member]
return expanded
func _take_available(target: Dictionary, id: String, amount: float) -> Dictionary:
var quantities: Dictionary = {}
for member in _members(id):
quantities[member] = target.amount.get(member, 0.0)
var distribution: Dictionary = _normalize_distribution(quantities)
var taken: Dictionary = {}
var total: float = minf(amount, _available(target, id))
var discrete: bool = not distribution.is_empty()
for member in distribution:
discrete = discrete and _is_discrete(member)
if discrete:
total = floorf(total)
var shares: Array = []
var assigned: float = 0.0
for member in distribution:
var expected: float = total * distribution[member]
taken[member] = floorf(expected) if discrete else expected
assigned += taken[member]
shares.append({"id": member, "rest": expected - taken[member]})
if discrete:
shares.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.rest > b.rest if a.rest != b.rest else a.id < b.id)
for share: Dictionary in shares:
if assigned >= total:
break
taken[share.id] += 1.0
assigned += 1.0
for member in taken:
target.amount[member] = maxf(0.0, target.amount.get(member, 0.0) - taken[member])
return taken
func _scale_batches(target: Dictionary, id: String, factor: float) -> void:
var batches: Array = target.batches.get(id, [])
var total: float = 0.0
var assigned: float = 0.0
var shares: Array = []
for batch: Dictionary in batches:
var expected: float = batch.amount * maxf(0.0, factor)
total += expected
batch.amount = floorf(expected)
assigned += batch.amount
shares.append({"batch": batch, "rest": expected - batch.amount})
shares.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.rest > b.rest)
for share: Dictionary in shares:
if assigned < roundf(total):
share.batch.amount += 1.0
assigned += 1.0
share.batch.withdraw_remainder = minf(share.batch.get("withdraw_remainder", 0.0), share.batch.amount)
func _is_discrete(_id: String) -> bool:
return true
func _credit(target: Dictionary, id: String, amount: float) -> float:
if amount <= 0.0:
return 0.0
if _is_discrete(id):
if not target.has("production_remainders"):
target.production_remainders = {}
var expected: float = amount + target.production_remainders.get(id, 0.0)
amount = floorf(expected)
target.production_remainders[id] = expected - amount
target.amount[id] = target.amount.get(id, 0.0) + amount
target.stats.total_amount[id] = target.stats.total_amount.get(id, 0.0) + amount
if entities.has(id) and entities[id].group_as != "":
var group: String = entities[id].group_as
target.stats.total_amount[group] = target.stats.total_amount.get(group, 0.0) + amount
return amount
# Allocate whole objects between free stock and owners without changing totals.
func _round_object_allocations(target: Dictionary) -> void:
var physical: Dictionary = _physical_amounts(target)
for id in physical:
if not _is_discrete(id):
continue
var slots: Array = [{"owner": "", "value": target.amount.get(id, 0.0)}]
for owner in target.binds:
if target.binds[owner].has(id):
slots.append({"owner": owner, "value": target.binds[owner][id]})
var assigned: float = 0.0
for slot: Dictionary in slots:
slot.whole = floorf(slot.value)
slot.rest = slot.value - slot.whole
assigned += slot.whole
slots.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.rest > b.rest if a.rest != b.rest else a.owner < b.owner)
var left: float = roundf(physical[id]) - assigned
for slot: Dictionary in slots:
if left > 0.0:
slot.whole += 1.0
left -= 1.0
if slot.owner == "":
target.amount[id] = slot.whole
else:
target.binds[slot.owner][id] = slot.whole
func _refresh_groups(target: Dictionary) -> void:
var groups: Dictionary = {}
for id in entities:
var group: String = entities[id].group_as
if group != "":
groups[group] = groups.get(group, 0.0) + target.amount.get(id, 0.0)
for group in groups:
target.amount[group] = groups[group]
func _clamp_auto_resources(target: Dictionary) -> void:
for id in target.get("auto_resources", {}):
target.auto_resources[id] = minf(target.auto_resources[id], _available(target, id))
# Each type collects production into consecutive lifespan_delta-wide creation windows.
# Its evolution window is the creation window shifted forward by one lifespan.
func _append_batch(target: Dictionary, id: String, amount: float,
created_from: float, created_to: float) -> void:
if amount <= 0.0 or not entities.has(id) or entities[id].lifespan <= 0.0:
return
if created_to < created_from:
return
if not target.batches.has(id):
target.batches[id] = []
if not target.has("batch_origins"):
target.batch_origins = {}
var batches: Array = target.batches[id]
var lifespan: float = entities[id].lifespan
var batch_duration: float = entities[id].lifespan_delta
if batch_duration <= 0.0:
return
if not target.batch_origins.has(id):
target.batch_origins[id] = batches[0].get("created_from", created_from) if not batches.is_empty() else created_from
var origin: float = target.batch_origins[id]
var cursor: float = created_from
var duration: float = created_to - created_from
var remaining_amount: float = amount
while true:
var window_index: float = floorf((cursor - origin) / batch_duration)
var window_start: float = origin + window_index * batch_duration
var window_end: float = origin + (window_index + 1.0) * batch_duration
var part_end: float = minf(created_to, window_end)
var part_amount: float = remaining_amount
if duration > 0.0 and part_end < created_to:
part_amount = floorf(amount * (part_end - created_from) / duration) - (amount - remaining_amount)
var batch: Dictionary = {}
for existing: Dictionary in batches:
if existing.get("created_from", -INF) == window_start:
batch = existing
break
if batch.is_empty():
batch = {
"created_from": window_start,
"created_to": window_end,
"start_year": window_start + lifespan,
"end_year": window_end + lifespan,
"amount": 0.0,
}
batches.append(batch)
batch.amount += part_amount
remaining_amount -= part_amount
if duration == 0.0 or part_end >= created_to:
break
cursor = window_end
batches.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.created_from < b.created_from)
# Move the left boundary after each partial withdrawal, keeping a uniform density.
func _withdraw_batches(target: Dictionary, id: String, to_year: float) -> Array:
var expired: Array = []
var remaining: Array = []
for batch: Dictionary in target.batches.get(id, []):
var start: float = maxf(batch.start_year, batch.get("processed_until", batch.start_year))
var end: float = batch.end_year
var amount: float = batch.amount
if amount <= 0.0:
continue
if to_year < start or (to_year == start and end > start):
remaining.append(batch)
continue
var consumed: float = amount
var until: float = minf(to_year, end)
if to_year < end:
var carry: float = batch.get("withdraw_remainder", 0.0)
var expected: float = carry + (amount - carry) * (to_year - start) / (end - start)
consumed = floorf(expected)
batch.withdraw_remainder = expected - consumed
batch.amount = maxf(0.0, amount - consumed)
batch.processed_until = to_year
remaining.append(batch)
if consumed > 0.0:
expired.append({"amount": consumed, "from": start, "to": until})
target.batches[id] = remaining
return expired
func _evolution_outputs(entity: OwniverseEntity) -> Dictionary:
if entity.output_or.is_empty():
return entity.output_and.duplicate(true)
# Expected quantities keep evolution independent of frame rate and RNG order.
var chance: float = clampf(entity.output_or.get("chance", 0.0), 0.0, 1.0)
var outputs: Dictionary = {}
for branch in ["negative", "positive"]:
var weight: float = chance if branch == "positive" else 1.0 - chance
for term in str(entity.output_or.get(branch, "")).split("&"):
var parts: PackedStringArray = term.split(":")
var id: String = parts[0]
var amount: float = float(parts[1]) if parts.size() == 2 else 1.0
if id != "" and weight > 0.0:
outputs[id] = outputs.get(id, 0.0) + amount * weight
return outputs
func _evolve_batches(target: Dictionary, to_year: float) -> void:
# A long cycle may contain several generations; preserve actual birth intervals.
while _evolve_batch_generation(target, to_year):
pass
func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
var quantities: Dictionary = _physical_amounts(target)
var expired: Dictionary = {}
# Withdraw all types before creating successors: iteration order cannot affect them.
for id in target.batches.keys():
if entities.has(id):
expired[id] = _withdraw_batches(target, id, to_year)
var has_expired: bool = false
for parts: Array in expired.values():
if not parts.is_empty():
has_expired = true
break
if not has_expired:
return false
var fractions: Dictionary = {}
var outputs: Dictionary = {}
for id in expired:
var total: float = 0.0
for part: Dictionary in expired[id]:
total += part.amount
fractions[id] = clampf(total / quantities[id], 0.0, 1.0) if quantities.get(id, 0.0) > 0.0 else 0.0
outputs[id] = _evolution_outputs(entities[id])
# Use a frozen binding table so simultaneous transitions cannot cascade by type order.
var bindings: Dictionary = target.binds.duplicate(true)
var incoming: Dictionary = {}
for owner in bindings:
var owner_survival: float = 1.0 - fractions.get(owner, 0.0)
target.binds[owner] = {}
for id in bindings[owner]:
var held: float = bindings[owner][id]
var released: float = held * (1.0 - owner_survival)
target.amount[id] = target.amount.get(id, 0.0) + released
var fraction: float = fractions.get(id, 0.0)
var remaining: float = held * owner_survival * (1.0 - fraction)
target.binds[owner][id] = target.binds[owner].get(id, 0.0) + remaining
for successor in outputs.get(id, {}):
var transferred: float = held * owner_survival * fraction * maxf(0.0, outputs[id][successor])
if not incoming.has(owner):
incoming[owner] = {}
incoming[owner][successor] = incoming[owner].get(successor, 0.0) + transferred
for id in fractions:
target.amount[id] = maxf(0.0, target.amount.get(id, 0.0) * (1.0 - fractions[id]))
var new_owners: Dictionary = {}
for id in expired:
for part: Dictionary in expired[id]:
for successor in outputs[id]:
var amount: float = part.amount * maxf(0.0, outputs[id][successor])
amount = _credit(target, successor, amount)
_append_batch(target, successor, amount, part.from, part.to)
new_owners[successor] = new_owners.get(successor, 0.0) + amount
for owner in incoming:
for id in incoming[owner]:
var transferred: float = minf(incoming[owner][id], target.amount.get(id, 0.0))
target.binds[owner][id] = target.binds[owner].get(id, 0.0) + transferred
target.amount[id] = maxf(0.0, target.amount.get(id, 0.0) - transferred)
_bind_new_owners(target, new_owners)
_round_object_allocations(target)
return true
func _bind_new_owners(target: Dictionary, amounts: Dictionary) -> void:
var requests: Dictionary = {}
for owner in amounts:
if not entities.has(owner):
continue
for id in entities[owner].bind:
var quantity: float = amounts[owner] * maxf(0.0, entities[owner].bind[id])
var weights: Dictionary = {}
for member in _members(id):
weights[member] = target.amount.get(member, 0.0)
weights = _normalize_distribution(weights)
for member in weights:
if not requests.has(member):
requests[member] = {}
requests[member][owner] = requests[member].get(owner, 0.0) + quantity * weights[member]
for id in requests:
var requested: float = 0.0
for quantity in requests[id].values():
requested += quantity
var available: float = _available(target, id)
var factor: float = minf(1.0, available / requested) if requested > 0.0 else 0.0
for owner in requests[id]:
var quantity: float = requests[id][owner] * factor
if not target.binds.has(owner):
target.binds[owner] = {}
target.binds[owner][id] = target.binds[owner].get(id, 0.0) + quantity
target.amount[id] = maxf(0.0, target.amount.get(id, 0.0) - quantity)
# Integrate survival over the period, including partial batch expiry.
func _object_years(source: Dictionary, id: String, quantity: float,
from_year: float, to_year: float) -> float:
var result: float = quantity * (to_year - from_year)
for batch: Dictionary in source.batches.get(id, []):
var start: float = maxf(batch.start_year, batch.get("processed_until", batch.start_year))
var end: float = batch.end_year
result -= batch.amount * (_expired_years(to_year, start, end) - _expired_years(from_year, start, end))
return maxf(0.0, result)
func _expired_years(year: float, start: float, end: float) -> float:
if year <= start:
return 0.0
if end <= start:
return year - start
if year >= end:
return year - end + (end - start) * 0.5
return (year - start) * (year - start) / (2.0 * (end - start))
# Largest-remainder allocation: whole float values, bounded by each request.
func _allocate_whole(requests: Dictionary, limit: float) -> Dictionary:
var total: float = 0.0
for amount in requests.values():
total += floorf(maxf(0.0, amount))
var available: float = floorf(minf(maxf(0.0, limit), total))
var result: Dictionary = {}
var order: Array = []
var assigned: float = 0.0
for id in requests:
var expected: float = floorf(maxf(0.0, requests[id])) * available / total if total > 0.0 else 0.0
result[id] = floorf(expected)
assigned += result[id]
order.append({"id": id, "rest": expected - result[id]})
order.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.rest > b.rest if a.rest != b.rest else a.id < b.id)
for item: Dictionary in order:
if assigned >= available:
break
if result[item.id] < floorf(requests[item.id]):
result[item.id] += 1.0
assigned += 1.0
return result
# The entity currently exposes type/amount fields; life_product can contain
# several resources, each specified as total lifetime output.
func _yearly_products(entity: OwniverseEntity) -> Dictionary:
var products: Dictionary = {}
if not entity.life_product.is_empty() and entity.lifespan > 0.0:
for resource in entity.life_product:
products[resource] = maxf(0.0, entity.life_product[resource]) / entity.lifespan
elif entity.product_per_year_type != "":
products[entity.product_per_year_type] = maxf(0.0, entity.product_per_year_amount)
return products
func _produce_for_period(previous: Dictionary, target: Dictionary, quantities: Dictionary,
distribution: Dictionary, from_year: float, to_year: float) -> void:
var basic: Dictionary = {}
var automatic: Dictionary = {}
for id in quantities:
var entity: OwniverseEntity = entities[id]
var object_years: float = _object_years(previous, id, quantities[id], from_year, to_year)
var auto_share: float = clampf(entity.auto, 0.0, 1.0)
var products: Dictionary = _yearly_products(entity)
for product in products:
var amount: float = object_years * products[product]
basic[product] = basic.get(product, 0.0) + amount * (1.0 - auto_share)
automatic[product] = automatic.get(product, 0.0) + amount * auto_share
# Basic and automatic output each retain their unproduced remainder.
if not target.has("resource_remainders"):
target.resource_remainders = {"basic": {}, "automatic": {}}
var pending: Dictionary = target.resource_remainders
var requests: Dictionary = {}
for id in basic:
pending.basic[id] = pending.basic.get(id, 0.0) + basic[id]
pending.automatic[id] = pending.automatic.get(id, 0.0) + automatic[id]
var totals: Dictionary = {}
for channel in ["basic", "automatic"]:
for id in pending[channel]:
totals[id] = maxf(0.0, pending.basic.get(id, 0.0) + pending.automatic.get(id, 0.0))
for id in totals:
if id == "S" or id == "H":
requests[id] = floorf(totals[id])
var funded: Dictionary = _allocate_whole(requests, floorf(maxf(0.0, previous.energy)))
automatic = {}
target.energy = previous.energy
for id in totals:
var amount: float = floorf(totals[id])
if id == "S" or id == "H":
amount = funded.get(id, 0.0)
target.energy -= amount
# Split the actual whole output, retaining the allocation error for next time.
var auto_ratio: float = clampf(pending.automatic.get(id, 0.0) / totals[id], 0.0, 1.0) if totals[id] > 0.0 else 0.0
var auto_amount: float = floorf(amount * auto_ratio)
pending.automatic[id] = pending.automatic.get(id, 0.0) - auto_amount
pending.basic[id] = pending.basic.get(id, 0.0) - (amount - auto_amount)
automatic[id] = auto_amount
amount = _credit(target, id, amount)
_append_batch(target, id, amount, from_year, to_year)
# Keep unused automatic resources across cycles; they are part of free amounts.
var budget: Dictionary = previous.get("auto_resources", {}).duplicate(true)
for id in automatic:
budget[id] = budget.get(id, 0.0) + automatic[id]
for id in budget:
budget[id] = minf(maxf(0.0, budget[id]), _available(target, id))
target.auto_resources = budget
# One common multiplier preserves the initial count ratios, even under scarcity.
var candidates: Dictionary = {}
for id in distribution:
var entity: OwniverseEntity = entities[id]
if entity.group_as != "Star":
continue
var has_cost: bool = false
for resource in entity.cost:
if entity.cost[resource] > 0.0:
has_cost = true
if has_cost:
candidates[id] = distribution[id]
candidates = _normalize_distribution(candidates)
if candidates.is_empty():
return
var costs: Dictionary = {}
var needs: Dictionary = {}
for id in candidates:
if _auto_capacity(target, entities[id], budget) <= 0.0:
continue
for resource in entities[id].cost:
var cost: float = maxf(0.0, entities[id].cost[resource]) * candidates[id]
costs[resource] = costs.get(resource, 0.0) + cost
needs[resource] = needs.get(resource, 0.0) + cost
for resource in entities[id].bind:
needs[resource] = needs.get(resource, 0.0) + maxf(0.0, entities[id].bind[resource]) * candidates[id]
var scale: float = INF
for resource in costs:
if costs[resource] > 0.0:
scale = minf(scale, budget.get(resource, 0.0) / costs[resource])
for resource in needs:
if needs[resource] > 0.0:
scale = minf(scale, _available(target, resource) / needs[resource])
var concrete_needs: Dictionary = _expand_requirements(target, needs)
for resource in concrete_needs:
if concrete_needs[resource] > 0.0:
scale = minf(scale, _available(target, resource) / concrete_needs[resource])
if not is_finite(scale) or scale <= 0.0:
return
# Rare types are served first; signed count deficits compensate later cycles.
if not target.has("auto_star_deficits"):
target.auto_star_deficits = {}
var deficits: Dictionary = target.auto_star_deficits
var order: Array = candidates.keys()
order.sort_custom(func(a: String, b: String) -> bool:
return candidates[a] < candidates[b] if candidates[a] != candidates[b] else a < b)
var created: Dictionary = {}
var total_created: float = 0.0
for id in order:
var entity: OwniverseEntity = entities[id]
var step: float = _production_step(entity)
if not is_finite(step):
continue
var quota: float = maxf(0.0, candidates[id] * scale + deficits.get(id, 0.0))
var desired: float = floorf(quota / step) * step
var actual: float = minf(desired, _auto_capacity(target, entity, budget))
if actual <= 0.0:
continue
for resource in entity.cost:
var cost: float = roundf(maxf(0.0, entity.cost[resource]) * actual)
budget[resource] = maxf(0.0, budget.get(resource, 0.0) - cost)
var before: Dictionary = _physical_amounts(target)
var taken: Dictionary = _take_available(target, resource, cost)
for consumed_id in taken:
var factor: float = 1.0 - taken[consumed_id] / before[consumed_id] if before.get(consumed_id, 0.0) > 0.0 else 0.0
_scale_batches(target, consumed_id, factor)
_bind_new_owners(target, {id: actual})
_round_object_allocations(target)
created[id] = actual
total_created += actual
for id in candidates:
# Credit only actual production, so retained resources aren't counted twice.
deficits[id] = deficits.get(id, 0.0) + candidates[id] * total_created - created.get(id, 0.0)
for id in created:
_credit(target, id, created[id])
_append_batch(target, id, created[id], from_year, to_year)
func _auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Dictionary) -> float:
var capacity: float = INF
var needs: Dictionary = {}
for resource in entity.cost:
var cost: float = maxf(0.0, entity.cost[resource])
needs[resource] = cost
if cost > 0.0:
capacity = minf(capacity, floorf(budget.get(resource, 0.0) / cost))
for resource in entity.bind:
needs[resource] = needs.get(resource, 0.0) + maxf(0.0, entity.bind[resource])
for resource in needs:
if needs[resource] > 0.0:
capacity = minf(capacity, floorf(_available(source, resource) / needs[resource]))
var concrete: Dictionary = _expand_requirements(source, needs)
for resource in concrete:
if concrete[resource] > 0.0:
capacity = minf(capacity, floorf(_available(source, resource) / concrete[resource]))
var step: float = _production_step(entity)
return floorf(capacity / step) * step if is_finite(capacity) and is_finite(step) else 0.0
# ----------
@@ -96,7 +680,7 @@ func produce(item_id: String, desired_amount: float) -> float:
if desired_amount <= 0:
return 0.0
var max_producible = calculate_max_producible(entity, desired_amount)
var max_producible = floorf(calculate_max_producible(entity, desired_amount))
if max_producible <= 0:
return 0.0
@@ -106,6 +690,7 @@ func produce(item_id: String, desired_amount: float) -> float:
# Add produced entities
_process_enitity(item_id, max_producible)
add_entity_to_batch(item_id, max_producible, state.time.datetime)
return max_producible
@@ -119,14 +704,13 @@ func produce_hs(entity_id: String, value: float) -> float:
return 0.0
var to_produce: float = floorf(value)
to_produce = min(to_produce, state.energy)
to_produce = minf(to_produce, floorf(state.energy))
if to_produce <= 0:
return 0.0
state.energy -= to_produce
_process_enitity(entity_id, to_produce)
state.stats.total_amount[entity_id] = state.stats.total_amount.get(entity_id, 0.0) + to_produce
return to_produce
@@ -135,58 +719,76 @@ func produce_hs(entity_id: String, value: float) -> float:
## @param entity - entity to produce
## @param desired_amount - desired quantity
## @return maximum producible amount considering all constraints
# Find the smallest whole batch whose cost is whole for every resource.
# Decimal costs are represented as integer fractions; combine their denominators.
func _production_step(entity: OwniverseEntity) -> float:
var step: float = 1.0
for value in entity.cost.values():
var cost: float = maxf(0.0, value)
if cost == 0.0:
continue
var decimal: String = String.num(cost, 12)
var point: int = decimal.find(".")
var digits: int = decimal.length() - point - 1 if point >= 0 else 0
var denominator: float = pow(10.0, digits)
var numerator: float = roundf(cost * denominator)
if numerator == 0.0:
return INF
denominator /= _whole_gcd(numerator, denominator)
step = step / _whole_gcd(step, denominator) * denominator
return step
func _whole_gcd(a: float, b: float) -> float:
while b > 0.0:
var remainder: float = fmod(a, b)
a = b
b = remainder
return a
func calculate_max_producible(entity: OwniverseEntity, desired_amount: float = 1.0E150) -> float:
var max_producible: float = desired_amount
# Check cost constraints - resources consumed during production
for cost_item in entity.cost:
if entity.cost[cost_item] == 0:
continue
var available = state.amount.get(cost_item, 0.0)
var max_from_cost = floorf(available / entity.cost[cost_item])
max_producible = min(max_producible, max_from_cost)
# Check bind constraints - resources locked by produced entity
for bind_item in entity.bind:
if entity.bind[bind_item] == 0:
continue
var available = state.amount.get(bind_item, 0.0)
var max_from_bind = floorf(available / entity.bind[bind_item])
max_producible = min(max_producible, max_from_bind)
return max_producible
var requirements: Dictionary = {}
for resource in entity.cost:
requirements[resource] = maxf(0.0, entity.cost[resource])
for resource in entity.bind:
requirements[resource] = requirements.get(resource, 0.0) + maxf(0.0, entity.bind[resource])
var available: float = maxf(0.0, desired_amount)
for resource in requirements:
if requirements[resource] > 0.0:
available = minf(available, floorf(_available(state, resource) / requirements[resource]))
var concrete: Dictionary = _expand_requirements(state, requirements)
for resource in concrete:
if concrete[resource] > 0.0:
available = minf(available, floorf(_available(state, resource) / concrete[resource]))
var step: float = _production_step(entity)
return floorf(available / step) * step if is_finite(step) else 0.0
## Applies cost (consumes resources)
## @param entity - entity being produced
## @param amount - amount to produce
## Consume resources without erasing cumulative production statistics.
func apply_costs(entity: OwniverseEntity, amount: float) -> void:
for cost_item in entity.cost:
if entity.cost[cost_item] == 0:
continue
var cost_amount = amount * entity.cost[cost_item]
_process_enitity(cost_item, -cost_amount)
var costs: Dictionary = {}
for resource in entity.cost:
costs[resource] = roundf(maxf(0.0, entity.cost[resource]) * amount)
for resource in costs:
var quantities: Dictionary = _physical_amounts(state)
var taken: Dictionary = _take_available(state, resource, costs[resource])
for id in taken:
var factor: float = 1.0 - taken[id] / quantities[id] if quantities.get(id, 0.0) > 0.0 else 0.0
_scale_batches(state, id, factor)
items_to_update[id] = state.amount[id]
_refresh_groups(state)
_clamp_auto_resources(state)
## Applies bind (locks resources)
## @param item_id - id of entity being produced
## @param entity - entity being produced
## @param amount - amount to produce
func apply_binds(item_id: String, entity: OwniverseEntity, amount: float) -> void:
for bind_item in entity.bind:
if entity.bind[bind_item] == 0:
continue
var bind_amount = amount * entity.bind[bind_item]
# Initialize binds dictionary for this entity if needed
if not state.binds.has(item_id):
state.binds[item_id] = {}
# Record bound amount
state.binds[item_id][bind_item] = bind_amount
# Decrease available amount (lock it)
_process_enitity(bind_item, -bind_amount)
## Keep concrete bound types, so their ages and transitions remain traceable.
func apply_binds(item_id: String, _entity: OwniverseEntity, amount: float) -> void:
_bind_new_owners(state, {item_id: amount})
_round_object_allocations(state)
_refresh_groups(state)
_clamp_auto_resources(state)
for id in state.amount:
items_to_update[id] = state.amount[id]
## Computes a normalized distribution of dictionary values (sum equals 1.0)
@@ -196,7 +798,7 @@ func _normalize_distribution(values: Dictionary) -> Dictionary:
var normalized_distribution: Dictionary = {}
var distribution_total: float = 0
for id in values:
if values[id] != 0:
if values[id] > 0:
distribution_total += values[id]
normalized_distribution[id] = values[id]
for id in normalized_distribution:
@@ -207,71 +809,26 @@ func _normalize_distribution(values: Dictionary) -> Dictionary:
#region Batches
## Consumes entities from batches based on current year
## Iterates through batches and withdraws amount based on time period:
## - If current_year >= end_year: consume entire batch
## - If current_year in [start_year, end_year): consume proportional amount
## @param entity - entity to consume from
## @param current_year - current year in universe
## @return total amount consumed from all applicable batches
## Withdraws only the previously unprocessed part of each evolution interval.
func consume_from_batches(entity: OwniverseEntity, current_year: float) -> float:
var batches = state.batches[entity.id]
var total_consumed: float = 0.0
# Iterate backwards to safely remove completed batches
state.amount[entity.id] = 0
for i in range(batches.size() - 1, -1, -1):
var batch = batches[i]
# Skip batches that haven't started yet
if current_year < batch["start_year"]:
state.amount[entity.id] += batch["amount"]
continue
# If year is past batch end, consume entire batch
if current_year >= batch["end_year"]:
total_consumed += batch["amount"]
batches.remove_at(i)
else:
# Consume proportional amount based on elapsed time
var batch_duration = batch["end_year"] - batch["start_year"]
var time_elapsed = current_year - batch["start_year"]
var proportion = time_elapsed / batch_duration
var consumed_amount = batch["amount"] * proportion
total_consumed += consumed_amount
batch["amount"] -= consumed_amount
state.amount[entity.id] += batch["amount"]
return total_consumed
var total: float = 0.0
var physical: Dictionary = _physical_amounts(state)
for part: Dictionary in _withdraw_batches(state, entity.id, current_year):
total += part.amount
var fraction: float = clampf(total / physical[entity.id], 0.0, 1.0) if physical.get(entity.id, 0.0) > 0.0 else 0.0
state.amount[entity.id] = state.amount.get(entity.id, 0.0) * (1.0 - fraction)
for owner in state.binds:
if state.binds[owner].has(entity.id):
state.binds[owner][entity.id] *= 1.0 - fraction
_round_object_allocations(state)
_refresh_groups(state)
items_to_update[entity.id] = state.amount[entity.id]
return total
## Adds entity amount to appropriate batch based on current year
## state.batches[entity_id] contains list of batches with intervals and amounts
## Each batch: {start_year, end_year, amount}
## @param entity_id - id of entity to add
## @param amount - quantity of entity being added
## @param current_year - current year in universe
## Add manually created objects to the lifespan_delta window containing current_year.
func add_entity_to_batch(entity_id: String, amount: float, current_year: float) -> void:
var batches = state.batches[entity_id]
# Check if we can add to last batch
var last_batch: Dictionary
if batches.size() > 0:
last_batch = batches[-1]
if batches.size() == 0 or current_year >= last_batch["end_year"]:
# Create new batch based on current_year
last_batch = {
"start_year": current_year,
"end_year": current_year + entities[entity_id].lifespan_delta,
"amount": amount
}
batches.append(last_batch)
else:
# Add to existing batch
last_batch["amount"] += amount
_append_batch(state, entity_id, amount, current_year, current_year)
#endregion
@@ -318,7 +875,7 @@ func _process_enitity(entity_id: String, amount: float) -> void:
# Update state amount
state.amount[entity_id] = state.amount.get(entity_id, 0.0) + amount
state.stats.total_amount[entity_id] += amount
state.stats.total_amount[entity_id] = state.stats.total_amount.get(entity_id, 0.0) + maxf(0.0, amount)
# Update items to display
items_to_update[entity_id] = state.amount[entity_id]
@@ -328,7 +885,7 @@ func _process_enitity(entity_id: String, amount: float) -> void:
if entity.group_as != "":
state.amount[entity.group_as] = state.amount.get(entity.group_as, 0.0) + amount
items_to_update[entity.group_as] = state.amount[entity.group_as]
state.stats.total_amount[entity.group_as] += amount
state.stats.total_amount[entity.group_as] = state.stats.total_amount.get(entity.group_as, 0.0) + maxf(0.0, amount)
+4 -3
View File
@@ -71,9 +71,10 @@ func add(entity: OwniverseEntity, year: float, amount: float):
}
_batch_array.append(_batch)
print('---------')
print(batches)
#print('---------')
#for id in batches:
#print(id)
#
func process(from_year: float, to_year: float) -> void:
# производим