From 85a13eb241344d3634184c4fe732fc4d380378f3 Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 14 Sep 2026 07:47:21 +0300 Subject: [PATCH] Refactor save management; update validation logic in save_manager.gd and enhance action handling in actions.gd for improved data integrity and user experience --- autoload/save_manager.gd | 8 +- export_presets.cfg | 20 +++-- project.godot | 2 +- stage/stage.gd | 13 ++- ui/actions/actions.gd | 14 ++- world/owniverse/owniverse.gd | 164 ++++++++++++++++++++++++++--------- 6 files changed, 160 insertions(+), 61 deletions(-) diff --git a/autoload/save_manager.gd b/autoload/save_manager.gd index 0ce6f24..7c4d03a 100644 --- a/autoload/save_manager.gd +++ b/autoload/save_manager.gd @@ -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: diff --git a/export_presets.cfg b/export_presets.cfg index 085baf7..c217614 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -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="" diff --git a/project.godot b/project.godot index f729bcc..ea7e4c5 100644 --- a/project.godot +++ b/project.godot @@ -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 diff --git a/stage/stage.gd b/stage/stage.gd index 493c94b..4cdfd13 100644 --- a/stage/stage.gd +++ b/stage/stage.gd @@ -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,7 +257,12 @@ 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) - page_actions[pa].set_actions(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) func _page_action_pressed(page_id: int, action_amount: float) -> void: @@ -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. diff --git a/ui/actions/actions.gd b/ui/actions/actions.gd index ba79326..fc40598 100644 --- a/ui/actions/actions.gd +++ b/ui/actions/actions.gd @@ -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: diff --git a/world/owniverse/owniverse.gd b/world/owniverse/owniverse.gd index 71c3d10..6fd8f93 100644 --- a/world/owniverse/owniverse.gd +++ b/world/owniverse/owniverse.gd @@ -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