Refactor save management; update validation logic in save_manager.gd and enhance action handling in actions.gd for improved data integrity and user experience

This commit is contained in:
Kirill
2026-09-14 07:47:21 +03:00
parent dea744bc30
commit 85a13eb241
6 changed files with 160 additions and 61 deletions
+4 -4
View File
@@ -146,10 +146,10 @@ func validate_data(data: Variant) -> String:
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]:
for owner_id in saved.binds:
if not saved.amount.has(owner_id) or not _number_map(saved.binds[owner_id], true):
return "invalid state.binds." + owner_id
for id in saved.binds[owner_id]:
if not saved.amount.has(id):
return "unknown bound resource " + id
for id in saved.batches:
+13 -7
View File
@@ -1,14 +1,17 @@
[runnable_presets]
"Windows Desktop"="Windows Desktop"
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../../Documents/Owad/Owniverse.I/Builds/owniverse.0.6.13-129.exe"
export_path="../../Documents/Owad/Owniverse.I/Builds/owniverse.0.6.28-144.exe"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
@@ -74,7 +77,6 @@ Remove-Item -Recurse -Force '{temp_dir}'"
name="Android"
platform="Android"
runnable=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
@@ -110,7 +112,7 @@ architectures/armeabi-v7a=false
architectures/arm64-v8a=true
architectures/x86=false
architectures/x86_64=false
version/code=143
version/code=144
version/name=""
package/unique_name="com.KIDGameProduction.Owniverse"
package/name="Owniverse"
@@ -136,11 +138,12 @@ screen/support_normal=true
screen/support_large=true
screen/support_xlarge=true
screen/background_color=Color(0, 0, 0, 1)
splash_screen/disable_godot_boot_splash=false
splash_screen/icon=""
splash_screen/branding_image=""
splash_screen/background_color=Color(0, 0, 0, 1)
user_data_backup/allow=false
command_line/extra_args=""
apk_expansion/enable=false
apk_expansion/SALT=""
apk_expansion/public_key=""
permissions/custom_permissions=PackedStringArray()
permissions/access_checkin_properties=false
permissions/access_coarse_location=false
@@ -295,3 +298,6 @@ permissions/write_sms=false
permissions/write_social_stream=false
permissions/write_sync_settings=false
permissions/write_user_dictionary=false
apk_expansion/enable=false
apk_expansion/SALT=""
apk_expansion/public_key=""
+1 -1
View File
@@ -11,7 +11,7 @@ config_version=5
[application]
config/name="Owniverse"
config/version="0.6.27"
config/version="0.6.28"
run/main_scene="uid://bv384dpmvjv8o"
config/features=PackedStringArray("4.7", "Mobile")
boot_splash/show_image=false
+10 -1
View File
@@ -5,6 +5,7 @@ const OWNIVERSE_ITEM = preload("uid://d3xjfxwe3w16v")
const SPEED_TEXTURE_NAME_TEMPLATE = "res://ui/topmenu/time&speed/time%s.png"
const PAGE_COUNT = 5
const RESOURCE_PAGE = 1
const PAGE_WIDTH = 1080
const DRAG_TOLERANCE = 360
const SWIPE_DURATION: float = 0.4
@@ -256,6 +257,11 @@ func _set_active_page_actions() -> void:
#var amount = owniverse.get_available_to_produce(entity)
var amount = owniverse.calculate_max_producible(entity)
#print(item_id, " : ", amount)
if item_id == "SMBH":
var preview: Callable = func(requested: float) -> float:
return owniverse.calculate_max_producible(entity, requested)
page_actions[pa].set_actions(item_id, amount, preview)
else:
page_actions[pa].set_actions(item_id, amount)
@@ -329,7 +335,7 @@ func _change_speed(delta: int) -> void:
g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed + 1))
func _on_topmenu_button_pressed(ButtonName) -> void:
func _on_topmenu_button_pressed(_ButtonName) -> void:
print("Debug → ")
#, owniverse.state)
for id in owniverse.state.batches:
@@ -359,6 +365,9 @@ func _on_unlock_and_save_timeout() -> void:
func load_data() -> bool:
var game_data: Dictionary = SaveManager.load_data("owniverse", "qwerty")
if game_data.is_empty():
if SaveManager.last_error == "":
pending_page = RESOURCE_PAGE
_restore_page.call_deferred()
return false
var restored: Dictionary = game_data.state
# Current definitions are initialized first; reject incompatible saves before publishing.
+10 -4
View File
@@ -23,9 +23,14 @@ var action_values: Dictionary = {
}
func set_actions(item_id: String, value: float) -> void:
func set_actions(item_id: String, value: float, preview: Callable = Callable()) -> void:
current_item_id = item_id
_set_action_values(_get_distribution(value))
var requests: Array = _get_distribution(value)
var displayed: Array = requests.duplicate()
if preview.is_valid():
for i in range(displayed.size()):
displayed[i] = preview.call(requests[i]) if requests[i] > 0.0 else 0.0
_set_action_values(requests, displayed)
func _get_distribution(value: float) -> Array:
if value <= 0.0 or not is_finite(value):
@@ -38,10 +43,11 @@ func _get_distribution(value: float) -> Array:
distribution.push_front(pow(10.0, ev))
return distribution
func _set_action_values(values: Array) -> void:
func _set_action_values(values: Array, displayed: Array) -> void:
for ind in range(0, 5):
action_values[ind] = values[ind]
action_labels[ind].text = Global.format_number(values[ind]) if values[ind] > 0 else ""
# Keep the original request for execution; show the exact planned output.
action_labels[ind].text = Global.format_number(displayed[ind]) if displayed[ind] > 0 else ""
func _on_action_button_pressed(button_id: int) -> void:
+121 -43
View File
@@ -12,13 +12,8 @@ var items_to_update: Dictionary[String, float] = {}
var auto_production: Array = []
var locked_entities: Array = []
#var cumulative_amount: Dictionary[String, float] = {}
#var current_amount: Dictionary[String, float] = {}
# ------------------------------------
var state: Dictionary = {
'version': '0.1.0',
'time': {
@@ -226,7 +221,7 @@ func _credit(target: Dictionary, id: String, amount: float) -> float:
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 != "":
if entities.has(id) and entities[id].group_as != "" and entities[id].group_as != id:
var group: String = entities[id].group_as
target.stats.total_amount[group] = target.stats.total_amount.get(group, 0.0) + amount
return amount
@@ -238,10 +233,10 @@ func _round_object_allocations(target: Dictionary) -> void:
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 slots: Array = [ {"owner": "", "value": target.amount.get(id, 0.0)}]
for owner_id in target.binds:
if target.binds[owner_id].has(id):
slots.append({"owner": owner_id, "value": target.binds[owner_id][id]})
var assigned: float = 0.0
for slot: Dictionary in slots:
slot.whole = floorf(slot.value)
@@ -426,21 +421,21 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
# 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]
for owner_id in bindings:
var owner_survival: float = 1.0 - fractions.get(owner_id, 0.0)
target.binds[owner_id] = {}
for id in bindings[owner_id]:
var held: float = bindings[owner_id][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
target.binds[owner_id][id] = target.binds[owner_id].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
if not incoming.has(owner_id):
incoming[owner_id] = {}
incoming[owner_id][successor] = incoming[owner_id].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]))
@@ -453,10 +448,10 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
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
for owner_id in incoming:
for id in incoming[owner_id]:
var transferred: float = minf(incoming[owner_id][id], target.amount.get(id, 0.0))
target.binds[owner_id][id] = target.binds[owner_id].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)
@@ -467,15 +462,15 @@ func _evolve_batch_generation(target: Dictionary, to_year: float) -> bool:
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):
for owner_id in amounts:
if not entities.has(owner_id):
return false
for id in entities[owner].bind:
var quantity: float = amounts[owner] * maxf(0.0, entities[owner].bind[id])
for id in entities[owner_id].bind:
var quantity: float = amounts[owner_id] * maxf(0.0, entities[owner_id].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.append({"owner": owner_id, "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
@@ -532,11 +527,11 @@ func _bind_new_owners(target: Dictionary, amounts: Dictionary, require_full: boo
if require_full:
return _bind_full(target, amounts)
var requests: Dictionary = {}
for owner in amounts:
if not entities.has(owner):
for owner_id in amounts:
if not entities.has(owner_id):
continue
for id in entities[owner].bind:
var quantity: float = amounts[owner] * maxf(0.0, entities[owner].bind[id])
for id in entities[owner_id].bind:
var quantity: float = amounts[owner_id] * maxf(0.0, entities[owner_id].bind[id])
var weights: Dictionary = {}
for member in _members(id):
weights[member] = target.amount.get(member, 0.0)
@@ -544,18 +539,18 @@ func _bind_new_owners(target: Dictionary, amounts: Dictionary, require_full: boo
for member in weights:
if not requests.has(member):
requests[member] = {}
requests[member][owner] = requests[member].get(owner, 0.0) + quantity * weights[member]
requests[member][owner_id] = requests[member].get(owner_id, 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
for owner_id in requests[id]:
var quantity: float = requests[id][owner_id] * factor
if not target.binds.has(owner_id):
target.binds[owner_id] = {}
target.binds[owner_id][id] = target.binds[owner_id].get(id, 0.0) + quantity
target.amount[id] = maxf(0.0, target.amount.get(id, 0.0) - quantity)
return true
@@ -774,7 +769,90 @@ func _auto_capacity(source: Dictionary, entity: OwniverseEntity, budget: Diction
## @param item_id - entity id to produce
## @param desired_amount - desired quantity to produce
## @return actual amount produced based on available resources
func _smbh_sources() -> Dictionary:
var masses: Dictionary = {}
for id in entities:
var entity: OwniverseEntity = entities[id]
if entity.count_as == "BH" and entity.mass > 0.0:
masses[id] = floorf(maxf(0.0, state.amount.get(id, 0.0))) * entity.mass
return masses
func _smbh_available() -> float:
var mass: float = 0.0
for value in _smbh_sources().values():
mass += value
return floorf(mass)
func _plan_smbh(desired_amount: float) -> Dictionary:
var empty: Dictionary = {"amount": 0.0, "taken": {}}
if desired_amount <= 0.0 or not is_finite(desired_amount):
return empty
var total_mass: float = _smbh_available()
if total_mass <= 0.0:
return empty
var requested: float = minf(floorf(desired_amount), total_mass)
if requested <= 0.0:
return empty
var masses: Dictionary = _smbh_sources()
var distribution: Dictionary = _normalize_distribution(masses)
var taken: Dictionary = {}
var order: Array = []
var consumed_mass: float = 0.0
for id in distribution:
var expected: float = requested * distribution[id] / entities[id].mass
taken[id] = minf(floorf(expected), floorf(state.amount[id]))
consumed_mass += taken[id] * entities[id].mass
order.append({"id": id, "remainder": expected - taken[id]})
order.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.remainder > b.remainder if a.remainder != b.remainder else a.id < b.id)
# Consume whole BH only; all excess mass becomes SMBH, never disappears.
for candidate: Dictionary in order:
if consumed_mass >= requested:
break
var id: String = candidate.id
if taken[id] < floorf(state.amount[id]):
taken[id] += 1.0
consumed_mass += entities[id].mass
if consumed_mass <= 0.0 or consumed_mass != floorf(consumed_mass):
return empty
return {"amount": consumed_mass, "taken": taken}
func _produce_smbh(desired_amount: float) -> float:
var plan: Dictionary = _plan_smbh(desired_amount)
var consumed_mass: float = plan.amount
var taken: Dictionary = plan.taken
if consumed_mass <= 0.0:
return 0.0
var next: Dictionary = state.duplicate(true)
var physical: Dictionary = _physical_amounts(state)
for id in taken:
if taken[id] <= 0.0:
continue
next.amount[id] -= taken[id]
var fraction: float = taken[id] / physical[id]
_scale_batches(next, id, 1.0 - fraction)
# Release the consumed owners' share of any resources they held.
for resource in next.binds.get(id, {}):
var released: float = next.binds[id][resource] * fraction
next.binds[id][resource] -= released
next.amount[resource] = next.amount.get(resource, 0.0) + released
_credit(next, "SMBH", consumed_mass)
_append_batch(next, "SMBH", consumed_mass, state.time.datetime, state.time.datetime)
_round_object_allocations(next)
_refresh_groups(next)
_clamp_auto_resources(next)
state = next
new_state = next
refresh_item_amounts()
return consumed_mass
func produce(item_id: String, desired_amount: float) -> float:
if item_id == "SMBH":
return _produce_smbh(desired_amount)
# Use special production for S and H
if item_id == "S" or item_id == "H":
return produce_hs(item_id, desired_amount)
@@ -852,6 +930,8 @@ func _whole_gcd(a: float, b: float) -> float:
func calculate_max_producible(entity: OwniverseEntity, desired_amount: float = 1.0E150) -> float:
if entity.id == "SMBH":
return _plan_smbh(desired_amount).amount
var requirements: Dictionary = {}
for resource in entity.cost:
requirements[resource] = maxf(0.0, entity.cost[resource])
@@ -930,9 +1010,9 @@ func consume_from_batches(entity: OwniverseEntity, current_year: float) -> float
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
for owner_id in state.binds:
if state.binds[owner_id].has(entity.id):
state.binds[owner_id][entity.id] *= 1.0 - fraction
_round_object_allocations(state)
_refresh_groups(state)
refresh_item_amounts()
@@ -999,8 +1079,6 @@ func _process_enitity(entity_id: String, amount: float) -> void:
refresh_item_amounts()
#endregion
#region Time