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:
+1
-1
@@ -1,6 +1,6 @@
|
||||
extends Node
|
||||
|
||||
const SPEED_LIMIT: int = 4
|
||||
const SPEED_LIMIT: int = 5
|
||||
|
||||
var Settings: Dictionary = {
|
||||
"filename": "save-0001",
|
||||
|
||||
+12
-2
@@ -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:
|
||||
|
||||
+694
-137
@@ -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):
|
||||
|
||||
var new_time: Dictionary = _add_time_delta(delta)
|
||||
# produce and evolute
|
||||
|
||||
state.time = new_time
|
||||
func evolute(delta: float) -> void:
|
||||
if delta <= 0.0 or not is_finite(delta):
|
||||
return
|
||||
|
||||
#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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
# производим
|
||||
|
||||
Reference in New Issue
Block a user