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
|
extends Node
|
||||||
|
|
||||||
const SPEED_LIMIT: int = 4
|
const SPEED_LIMIT: int = 5
|
||||||
|
|
||||||
var Settings: Dictionary = {
|
var Settings: Dictionary = {
|
||||||
"filename": "save-0001",
|
"filename": "save-0001",
|
||||||
|
|||||||
+12
-2
@@ -315,12 +315,22 @@ func _change_speed(delta: int) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_topmenu_button_pressed(ButtonName) -> 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 s: float = 1E15
|
||||||
#var z = 1.0
|
#var z = 1.0
|
||||||
#for i in range(30):
|
#for i in range(30):
|
||||||
#print(i, " | ", z, " | ", " | ", s - z)
|
#print(i, " | ", z, " | ", " | ", s - z)
|
||||||
#z *= 10
|
#z *= 10
|
||||||
|
print("-------")
|
||||||
|
|
||||||
|
|
||||||
func _on_unlock_and_save_timeout() -> void:
|
func _on_unlock_and_save_timeout() -> void:
|
||||||
@@ -356,7 +366,7 @@ func save_data() -> void:
|
|||||||
game_data["state"] = owniverse.state
|
game_data["state"] = owniverse.state
|
||||||
|
|
||||||
SaveManager.save_data("owniverse", "qwerty", game_data)
|
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:
|
func init_enitities() -> Dictionary:
|
||||||
|
|||||||
+691
-134
@@ -29,8 +29,11 @@ var state: Dictionary = {
|
|||||||
'speed_in_year': 1,
|
'speed_in_year': 1,
|
||||||
'energy': 1E14,
|
'energy': 1E14,
|
||||||
'batches': {},
|
'batches': {},
|
||||||
|
'batch_origins': {},
|
||||||
'binds': {},
|
'binds': {},
|
||||||
'amount': {},
|
'amount': {},
|
||||||
|
'distribution': {},
|
||||||
|
'auto_resources': {},
|
||||||
'stats': {
|
'stats': {
|
||||||
'total_amount': {},
|
'total_amount': {},
|
||||||
}
|
}
|
||||||
@@ -44,36 +47,617 @@ var speed: int:
|
|||||||
return state.speed
|
return state.speed
|
||||||
set(value):
|
set(value):
|
||||||
state.speed = clampi(value, 1, Global.SPEED_LIMIT)
|
state.speed = clampi(value, 1, Global.SPEED_LIMIT)
|
||||||
state.speed_in_years = pow(10, state.speed - 1)
|
state.speed_in_year = pow(10.0, state.speed - 1)
|
||||||
print(state.speed, " | ", state.speed_in_years)
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
var previous: Dictionary = state
|
||||||
# produce and evolute
|
var next: Dictionary = previous.duplicate_deep()
|
||||||
|
next.speed = clampi(int(previous.speed), 1, Global.SPEED_LIMIT)
|
||||||
state.time = new_time
|
next.speed_in_year = pow(10.0, next.speed - 1)
|
||||||
|
var datetime: float = previous.time.datetime + delta * next.speed_in_year
|
||||||
#new_owniverse.state = state
|
var from_year: float = floorf(previous.time.year)
|
||||||
#new_owniverse.stats = stats
|
var to_year: float = floorf(datetime)
|
||||||
#new_owniverse.entities = entities
|
if to_year > from_year:
|
||||||
|
var quantities: Dictionary = _physical_amounts(previous)
|
||||||
#.locked_entities = locked_entities
|
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:
|
func _add_time_delta(delta: float) -> Dictionary:
|
||||||
var new_datetime = state.time.datetime + delta * state.speed_in_year
|
var new_datetime: float = state.time.datetime + delta * pow(10.0, speed - 1)
|
||||||
#
|
return {"datetime": new_datetime, "year": floorf(new_datetime)}
|
||||||
#new_owniverse.state.year = floorf(new_owniverse.state.datetime)
|
|
||||||
var new_time:= {
|
|
||||||
'datetime': new_datetime,
|
func _is_group(id: String) -> bool:
|
||||||
'year': floorf(new_datetime),
|
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,
|
||||||
}
|
}
|
||||||
#print(new_time)
|
batches.append(batch)
|
||||||
return new_time
|
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:
|
if desired_amount <= 0:
|
||||||
return 0.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:
|
if max_producible <= 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
@@ -106,6 +690,7 @@ func produce(item_id: String, desired_amount: float) -> float:
|
|||||||
|
|
||||||
# Add produced entities
|
# Add produced entities
|
||||||
_process_enitity(item_id, max_producible)
|
_process_enitity(item_id, max_producible)
|
||||||
|
add_entity_to_batch(item_id, max_producible, state.time.datetime)
|
||||||
|
|
||||||
return max_producible
|
return max_producible
|
||||||
|
|
||||||
@@ -119,14 +704,13 @@ func produce_hs(entity_id: String, value: float) -> float:
|
|||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
var to_produce: float = floorf(value)
|
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:
|
if to_produce <= 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
state.energy -= to_produce
|
state.energy -= to_produce
|
||||||
_process_enitity(entity_id, 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
|
return to_produce
|
||||||
|
|
||||||
@@ -135,58 +719,76 @@ func produce_hs(entity_id: String, value: float) -> float:
|
|||||||
## @param entity - entity to produce
|
## @param entity - entity to produce
|
||||||
## @param desired_amount - desired quantity
|
## @param desired_amount - desired quantity
|
||||||
## @return maximum producible amount considering all constraints
|
## @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:
|
func calculate_max_producible(entity: OwniverseEntity, desired_amount: float = 1.0E150) -> float:
|
||||||
var max_producible: float = desired_amount
|
var requirements: Dictionary = {}
|
||||||
|
for resource in entity.cost:
|
||||||
# Check cost constraints - resources consumed during production
|
requirements[resource] = maxf(0.0, entity.cost[resource])
|
||||||
for cost_item in entity.cost:
|
for resource in entity.bind:
|
||||||
if entity.cost[cost_item] == 0:
|
requirements[resource] = requirements.get(resource, 0.0) + maxf(0.0, entity.bind[resource])
|
||||||
continue
|
var available: float = maxf(0.0, desired_amount)
|
||||||
var available = state.amount.get(cost_item, 0.0)
|
for resource in requirements:
|
||||||
var max_from_cost = floorf(available / entity.cost[cost_item])
|
if requirements[resource] > 0.0:
|
||||||
max_producible = min(max_producible, max_from_cost)
|
available = minf(available, floorf(_available(state, resource) / requirements[resource]))
|
||||||
|
var concrete: Dictionary = _expand_requirements(state, requirements)
|
||||||
# Check bind constraints - resources locked by produced entity
|
for resource in concrete:
|
||||||
for bind_item in entity.bind:
|
if concrete[resource] > 0.0:
|
||||||
if entity.bind[bind_item] == 0:
|
available = minf(available, floorf(_available(state, resource) / concrete[resource]))
|
||||||
continue
|
var step: float = _production_step(entity)
|
||||||
var available = state.amount.get(bind_item, 0.0)
|
return floorf(available / step) * step if is_finite(step) else 0.0
|
||||||
var max_from_bind = floorf(available / entity.bind[bind_item])
|
|
||||||
max_producible = min(max_producible, max_from_bind)
|
|
||||||
|
|
||||||
return max_producible
|
|
||||||
|
|
||||||
|
|
||||||
## Applies cost (consumes resources)
|
## Consume resources without erasing cumulative production statistics.
|
||||||
## @param entity - entity being produced
|
|
||||||
## @param amount - amount to produce
|
|
||||||
func apply_costs(entity: OwniverseEntity, amount: float) -> void:
|
func apply_costs(entity: OwniverseEntity, amount: float) -> void:
|
||||||
for cost_item in entity.cost:
|
var costs: Dictionary = {}
|
||||||
if entity.cost[cost_item] == 0:
|
for resource in entity.cost:
|
||||||
continue
|
costs[resource] = roundf(maxf(0.0, entity.cost[resource]) * amount)
|
||||||
var cost_amount = amount * entity.cost[cost_item]
|
for resource in costs:
|
||||||
_process_enitity(cost_item, -cost_amount)
|
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)
|
## Keep concrete bound types, so their ages and transitions remain traceable.
|
||||||
## @param item_id - id of entity being produced
|
func apply_binds(item_id: String, _entity: OwniverseEntity, amount: float) -> void:
|
||||||
## @param entity - entity being produced
|
_bind_new_owners(state, {item_id: amount})
|
||||||
## @param amount - amount to produce
|
_round_object_allocations(state)
|
||||||
func apply_binds(item_id: String, entity: OwniverseEntity, amount: float) -> void:
|
_refresh_groups(state)
|
||||||
for bind_item in entity.bind:
|
_clamp_auto_resources(state)
|
||||||
if entity.bind[bind_item] == 0:
|
for id in state.amount:
|
||||||
continue
|
items_to_update[id] = state.amount[id]
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
## Computes a normalized distribution of dictionary values (sum equals 1.0)
|
## 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 normalized_distribution: Dictionary = {}
|
||||||
var distribution_total: float = 0
|
var distribution_total: float = 0
|
||||||
for id in values:
|
for id in values:
|
||||||
if values[id] != 0:
|
if values[id] > 0:
|
||||||
distribution_total += values[id]
|
distribution_total += values[id]
|
||||||
normalized_distribution[id] = values[id]
|
normalized_distribution[id] = values[id]
|
||||||
for id in normalized_distribution:
|
for id in normalized_distribution:
|
||||||
@@ -207,71 +809,26 @@ func _normalize_distribution(values: Dictionary) -> Dictionary:
|
|||||||
|
|
||||||
#region Batches
|
#region Batches
|
||||||
|
|
||||||
## Consumes entities from batches based on current year
|
## Withdraws only the previously unprocessed part of each evolution interval.
|
||||||
## 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
|
|
||||||
func consume_from_batches(entity: OwniverseEntity, current_year: float) -> float:
|
func consume_from_batches(entity: OwniverseEntity, current_year: float) -> float:
|
||||||
var batches = state.batches[entity.id]
|
var total: float = 0.0
|
||||||
var total_consumed: float = 0.0
|
var physical: Dictionary = _physical_amounts(state)
|
||||||
|
for part: Dictionary in _withdraw_batches(state, entity.id, current_year):
|
||||||
# Iterate backwards to safely remove completed batches
|
total += part.amount
|
||||||
state.amount[entity.id] = 0
|
var fraction: float = clampf(total / physical[entity.id], 0.0, 1.0) if physical.get(entity.id, 0.0) > 0.0 else 0.0
|
||||||
for i in range(batches.size() - 1, -1, -1):
|
state.amount[entity.id] = state.amount.get(entity.id, 0.0) * (1.0 - fraction)
|
||||||
var batch = batches[i]
|
for owner in state.binds:
|
||||||
|
if state.binds[owner].has(entity.id):
|
||||||
# Skip batches that haven't started yet
|
state.binds[owner][entity.id] *= 1.0 - fraction
|
||||||
if current_year < batch["start_year"]:
|
_round_object_allocations(state)
|
||||||
state.amount[entity.id] += batch["amount"]
|
_refresh_groups(state)
|
||||||
continue
|
items_to_update[entity.id] = state.amount[entity.id]
|
||||||
|
return total
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
## Adds entity amount to appropriate batch based on current year
|
## Add manually created objects to the lifespan_delta window containing 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
|
|
||||||
func add_entity_to_batch(entity_id: String, amount: float, current_year: float) -> void:
|
func add_entity_to_batch(entity_id: String, amount: float, current_year: float) -> void:
|
||||||
var batches = state.batches[entity_id]
|
_append_batch(state, entity_id, amount, current_year, current_year)
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -318,7 +875,7 @@ func _process_enitity(entity_id: String, amount: float) -> void:
|
|||||||
|
|
||||||
# Update state amount
|
# Update state amount
|
||||||
state.amount[entity_id] = state.amount.get(entity_id, 0.0) + 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
|
# Update items to display
|
||||||
items_to_update[entity_id] = state.amount[entity_id]
|
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 != "":
|
if entity.group_as != "":
|
||||||
state.amount[entity.group_as] = state.amount.get(entity.group_as, 0.0) + amount
|
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]
|
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)
|
_batch_array.append(_batch)
|
||||||
|
|
||||||
print('---------')
|
#print('---------')
|
||||||
print(batches)
|
#for id in batches:
|
||||||
|
#print(id)
|
||||||
|
#
|
||||||
|
|
||||||
func process(from_year: float, to_year: float) -> void:
|
func process(from_year: float, to_year: float) -> void:
|
||||||
# производим
|
# производим
|
||||||
|
|||||||
Reference in New Issue
Block a user