Refactor save management; enhance save_data function with verification and plaintext copy options, and update load_data logic for improved error handling
This commit is contained in:
+4
-1
@@ -2,6 +2,9 @@ extends Node
|
|||||||
|
|
||||||
const SPEED_LIMIT: int = 5
|
const SPEED_LIMIT: int = 5
|
||||||
|
|
||||||
|
# Write a readable diagnostic copy next to the encrypted save. Never used for loading.
|
||||||
|
var save_plaintext_copy: bool = true
|
||||||
|
|
||||||
var Settings: Dictionary = {
|
var Settings: Dictionary = {
|
||||||
"filename": "save-0001",
|
"filename": "save-0001",
|
||||||
"sound_volume": 1.0,
|
"sound_volume": 1.0,
|
||||||
@@ -39,7 +42,7 @@ func get_description(item_id: String, items: Array = []) -> String:
|
|||||||
func format_number(value: float, digits: int = 0) -> String:
|
func format_number(value: float, digits: int = 0) -> String:
|
||||||
if value == 0: return ""
|
if value == 0: return ""
|
||||||
if value < 10_000:
|
if value < 10_000:
|
||||||
return "%.*f" % [digits, value]
|
return "%.*f" % [digits, value]
|
||||||
|
|
||||||
var exp_value = int(floor(log(abs(value)) / log(10.0)))
|
var exp_value = int(floor(log(abs(value)) / log(10.0)))
|
||||||
var mantissa = value / pow(10.0, exp_value)
|
var mantissa = value / pow(10.0, exp_value)
|
||||||
|
|||||||
+166
-16
@@ -5,34 +5,184 @@ extends Node
|
|||||||
const SETTING_SECTION := "Settings"
|
const SETTING_SECTION := "Settings"
|
||||||
const SAVE_PATH = "user://settings.cfg"
|
const SAVE_PATH = "user://settings.cfg"
|
||||||
|
|
||||||
|
const FORMAT_VERSION: int = 1
|
||||||
|
const STATE_VERSION: String = "0.1.0"
|
||||||
|
var last_error: String = ""
|
||||||
|
var blocked_saves: Dictionary = {}
|
||||||
|
|
||||||
#region Save Load routines
|
#region Save Load routines
|
||||||
|
|
||||||
|
func _fail(message: String) -> bool:
|
||||||
|
last_error = message
|
||||||
|
push_error(message)
|
||||||
|
return false
|
||||||
|
|
||||||
func save_data(filename: String, password: String, data: Dictionary) -> void:
|
|
||||||
filename = "user://" + filename + ".adventure"
|
func save_data(filename: String, password: String, data: Dictionary) -> bool:
|
||||||
var file := FileAccess.open_encrypted_with_pass(filename, FileAccess.WRITE, password)
|
last_error = ""
|
||||||
|
if blocked_saves.has(filename):
|
||||||
|
return _fail("Save blocked to preserve an unreadable save: " + filename)
|
||||||
|
var payload: Dictionary = data.duplicate(true)
|
||||||
|
payload.format_version = FORMAT_VERSION
|
||||||
|
payload.app_version = str(ProjectSettings.get_setting("application/config/version", ""))
|
||||||
|
var problem: String = validate_data(payload)
|
||||||
|
if problem != "":
|
||||||
|
return _fail("Invalid save: " + problem)
|
||||||
|
var path: String = "user://" + filename + ".adventure"
|
||||||
|
var temporary: String = path + ".tmp"
|
||||||
|
var file := FileAccess.open_encrypted_with_pass(temporary, FileAccess.WRITE, password)
|
||||||
if file == null:
|
if file == null:
|
||||||
push_error("Can't write to the file: ", filename)
|
return _fail("Cannot write encrypted save: " + temporary)
|
||||||
return
|
file.store_var(payload, false)
|
||||||
|
|
||||||
file.store_var(data) # бинарно + быстрее + сериализация
|
|
||||||
file.close()
|
file.close()
|
||||||
|
# Check the actual encrypted file, including finalization, before replacing the old save.
|
||||||
|
var verification := FileAccess.open_encrypted_with_pass(temporary, FileAccess.READ, password)
|
||||||
|
if verification == null:
|
||||||
|
return _fail("Cannot verify encrypted save: " + temporary)
|
||||||
|
var restored: Variant = verification.get_var(false)
|
||||||
|
verification.close()
|
||||||
|
if not restored is Dictionary or restored != payload:
|
||||||
|
return _fail("Encrypted save verification failed: " + temporary)
|
||||||
|
if DirAccess.rename_absolute(temporary, path) != OK:
|
||||||
|
return _fail("Cannot replace encrypted save: " + path)
|
||||||
|
if Global.save_plaintext_copy:
|
||||||
|
var plain_path: String = path + ".json"
|
||||||
|
file = FileAccess.open(plain_path + ".tmp", FileAccess.WRITE)
|
||||||
|
if file == null:
|
||||||
|
return _fail("Encrypted save written, but cannot write diagnostic copy: " + plain_path)
|
||||||
|
file.store_string(JSON.stringify(payload, " ", true, true) + "\n")
|
||||||
|
file.flush()
|
||||||
|
var write_error: Error = file.get_error()
|
||||||
|
file.close()
|
||||||
|
if write_error != OK or DirAccess.rename_absolute(plain_path + ".tmp", plain_path) != OK:
|
||||||
|
return _fail("Encrypted save written, but diagnostic copy failed: " + plain_path)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
func load_data(filename: String, password: String) -> Dictionary:
|
func load_data(filename: String, password: String) -> Dictionary:
|
||||||
filename = "user://" + filename + ".adventure"
|
last_error = ""
|
||||||
if not FileAccess.file_exists(filename):
|
# Plaintext is diagnostic only: never use it as a fallback.
|
||||||
|
var path: String = "user://" + filename + ".adventure"
|
||||||
|
if not FileAccess.file_exists(path):
|
||||||
return {}
|
return {}
|
||||||
|
var file := FileAccess.open_encrypted_with_pass(path, FileAccess.READ, password)
|
||||||
var file := FileAccess.open_encrypted_with_pass(filename, FileAccess.READ, password)
|
|
||||||
if file == null:
|
if file == null:
|
||||||
push_error(filename, " couldn't be decrypted.")
|
blocked_saves[filename] = true
|
||||||
|
_fail("Cannot decrypt save: " + path)
|
||||||
return {}
|
return {}
|
||||||
|
var data: Variant = file.get_var(false)
|
||||||
var data: Variant = file.get_var()
|
|
||||||
file.close()
|
file.close()
|
||||||
|
var problem: String = validate_data(data)
|
||||||
return data if data is Dictionary else {}
|
if problem != "":
|
||||||
|
blocked_saves[filename] = true
|
||||||
|
_fail("Invalid save " + path + ": " + problem)
|
||||||
|
return {}
|
||||||
|
blocked_saves.erase(filename)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
func _number(value: Variant, whole: bool = false, nonnegative: bool = true) -> bool:
|
||||||
|
if not (value is int or value is float) or not is_finite(float(value)):
|
||||||
|
return false
|
||||||
|
return (not whole or float(value) == floorf(float(value))) and (not nonnegative or value >= 0)
|
||||||
|
|
||||||
|
|
||||||
|
func _number_map(value: Variant, whole: bool = false, nonnegative: bool = true) -> bool:
|
||||||
|
if not value is Dictionary:
|
||||||
|
return false
|
||||||
|
for key in value:
|
||||||
|
if not (key is String or key is StringName) or not _number(value[key], whole, nonnegative):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _plain_tree(value: Variant) -> bool:
|
||||||
|
if value is Dictionary:
|
||||||
|
for key in value:
|
||||||
|
if not (key is String or key is StringName) or not _plain_tree(value[key]):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
if value is Array:
|
||||||
|
for item in value:
|
||||||
|
if not _plain_tree(item):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
return value == null or value is String or value is StringName or value is bool or _number(value, false, false)
|
||||||
|
|
||||||
|
|
||||||
|
## Return a diagnostic path on failure; an empty string means the structure is valid.
|
||||||
|
func validate_data(data: Variant) -> String:
|
||||||
|
if not data is Dictionary or not data.get("state") is Dictionary:
|
||||||
|
return "state must be a Dictionary"
|
||||||
|
if not _plain_tree(data):
|
||||||
|
return "unsupported value or non-finite number"
|
||||||
|
# Legacy files contained only {state}; accept their known state schema.
|
||||||
|
if data.has("format_version") and (not data.format_version is int or data.format_version != FORMAT_VERSION):
|
||||||
|
return "unsupported format_version"
|
||||||
|
if data.has("ui"):
|
||||||
|
if not data.ui is Dictionary or not data.ui.get("current_page") is int or data.ui.current_page < 0:
|
||||||
|
return "invalid ui.current_page"
|
||||||
|
var saved: Dictionary = data.state
|
||||||
|
if saved.get("version") != STATE_VERSION:
|
||||||
|
return "unsupported state.version"
|
||||||
|
if not saved.get("time") is Dictionary:
|
||||||
|
return "state.time must be a Dictionary"
|
||||||
|
if not _number(saved.time.get("datetime")) or not _number(saved.time.get("year"), true):
|
||||||
|
return "invalid state.time"
|
||||||
|
if saved.time.year != floorf(saved.time.datetime):
|
||||||
|
return "state.time.year must equal floor(datetime)"
|
||||||
|
if not _number(saved.get("speed"), true) or saved.speed < 1 or saved.speed > Global.SPEED_LIMIT:
|
||||||
|
return "invalid state.speed"
|
||||||
|
if not _number(saved.get("speed_in_year")) or saved.speed_in_year != pow(10.0, saved.speed - 1):
|
||||||
|
return "invalid state.speed_in_year"
|
||||||
|
if not _number(saved.get("energy"), true):
|
||||||
|
return "invalid state.energy"
|
||||||
|
if not _number_map(saved.get("amount"), true):
|
||||||
|
return "invalid state.amount"
|
||||||
|
if not saved.get("stats") is Dictionary or not _number_map(saved.stats.get("total_amount"), true):
|
||||||
|
return "invalid state.stats.total_amount"
|
||||||
|
for id in saved.amount:
|
||||||
|
if not saved.stats.total_amount.has(id):
|
||||||
|
return "missing total_amount for " + id
|
||||||
|
if not saved.get("binds") is Dictionary or not saved.get("batches") is Dictionary:
|
||||||
|
return "state.binds and state.batches must be Dictionaries"
|
||||||
|
for owner in saved.binds:
|
||||||
|
if not saved.amount.has(owner) or not _number_map(saved.binds[owner], true):
|
||||||
|
return "invalid state.binds." + owner
|
||||||
|
for id in saved.binds[owner]:
|
||||||
|
if not saved.amount.has(id):
|
||||||
|
return "unknown bound resource " + id
|
||||||
|
for id in saved.batches:
|
||||||
|
if not saved.amount.has(id) or not saved.batches[id] is Array:
|
||||||
|
return "invalid state.batches." + id
|
||||||
|
for batch in saved.batches[id]:
|
||||||
|
if not batch is Dictionary:
|
||||||
|
return "batch must be a Dictionary"
|
||||||
|
if not _number(batch.get("amount"), true) or not _number(batch.get("start_year")) or not _number(batch.get("end_year")):
|
||||||
|
return "invalid batch in " + id
|
||||||
|
if batch.end_year < batch.start_year:
|
||||||
|
return "reversed batch interval in " + id
|
||||||
|
if batch.has("created_from") or batch.has("created_to"):
|
||||||
|
if not _number(batch.get("created_from")) or not _number(batch.get("created_to")):
|
||||||
|
return "invalid batch creation interval in " + id
|
||||||
|
if batch.created_to <= batch.created_from or batch.start_year < batch.created_from:
|
||||||
|
return "reversed batch creation interval in " + id
|
||||||
|
if batch.has("processed_until"):
|
||||||
|
if not _number(batch.processed_until) or batch.processed_until < batch.start_year or batch.processed_until > batch.end_year:
|
||||||
|
return "invalid batch.processed_until in " + id
|
||||||
|
if batch.has("withdraw_remainder") and (not _number(batch.withdraw_remainder) or batch.withdraw_remainder > batch.amount):
|
||||||
|
return "invalid batch.withdraw_remainder in " + id
|
||||||
|
for field in ["batch_origins", "distribution", "auto_resources", "production_remainders", "auto_star_deficits", "evolution_remainders"]:
|
||||||
|
if saved.has(field) and not _number_map(saved[field], field == "auto_resources", field != "auto_star_deficits"):
|
||||||
|
return "invalid state." + field
|
||||||
|
if saved.has("resource_remainders"):
|
||||||
|
if not saved.resource_remainders is Dictionary:
|
||||||
|
return "invalid state.resource_remainders"
|
||||||
|
for channel in ["basic", "automatic"]:
|
||||||
|
# Signed allocation errors are valid, even though resource balances are whole.
|
||||||
|
if not _number_map(saved.resource_remainders.get(channel), false, false):
|
||||||
|
return "invalid state.resource_remainders." + channel
|
||||||
|
return ""
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
+54
-23
@@ -71,6 +71,8 @@ var g_counters: Dictionary[String, Counter] = {
|
|||||||
|
|
||||||
var g_active_items: Dictionary = {}
|
var g_active_items: Dictionary = {}
|
||||||
|
|
||||||
|
var pending_page: int = -1
|
||||||
|
|
||||||
var owniverse:Owniverse = Owniverse.new()
|
var owniverse:Owniverse = Owniverse.new()
|
||||||
var owniverse_items: Dictionary[String, OwniverseItem] = {}
|
var owniverse_items: Dictionary[String, OwniverseItem] = {}
|
||||||
var locked_items: Array = []
|
var locked_items: Array = []
|
||||||
@@ -83,8 +85,10 @@ func set_datetime(value: float):
|
|||||||
|
|
||||||
|
|
||||||
func set_owniverse_item_value() -> void:
|
func set_owniverse_item_value() -> void:
|
||||||
for _item_id in owniverse_items:
|
for item_id in owniverse.items_to_update:
|
||||||
owniverse_items[_item_id].set_label(owniverse.state.amount[_item_id])
|
if owniverse_items.has(item_id):
|
||||||
|
owniverse_items[item_id].set_label(owniverse.items_to_update[item_id])
|
||||||
|
owniverse.items_to_update.clear()
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -93,16 +97,21 @@ func set_owniverse_item_value() -> void:
|
|||||||
func _unlock_items():
|
func _unlock_items():
|
||||||
if locked_items.size() == 0: return
|
if locked_items.size() == 0: return
|
||||||
var _new_locked_items = []
|
var _new_locked_items = []
|
||||||
|
var display_totals: Dictionary = owniverse.get_item_amounts(true)
|
||||||
for _item_id in locked_items:
|
for _item_id in locked_items:
|
||||||
#print(_item_id ' | ', _entity.unlock)
|
#print(_item_id ' | ', _entity.unlock)
|
||||||
var _flag_to_unlock = true
|
var _flag_to_unlock = true
|
||||||
for _elem in owniverse.entities[_item_id].unlock:
|
for _elem in owniverse.entities[_item_id].unlock:
|
||||||
if owniverse.state.stats.total_amount[_elem] < owniverse.entities[_item_id].unlock[_elem]:
|
if maxf(owniverse.state.stats.total_amount.get(_elem, 0.0), display_totals.get(_elem, 0.0)) < owniverse.entities[_item_id].unlock[_elem]:
|
||||||
_flag_to_unlock = false
|
_flag_to_unlock = false
|
||||||
break
|
break
|
||||||
|
|
||||||
if _flag_to_unlock:
|
if _flag_to_unlock:
|
||||||
owniverse_items[_item_id].set_visible(true)
|
var item_id: String = owniverse.entities[_item_id].count_as
|
||||||
|
if item_id == "":
|
||||||
|
item_id = _item_id
|
||||||
|
if owniverse_items.has(item_id):
|
||||||
|
owniverse_items[item_id].set_visible(true)
|
||||||
else:
|
else:
|
||||||
_new_locked_items.append(_item_id)
|
_new_locked_items.append(_item_id)
|
||||||
locked_items = _new_locked_items
|
locked_items = _new_locked_items
|
||||||
@@ -268,6 +277,11 @@ func _ready() -> void:
|
|||||||
locked_items = inited_result.locked_items
|
locked_items = inited_result.locked_items
|
||||||
load_data()
|
load_data()
|
||||||
_init_owniverse_items()
|
_init_owniverse_items()
|
||||||
|
owniverse.refresh_item_amounts()
|
||||||
|
_change_speed(0)
|
||||||
|
set_datetime(owniverse.state.time.year)
|
||||||
|
set_owniverse_item_value()
|
||||||
|
_unlock_items()
|
||||||
|
|
||||||
g_counters.swipe.sec = 0.2
|
g_counters.swipe.sec = 0.2
|
||||||
|
|
||||||
@@ -279,6 +293,7 @@ func _ready() -> void:
|
|||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
|
||||||
owniverse.evolute(delta)
|
owniverse.evolute(delta)
|
||||||
|
set_owniverse_item_value()
|
||||||
set_datetime(owniverse.state.time.year)
|
set_datetime(owniverse.state.time.year)
|
||||||
|
|
||||||
#owniverse.cycle(delta)
|
#owniverse.cycle(delta)
|
||||||
@@ -342,31 +357,47 @@ func _on_unlock_and_save_timeout() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func load_data() -> bool:
|
func load_data() -> bool:
|
||||||
# Получаем версию приложения
|
|
||||||
var app_version = ProjectSettings.get_setting("application/config/version")
|
|
||||||
print("Версия приложения: ", app_version)
|
|
||||||
|
|
||||||
var game_data: Dictionary = SaveManager.load_data("owniverse", "qwerty")
|
var game_data: Dictionary = SaveManager.load_data("owniverse", "qwerty")
|
||||||
if game_data == {}: return false
|
if game_data.is_empty():
|
||||||
#if game_data.get('version', '0.0.1') != 'app_version': return false
|
|
||||||
if game_data.get('version', str(app_version)) != '0.1.0':
|
|
||||||
print(game_data.get('version', 7))
|
|
||||||
print('wrong load version')
|
|
||||||
return false
|
return false
|
||||||
|
var restored: Dictionary = game_data.state
|
||||||
owniverse.state = game_data["state"]
|
# Current definitions are initialized first; reject incompatible saves before publishing.
|
||||||
|
for id in owniverse.state.amount:
|
||||||
for item in owniverse.state.amount:
|
if not restored.amount.has(id) or not restored.stats.total_amount.has(id):
|
||||||
owniverse.items_to_update[item] = owniverse.state.amount[item]
|
SaveManager.blocked_saves["owniverse"] = true
|
||||||
|
push_error("Save is missing the current entity: " + id)
|
||||||
|
return false
|
||||||
|
var complete: Dictionary = owniverse.state.duplicate(true)
|
||||||
|
complete.merge(restored, true)
|
||||||
|
owniverse.state = complete
|
||||||
|
owniverse.new_state = complete
|
||||||
|
owniverse.refresh_item_amounts()
|
||||||
|
pending_page = clampi(int(game_data.get("ui", {}).get("current_page", 0)), 0, PAGE_COUNT - 1)
|
||||||
|
_restore_page.call_deferred()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _restore_page() -> void:
|
||||||
|
# Wait until containers have calculated the scrollable width.
|
||||||
|
await get_tree().process_frame
|
||||||
|
if pending_page >= 0:
|
||||||
|
stage_page_scroller.scroll_horizontal = pending_page * PAGE_WIDTH
|
||||||
|
pending_page = -1
|
||||||
|
|
||||||
|
|
||||||
func save_data() -> void:
|
func save_data() -> void:
|
||||||
var game_data: Dictionary = {}
|
var page: int = pending_page
|
||||||
game_data["state"] = owniverse.state
|
if page < 0:
|
||||||
|
page = clampi(roundi(float(stage_page_scroller.scroll_horizontal) / PAGE_WIDTH), 0, PAGE_COUNT - 1)
|
||||||
SaveManager.save_data("owniverse", "qwerty", game_data)
|
SaveManager.save_data("owniverse", "qwerty", {
|
||||||
#print("saved ", game_data.version, "\n\n\n")
|
"state": owniverse.state,
|
||||||
|
"ui": {"current_page": page},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
if is_node_ready() and (what == NOTIFICATION_WM_CLOSE_REQUEST or what == NOTIFICATION_APPLICATION_PAUSED):
|
||||||
|
save_data()
|
||||||
|
|
||||||
|
|
||||||
func init_enitities() -> Dictionary:
|
func init_enitities() -> Dictionary:
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ func set_actions(item_id: String, value: float) -> void:
|
|||||||
_set_action_values(_get_distribution(value))
|
_set_action_values(_get_distribution(value))
|
||||||
|
|
||||||
func _get_distribution(value: float) -> Array:
|
func _get_distribution(value: float) -> Array:
|
||||||
|
if value <= 0.0 or not is_finite(value):
|
||||||
|
return [0.0, 0.0, 0.0, 0.0, 0.0]
|
||||||
var distribution: Array = [value, 0, 0, 0, 0]
|
var distribution: Array = [value, 0, 0, 0, 0]
|
||||||
|
|
||||||
var exp_value = int(floor(log(abs(value)) / log(10.0)))
|
var exp_value = int(floor(log(abs(value)) / log(10.0)))
|
||||||
|
|||||||
+154
-43
@@ -75,9 +75,32 @@ func evolute(delta: float) -> void:
|
|||||||
# Publish only after every phase has finished; previous stays untouched.
|
# Publish only after every phase has finished; previous stays untouched.
|
||||||
new_state = next
|
new_state = next
|
||||||
state = next
|
state = next
|
||||||
for id in next.amount:
|
if to_year > from_year:
|
||||||
if next.amount[id] != previous.amount.get(id, 0.0):
|
refresh_item_amounts()
|
||||||
items_to_update[id] = next.amount[id]
|
|
||||||
|
|
||||||
|
## Aggregate only for presentation; never write aliases back into simulation state.
|
||||||
|
func get_item_amounts(total_produced: bool = false) -> Dictionary[String, float]:
|
||||||
|
var source: Dictionary = state.stats.total_amount if total_produced else state.amount
|
||||||
|
var result: Dictionary[String, float] = {}
|
||||||
|
for id in source:
|
||||||
|
result[id] = 0.0
|
||||||
|
for id in source:
|
||||||
|
# group_as counters are already sums and must not be counted a second time.
|
||||||
|
if _is_group(id):
|
||||||
|
continue
|
||||||
|
var item_id: String = id
|
||||||
|
if entities.has(id) and entities[id].count_as != "":
|
||||||
|
item_id = entities[id].count_as
|
||||||
|
result[item_id] = result.get(item_id, 0.0) + float(source[id])
|
||||||
|
for id in source:
|
||||||
|
if _is_group(id):
|
||||||
|
result[id] = float(source[id])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func refresh_item_amounts() -> void:
|
||||||
|
items_to_update = get_item_amounts()
|
||||||
|
|
||||||
|
|
||||||
func _add_time_delta(delta: float) -> Dictionary:
|
func _add_time_delta(delta: float) -> Dictionary:
|
||||||
@@ -335,20 +358,31 @@ func _withdraw_batches(target: Dictionary, id: String, to_year: float) -> Array:
|
|||||||
return expired
|
return expired
|
||||||
|
|
||||||
|
|
||||||
func _evolution_outputs(entity: OwniverseEntity) -> Dictionary:
|
func _add_output_terms(outputs: Dictionary, terms: String, count: float) -> void:
|
||||||
if entity.output_or.is_empty():
|
if count <= 0.0:
|
||||||
return entity.output_and.duplicate(true)
|
return
|
||||||
# Expected quantities keep evolution independent of frame rate and RNG order.
|
for term in terms.split("&"):
|
||||||
var chance: float = clampf(entity.output_or.get("chance", 0.0), 0.0, 1.0)
|
var parts: PackedStringArray = term.strip_edges().split(":")
|
||||||
|
var id: String = parts[0].strip_edges()
|
||||||
|
var amount: float = float(parts[1]) if parts.size() == 2 else 1.0
|
||||||
|
if id != "" and amount > 0.0:
|
||||||
|
outputs[id] = outputs.get(id, 0.0) + count * amount
|
||||||
|
|
||||||
|
|
||||||
|
func _evolution_products(target: Dictionary, entity: OwniverseEntity, count: float) -> Dictionary:
|
||||||
var outputs: Dictionary = {}
|
var outputs: Dictionary = {}
|
||||||
for branch in ["negative", "positive"]:
|
for id in entity.output_and:
|
||||||
var weight: float = chance if branch == "positive" else 1.0 - chance
|
outputs[id] = count * maxf(0.0, entity.output_and[id])
|
||||||
for term in str(entity.output_or.get(branch, "")).split("&"):
|
if not entity.output_or.is_empty():
|
||||||
var parts: PackedStringArray = term.split(":")
|
# The parser stores the percentage after '%', e.g. 0.1 means 0.1 percent.
|
||||||
var id: String = parts[0]
|
var chance: float = clampf(entity.output_or.get("chance", 0.0) / 100.0, 0.0, 1.0)
|
||||||
var amount: float = float(parts[1]) if parts.size() == 2 else 1.0
|
if not target.has("evolution_remainders"):
|
||||||
if id != "" and weight > 0.0:
|
target.evolution_remainders = {}
|
||||||
outputs[id] = outputs.get(id, 0.0) + amount * weight
|
var expected: float = count * chance + target.evolution_remainders.get(entity.id, 0.0)
|
||||||
|
var positive: float = minf(count, floorf(snappedf(expected, 1.0e-12)))
|
||||||
|
target.evolution_remainders[entity.id] = maxf(0.0, expected - positive)
|
||||||
|
_add_output_terms(outputs, str(entity.output_or.get("positive", "")), positive)
|
||||||
|
_add_output_terms(outputs, str(entity.output_or.get("negative", "")), count - positive)
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
|
|
||||||
@@ -379,7 +413,15 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
|
|||||||
for part: Dictionary in expired[id]:
|
for part: Dictionary in expired[id]:
|
||||||
total += part.amount
|
total += part.amount
|
||||||
fractions[id] = clampf(total / quantities[id], 0.0, 1.0) if quantities.get(id, 0.0) > 0.0 else 0.0
|
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])
|
var combined: Dictionary = {}
|
||||||
|
for part: Dictionary in expired[id]:
|
||||||
|
part.products = _evolution_products(target, entities[id], part.amount)
|
||||||
|
for successor in part.products:
|
||||||
|
combined[successor] = combined.get(successor, 0.0) + part.products[successor]
|
||||||
|
outputs[id] = {}
|
||||||
|
if total > 0.0:
|
||||||
|
for successor in combined:
|
||||||
|
outputs[id][successor] = combined[successor] / total
|
||||||
|
|
||||||
# Use a frozen binding table so simultaneous transitions cannot cascade by type order.
|
# Use a frozen binding table so simultaneous transitions cannot cascade by type order.
|
||||||
var bindings: Dictionary = target.binds.duplicate(true)
|
var bindings: Dictionary = target.binds.duplicate(true)
|
||||||
@@ -406,8 +448,8 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
|
|||||||
var new_owners: Dictionary = {}
|
var new_owners: Dictionary = {}
|
||||||
for id in expired:
|
for id in expired:
|
||||||
for part: Dictionary in expired[id]:
|
for part: Dictionary in expired[id]:
|
||||||
for successor in outputs[id]:
|
for successor in part.products:
|
||||||
var amount: float = part.amount * maxf(0.0, outputs[id][successor])
|
var amount: float = part.products[successor]
|
||||||
amount = _credit(target, successor, amount)
|
amount = _credit(target, successor, amount)
|
||||||
_append_batch(target, successor, amount, part.from, part.to)
|
_append_batch(target, successor, amount, part.from, part.to)
|
||||||
new_owners[successor] = new_owners.get(successor, 0.0) + amount
|
new_owners[successor] = new_owners.get(successor, 0.0) + amount
|
||||||
@@ -421,7 +463,74 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _bind_new_owners(target: Dictionary, amounts: Dictionary) -> void:
|
# Plan all bindings on a copy, reserving specific types before broader groups.
|
||||||
|
func _bind_full(target: Dictionary, amounts: Dictionary) -> bool:
|
||||||
|
var trial: Dictionary = target.duplicate(true)
|
||||||
|
var requests: Array = []
|
||||||
|
for owner in amounts:
|
||||||
|
if not entities.has(owner):
|
||||||
|
return false
|
||||||
|
for id in entities[owner].bind:
|
||||||
|
var quantity: float = amounts[owner] * maxf(0.0, entities[owner].bind[id])
|
||||||
|
if not is_equal_approx(quantity, roundf(quantity)):
|
||||||
|
return false
|
||||||
|
if quantity > 0.0:
|
||||||
|
requests.append({"owner": owner, "id": id, "amount": roundf(quantity), "members": _members(id).size()})
|
||||||
|
requests.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||||
|
if a.members != b.members:
|
||||||
|
return a.members < b.members
|
||||||
|
return a.id < b.id if a.id != b.id else a.owner < b.owner)
|
||||||
|
for request: Dictionary in requests:
|
||||||
|
var taken: Dictionary = _take_available(trial, request.id, request.amount)
|
||||||
|
var total: float = 0.0
|
||||||
|
for quantity in taken.values():
|
||||||
|
total += quantity
|
||||||
|
if total != request.amount:
|
||||||
|
return false
|
||||||
|
if not trial.binds.has(request.owner):
|
||||||
|
trial.binds[request.owner] = {}
|
||||||
|
for id in taken:
|
||||||
|
trial.binds[request.owner][id] = trial.binds[request.owner].get(id, 0.0) + taken[id]
|
||||||
|
target.amount = trial.amount
|
||||||
|
target.binds = trial.binds
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# Neither costs nor bindings are committed unless the whole batch is funded.
|
||||||
|
func _try_production(target: Dictionary, entity: OwniverseEntity, amount: float,
|
||||||
|
automatic: bool = false) -> bool:
|
||||||
|
if amount <= 0.0 or amount != floorf(amount):
|
||||||
|
return false
|
||||||
|
var trial: Dictionary = target.duplicate(true)
|
||||||
|
for resource in entity.cost:
|
||||||
|
var cost: float = roundf(maxf(0.0, entity.cost[resource]) * amount)
|
||||||
|
if automatic and trial.auto_resources.get(resource, 0.0) < cost:
|
||||||
|
return false
|
||||||
|
var before: Dictionary = _physical_amounts(trial)
|
||||||
|
var taken: Dictionary = _take_available(trial, resource, cost)
|
||||||
|
var total: float = 0.0
|
||||||
|
for quantity in taken.values():
|
||||||
|
total += quantity
|
||||||
|
if total != cost:
|
||||||
|
return false
|
||||||
|
if automatic:
|
||||||
|
trial.auto_resources[resource] = maxf(0.0, trial.auto_resources.get(resource, 0.0) - cost)
|
||||||
|
for id in taken:
|
||||||
|
var factor: float = 1.0 - taken[id] / before[id] if before.get(id, 0.0) > 0.0 else 0.0
|
||||||
|
_scale_batches(trial, id, factor)
|
||||||
|
if not _bind_new_owners(trial, {entity.id: amount}, true):
|
||||||
|
return false
|
||||||
|
_refresh_groups(trial)
|
||||||
|
_clamp_auto_resources(trial)
|
||||||
|
for field in ["amount", "binds", "batches", "auto_resources"]:
|
||||||
|
if trial.has(field):
|
||||||
|
target[field] = trial[field]
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _bind_new_owners(target: Dictionary, amounts: Dictionary, require_full: bool = false) -> bool:
|
||||||
|
if require_full:
|
||||||
|
return _bind_full(target, amounts)
|
||||||
var requests: Dictionary = {}
|
var requests: Dictionary = {}
|
||||||
for owner in amounts:
|
for owner in amounts:
|
||||||
if not entities.has(owner):
|
if not entities.has(owner):
|
||||||
@@ -449,6 +558,8 @@ func _bind_new_owners(target: Dictionary, amounts: Dictionary) -> void:
|
|||||||
target.binds[owner][id] = target.binds[owner].get(id, 0.0) + quantity
|
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)
|
target.amount[id] = maxf(0.0, target.amount.get(id, 0.0) - quantity)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
# Integrate survival over the period, including partial batch expiry.
|
# Integrate survival over the period, including partial batch expiry.
|
||||||
func _object_years(source: Dictionary, id: String, quantity: float,
|
func _object_years(source: Dictionary, id: String, quantity: float,
|
||||||
@@ -619,16 +730,9 @@ func _produce_for_period(previous: Dictionary, target: Dictionary, quantities: D
|
|||||||
var actual: float = minf(desired, _auto_capacity(target, entity, budget))
|
var actual: float = minf(desired, _auto_capacity(target, entity, budget))
|
||||||
if actual <= 0.0:
|
if actual <= 0.0:
|
||||||
continue
|
continue
|
||||||
for resource in entity.cost:
|
if not _try_production(target, entity, actual, true):
|
||||||
var cost: float = roundf(maxf(0.0, entity.cost[resource]) * actual)
|
continue
|
||||||
budget[resource] = maxf(0.0, budget.get(resource, 0.0) - cost)
|
budget = target.auto_resources
|
||||||
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
|
created[id] = actual
|
||||||
total_created += actual
|
total_created += actual
|
||||||
for id in candidates:
|
for id in candidates:
|
||||||
@@ -685,8 +789,8 @@ func produce(item_id: String, desired_amount: float) -> float:
|
|||||||
if max_producible <= 0:
|
if max_producible <= 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
apply_costs(entity, max_producible)
|
if not _try_production(state, entity, max_producible):
|
||||||
apply_binds(item_id, entity, max_producible)
|
return 0.0
|
||||||
|
|
||||||
# Add produced entities
|
# Add produced entities
|
||||||
_process_enitity(item_id, max_producible)
|
_process_enitity(item_id, max_producible)
|
||||||
@@ -723,7 +827,7 @@ func produce_hs(entity_id: String, value: float) -> float:
|
|||||||
# Decimal costs are represented as integer fractions; combine their denominators.
|
# Decimal costs are represented as integer fractions; combine their denominators.
|
||||||
func _production_step(entity: OwniverseEntity) -> float:
|
func _production_step(entity: OwniverseEntity) -> float:
|
||||||
var step: float = 1.0
|
var step: float = 1.0
|
||||||
for value in entity.cost.values():
|
for value in entity.cost.values() + entity.bind.values():
|
||||||
var cost: float = maxf(0.0, value)
|
var cost: float = maxf(0.0, value)
|
||||||
if cost == 0.0:
|
if cost == 0.0:
|
||||||
continue
|
continue
|
||||||
@@ -753,6 +857,14 @@ func calculate_max_producible(entity: OwniverseEntity, desired_amount: float = 1
|
|||||||
requirements[resource] = maxf(0.0, entity.cost[resource])
|
requirements[resource] = maxf(0.0, entity.cost[resource])
|
||||||
for resource in entity.bind:
|
for resource in entity.bind:
|
||||||
requirements[resource] = requirements.get(resource, 0.0) + maxf(0.0, entity.bind[resource])
|
requirements[resource] = requirements.get(resource, 0.0) + maxf(0.0, entity.bind[resource])
|
||||||
|
var has_requirements: bool = false
|
||||||
|
for quantity in requirements.values():
|
||||||
|
if quantity > 0.0:
|
||||||
|
has_requirements = true
|
||||||
|
break
|
||||||
|
# No recipe means unavailable for production, not unlimited free objects.
|
||||||
|
if not has_requirements:
|
||||||
|
return 0.0
|
||||||
var available: float = maxf(0.0, desired_amount)
|
var available: float = maxf(0.0, desired_amount)
|
||||||
for resource in requirements:
|
for resource in requirements:
|
||||||
if requirements[resource] > 0.0:
|
if requirements[resource] > 0.0:
|
||||||
@@ -776,19 +888,20 @@ func apply_costs(entity: OwniverseEntity, amount: float) -> void:
|
|||||||
for id in taken:
|
for id in taken:
|
||||||
var factor: float = 1.0 - taken[id] / quantities[id] if quantities.get(id, 0.0) > 0.0 else 0.0
|
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)
|
_scale_batches(state, id, factor)
|
||||||
items_to_update[id] = state.amount[id]
|
|
||||||
_refresh_groups(state)
|
_refresh_groups(state)
|
||||||
_clamp_auto_resources(state)
|
_clamp_auto_resources(state)
|
||||||
|
refresh_item_amounts()
|
||||||
|
|
||||||
|
|
||||||
## Keep concrete bound types, so their ages and transitions remain traceable.
|
## Keep concrete bound types, so their ages and transitions remain traceable.
|
||||||
func apply_binds(item_id: String, _entity: OwniverseEntity, amount: float) -> void:
|
func apply_binds(item_id: String, _entity: OwniverseEntity, amount: float) -> bool:
|
||||||
_bind_new_owners(state, {item_id: amount})
|
if not _bind_new_owners(state, {item_id: amount}, true):
|
||||||
_round_object_allocations(state)
|
return false
|
||||||
_refresh_groups(state)
|
_refresh_groups(state)
|
||||||
_clamp_auto_resources(state)
|
_clamp_auto_resources(state)
|
||||||
for id in state.amount:
|
refresh_item_amounts()
|
||||||
items_to_update[id] = state.amount[id]
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
## Computes a normalized distribution of dictionary values (sum equals 1.0)
|
## Computes a normalized distribution of dictionary values (sum equals 1.0)
|
||||||
@@ -822,7 +935,7 @@ func consume_from_batches(entity: OwniverseEntity, current_year: float) -> float
|
|||||||
state.binds[owner][entity.id] *= 1.0 - fraction
|
state.binds[owner][entity.id] *= 1.0 - fraction
|
||||||
_round_object_allocations(state)
|
_round_object_allocations(state)
|
||||||
_refresh_groups(state)
|
_refresh_groups(state)
|
||||||
items_to_update[entity.id] = state.amount[entity.id]
|
refresh_item_amounts()
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
@@ -877,15 +990,13 @@ func _process_enitity(entity_id: String, amount: float) -> void:
|
|||||||
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] = state.stats.total_amount.get(entity_id, 0.0) + maxf(0.0, 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]
|
|
||||||
|
|
||||||
# Handle group updates if entity belongs to a group
|
# Handle group updates if entity belongs to a group
|
||||||
var entity = entities[entity_id]
|
var entity = entities[entity_id]
|
||||||
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]
|
|
||||||
state.stats.total_amount[entity.group_as] = state.stats.total_amount.get(entity.group_as, 0.0) + maxf(0.0, amount)
|
state.stats.total_amount[entity.group_as] = state.stats.total_amount.get(entity.group_as, 0.0) + maxf(0.0, amount)
|
||||||
|
refresh_item_amounts()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
class_name State
|
|
||||||
|
|
||||||
const FLOAT_THRESHOLD: float = 1.0E14
|
|
||||||
|
|
||||||
var data: Dictionary = {
|
|
||||||
'time': {
|
|
||||||
'timestamp': 0.0,
|
|
||||||
'year': 0.0,
|
|
||||||
},
|
|
||||||
'timestamp': 0.0,
|
|
||||||
'year': 0.0,
|
|
||||||
'speed_in_year': 0,
|
|
||||||
'energy': 1E14,
|
|
||||||
}
|
|
||||||
|
|
||||||
var entities: Dictionary[String, OwniverseEntity] = {}
|
|
||||||
|
|
||||||
var producing_entities: Array[OwniverseEntity] = []
|
|
||||||
|
|
||||||
# TODO to save !!!
|
|
||||||
var auto_production_rest: Dictionary = {
|
|
||||||
'H': 0.0,
|
|
||||||
'S': 0.0,
|
|
||||||
'auto': 0.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
var evolving_entities: Array[OwniverseEntity] = []
|
|
||||||
|
|
||||||
# TODO to save !!!
|
|
||||||
var batches: Dictionary[String, Array] = {}
|
|
||||||
# TODO to save !!!
|
|
||||||
var gamount: Dictionary[String, float] = {}
|
|
||||||
|
|
||||||
|
|
||||||
var distribution: Dictionary[String, float] = {}
|
|
||||||
|
|
||||||
|
|
||||||
# new functions
|
|
||||||
func evolute(prev_state: State, delta: float, current_speed_in_year: float) -> float:
|
|
||||||
var _new_year: float = _add_year_delta(delta, current_speed_in_year)
|
|
||||||
if data.year == _new_year:
|
|
||||||
return data.year
|
|
||||||
|
|
||||||
return _new_year
|
|
||||||
|
|
||||||
|
|
||||||
func _add_year_delta(delta: float, current_speed_in_year: float) -> float:
|
|
||||||
var _new_year:float = floorf(data.year + delta * current_speed_in_year)
|
|
||||||
#print(data.year, " | ", _new_year, " | ", data)
|
|
||||||
return _new_year
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# end of new functions
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func add(entity: OwniverseEntity, year: float, amount: float):
|
|
||||||
var _batch_array: Array = batches[entity.id]
|
|
||||||
var _from_year: float = year + entity.lifespan - entity.lifespan_delta
|
|
||||||
var _to_year: float = year + entity.lifespan + entity.lifespan_delta
|
|
||||||
if _batch_array.size() != 0 and _batch_array[-1].to_year > year + entity.lifespan:
|
|
||||||
_batch_array[-1].amount += amount
|
|
||||||
else:
|
|
||||||
var _batch := {
|
|
||||||
'from_year': _from_year,
|
|
||||||
'to_year': _to_year,
|
|
||||||
'amount': amount,
|
|
||||||
}
|
|
||||||
_batch_array.append(_batch)
|
|
||||||
|
|
||||||
#print('---------')
|
|
||||||
#for id in batches:
|
|
||||||
#print(id)
|
|
||||||
#
|
|
||||||
|
|
||||||
func process(from_year: float, to_year: float) -> void:
|
|
||||||
# производим
|
|
||||||
# эволюционируем
|
|
||||||
# записываем новые
|
|
||||||
var _to_produce := {}
|
|
||||||
var _to_evolute := {}
|
|
||||||
for _entity_id in batches:
|
|
||||||
_to_produce[_entity_id] = 0
|
|
||||||
_to_evolute[_entity_id] = 0
|
|
||||||
var _new_batch_array := []
|
|
||||||
for _batch: Dictionary in batches[_entity_id]:
|
|
||||||
# production
|
|
||||||
_to_produce[_entity_id] += _batch.amount
|
|
||||||
# evolution
|
|
||||||
if _batch.to_year < to_year:
|
|
||||||
_to_produce[_entity_id] += _batch.amount
|
|
||||||
else:
|
|
||||||
var _max_from_year: float = max(from_year, _batch.from_year)
|
|
||||||
var _min_to_year: float = min(to_year, _batch.to_year)
|
|
||||||
var _part_of_amount: float = _batch.amount \
|
|
||||||
* (_min_to_year - _max_from_year) \
|
|
||||||
/ (_batch.to_year - _batch.from_year)
|
|
||||||
_to_evolute[_entity_id] += _part_of_amount
|
|
||||||
var _rest_of_amount: float = _batch.amount - _part_of_amount
|
|
||||||
|
|
||||||
if _rest_of_amount > 0:
|
|
||||||
_new_batch_array.append({
|
|
||||||
'from_year': _batch._from_year,
|
|
||||||
'to_year': _batch._to_year,
|
|
||||||
'amount': _rest_of_amount,
|
|
||||||
})
|
|
||||||
|
|
||||||
# entity level
|
|
||||||
batches[_entity_id] = _new_batch_array
|
|
||||||
|
|
||||||
|
|
||||||
func produce(from_year: float, to_year: float):
|
|
||||||
var _time_delta = to_year - from_year
|
|
||||||
# get entity array for production
|
|
||||||
for _entity: OwniverseEntity in producing_entities:
|
|
||||||
var _to_produce: float = 0.0
|
|
||||||
for _batch: Dictionary in batches[_entity.id]:
|
|
||||||
_to_produce += _batch.amount
|
|
||||||
_to_produce *= _time_delta * _entity.product_per_year_amount
|
|
||||||
if _entity.auto > 0:
|
|
||||||
var _auto_production_part: float = _entity.auto * _to_produce
|
|
||||||
_to_produce -= _auto_production_part
|
|
||||||
auto_production_rest.auto += _auto_production_part
|
|
||||||
|
|
||||||
auto_production_rest[_entity.product_per_year_type] += _to_produce
|
|
||||||
print("auto prod rest: ", auto_production_rest)
|
|
||||||
auto_production_rest["H"] = produce_hs("H", auto_production_rest["H"])
|
|
||||||
auto_production_rest["S"] = produce_hs("S", auto_production_rest["S"])
|
|
||||||
|
|
||||||
|
|
||||||
func produce_hs(entity_id: String, value: float) -> float:
|
|
||||||
var _to_produce: float = floorf(value)
|
|
||||||
gamount[entity_id] += _to_produce
|
|
||||||
# TODO double double math
|
|
||||||
if _to_produce > data["energy"]:
|
|
||||||
_to_produce = data["energy"]
|
|
||||||
data["energy"] -= _to_produce
|
|
||||||
return value - _to_produce
|
|
||||||
|
|
||||||
func produce_auto() -> float:
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
func init_entity(entity: OwniverseEntity) -> void:
|
|
||||||
entities[entity.id] = entity
|
|
||||||
gamount[entity.id] = 0.0
|
|
||||||
if entity.product_per_year_type != "":
|
|
||||||
producing_entities.append(entity)
|
|
||||||
|
|
||||||
if entity.lifespan > 0:
|
|
||||||
evolving_entities.append(entity)
|
|
||||||
|
|
||||||
batches[entity.id] = []
|
|
||||||
|
|
||||||
|
|
||||||
func update_distribution(_distribute_by: Dictionary[String, float]) -> void:
|
|
||||||
distribution = {}
|
|
||||||
var _total: float = 0
|
|
||||||
for id in _distribute_by:
|
|
||||||
if _distribute_by[id] != 0:
|
|
||||||
var _value: float = _distribute_by[id]
|
|
||||||
_total += _value
|
|
||||||
distribution[id] = _value
|
|
||||||
|
|
||||||
if _total == 0: return
|
|
||||||
|
|
||||||
for id in distribution:
|
|
||||||
distribution[id] /= _total
|
|
||||||
|
|
||||||
#func _add_float(a: float, b: Array[float]) -> Array[float]:
|
|
||||||
#if a < FLOAT_THRESHOLD:
|
|
||||||
#return [a+b[0], 0]
|
|
||||||
#else:
|
|
||||||
#return [a+b[0], 0]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://mgnxt6kbf80n
|
|
||||||
Reference in New Issue
Block a user