From d90f8693c9d2277d7a10f0c02b1730dad9a9dd1b Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 31 Aug 2026 23:51:07 +0300 Subject: [PATCH] Update Godot core rules to version 4.7; refactor production logic and add entity batch processing --- .continue/rules/01-godot-core.md | 12 +- .continue/rules/03-style.md | 1 + world/owniverse/owniverse.gd | 395 +++++++++++++++++++--------- world/owniverse/owniverse_entity.gd | 3 +- world/world.tscn | 12 - 5 files changed, 287 insertions(+), 136 deletions(-) create mode 100644 .continue/rules/03-style.md diff --git a/.continue/rules/01-godot-core.md b/.continue/rules/01-godot-core.md index ad37677..bb6f129 100644 --- a/.continue/rules/01-godot-core.md +++ b/.continue/rules/01-godot-core.md @@ -1,13 +1,19 @@ --- -name: Godot 4.4 Core Rules +name: Godot 4.7 Core Rules priority: high --- -Ты — senior Godot 4.4 GDScript разработчик. +Ты — senior Godot 4.7 GDScript разработчик. + +- Use Godot 4.7+ API. +- Do not use Godot 3.x syntax or APIs. +- Follow the existing project architecture and coding style. +- Do not introduce unnecessary abstractions. +- Do not modify unrelated files. -- Всегда используй Godot 4.4+ API. - Пиши строго типизированный код (`var health: int = 100`, `: Array[Node]` и т.д.). - Предпочитай Signals вместо проверки в _process. - Используй @export, @export_range, @export_enum, @onready. - Composition over deep inheritance. - Менеджеры и автолоады выноси в отдельные singleton'ы. + diff --git a/.continue/rules/03-style.md b/.continue/rules/03-style.md new file mode 100644 index 0000000..6aeef81 --- /dev/null +++ b/.continue/rules/03-style.md @@ -0,0 +1 @@ +- use spaces not tabs for indentation \ No newline at end of file diff --git a/world/owniverse/owniverse.gd b/world/owniverse/owniverse.gd index 57b3001..12168ae 100644 --- a/world/owniverse/owniverse.gd +++ b/world/owniverse/owniverse.gd @@ -12,7 +12,7 @@ var items_to_update: Dictionary[String, float] = {} var auto_production: Array = [] var locked_entities: Array = [] -var cumulative_amount: Dictionary[String, float] = {} +#var cumulative_amount: Dictionary[String, float] = {} var current_amount: Dictionary[String, float] = {} @@ -42,7 +42,6 @@ var speed: int: func evolute(delta: float) -> Owniverse: - var new_owniverse := Owniverse.new() _add_time_delta(new_owniverse, delta) # produce and evolute @@ -64,114 +63,274 @@ func _add_time_delta(new_owniverse: Owniverse, delta: float): new_owniverse.state.year = floorf(new_owniverse.state.datetime) -func produce_hs(entity_id: String, value: float) -> float: - var _to_produce: float = floorf(value) - _to_produce = min(_to_produce, state.energy) - state.energy -= _to_produce - state.amount[entity_id] += _to_produce - stats.total_amount[entity_id] += _to_produce - return _to_produce - - - - # ---------- -#region auto production and evolution - -func _produce_automatically(year_delta: float) -> void: - var _cycle_production = {"S": 0.0, "H": 0.0} - for entity: OwniverseEntity in auto_production: - if current_amount[entity.id] == 0: - continue - _cycle_production[entity.product_per_year_type] += \ - current_amount[entity.id] * entity.product_per_year_amount * year_delta - - for item in _cycle_production: - _count_amount(item, _cycle_production[item]) - - -func _get_distribution(source: Dictionary) -> Dictionary: - var distribution: Dictionary = {} - var total: float = 0 - for id in source: - if source[id] != 0: - total += source[id] - distribution[id] = source[id] - for id in distribution: - distribution[id] /= total - return distribution - -#endregion - #region Production +## Produces entities with cost and bind constraints +## @param item_id - entity id to produce +## @param desired_amount - desired quantity to produce +## @return actual amount produced based on available resources +func produce(item_id: String, desired_amount: float) -> float: + # Use special production for S and H + if item_id == "S" or item_id == "H": + return produce_hs(item_id, desired_amount) + + var entity: OwniverseEntity = entities[item_id] + + if desired_amount <= 0: + return 0.0 + + var max_producible = calculate_max_producible(entity, desired_amount) + + if max_producible <= 0: + return 0.0 + + apply_costs(entity, max_producible) + apply_binds(item_id, entity, max_producible) + + # Add produced entities + _process_enitity(item_id, max_producible) + + return max_producible +## Produces S (space) or H (hydrogen) using energy from state.energy +## @param entity_id - entity id to produce ("S" or "H") +## @param value - desired amount to produce +## @return actual amount produced (limited by available energy) +func produce_hs(entity_id: String, value: float) -> float: + if value <= 0: + return 0.0 + + var to_produce: float = floorf(value) + to_produce = min(to_produce, state.energy) + + if to_produce <= 0: + return 0.0 + + state.energy -= to_produce + _process_enitity(entity_id, to_produce) + stats.total_amount[entity_id] = stats.total_amount.get(entity_id, 0.0) + to_produce + + return to_produce + + +## Calculates maximum producible amount based on cost and bind constraints +## @param entity - entity to produce +## @param desired_amount - desired quantity +## @return maximum producible amount considering all constraints +func calculate_max_producible(entity: OwniverseEntity, desired_amount: float) -> float: + var max_producible: float = desired_amount + + # Check cost constraints - resources consumed during production + for cost_item in entity.cost: + if entity.cost[cost_item] == 0: + continue + var available = current_amount.get(cost_item, 0.0) + var max_from_cost = floorf(available / entity.cost[cost_item]) + max_producible = min(max_producible, max_from_cost) + + # Check bind constraints - resources locked by produced entity + for bind_item in entity.bind: + if entity.bind[bind_item] == 0: + continue + var available = current_amount.get(bind_item, 0.0) + var max_from_bind = floorf(available / entity.bind[bind_item]) + max_producible = min(max_producible, max_from_bind) + + return max_producible + + +## Applies cost (consumes resources) +## @param entity - entity being produced +## @param amount - amount to produce +func apply_costs(entity: OwniverseEntity, amount: float) -> void: + for cost_item in entity.cost: + if entity.cost[cost_item] == 0: + continue + var cost_amount = amount * entity.cost[cost_item] + _process_enitity(cost_item, -cost_amount) + + +## Applies bind (locks resources) +## @param item_id - id of entity being produced +## @param entity - entity being produced +## @param amount - amount to produce +func apply_binds(item_id: String, entity: OwniverseEntity, amount: float) -> void: + for bind_item in entity.bind: + if entity.bind[bind_item] == 0: + continue + var bind_amount = amount * entity.bind[bind_item] + + # Initialize binds dictionary for this entity if needed + if not state.binds.has(item_id): + state.binds[item_id] = {} + + # Record bound amount + state.binds[item_id][bind_item] = bind_amount + + # Decrease available amount (lock it) + _process_enitity(bind_item, -bind_amount) + + +## Computes a normalized distribution of dictionary values (sum equals 1.0) +## @param values - dictionary containing numeric values +## @return dictionary with normalized values, each ranging from 0.0 to 1.0 +func _normalize_distribution(values: Dictionary) -> Dictionary: + var normalized_distribution: Dictionary = {} + var distribution_total: float = 0 + for id in values: + if values[id] != 0: + distribution_total += values[id] + normalized_distribution[id] = values[id] + for id in normalized_distribution: + normalized_distribution[id] /= distribution_total + return normalized_distribution + +#endregion + +#region Batches + +## Consumes entities from batches based on current year +## Iterates through batches and withdraws amount based on time period: +## - If current_year >= end_year: consume entire batch +## - If current_year in [start_year, end_year): consume proportional amount +## @param entity - entity to consume from +## @param current_year - current year in universe +## @return total amount consumed from all applicable batches +func consume_from_batches(entity: OwniverseEntity, current_year: float) -> float: + var batches = state.batches[entity.id] + var total_consumed: float = 0.0 + + # Iterate backwards to safely remove completed batches + state.amount[entity.id] = 0 + for i in range(batches.size() - 1, -1, -1): + var batch = batches[i] + + # Skip batches that haven't started yet + if current_year < batch["start_year"]: + state.amount[entity.id] += batch["amount"] + continue + + # If year is past batch end, consume entire batch + if current_year >= batch["end_year"]: + total_consumed += batch["amount"] + batches.remove_at(i) + else: + # Consume proportional amount based on elapsed time + var batch_duration = batch["end_year"] - batch["start_year"] + var time_elapsed = current_year - batch["start_year"] + var proportion = time_elapsed / batch_duration + var consumed_amount = batch["amount"] * proportion + + total_consumed += consumed_amount + batch["amount"] -= consumed_amount + state.amount[entity.id] += batch["amount"] + + return total_consumed + + +## Adds entity amount to appropriate batch based on current year +## state.batches[entity_id] contains list of batches with intervals and amounts +## Each batch: {start_year, end_year, amount} +## @param entity_id - id of entity to add +## @param amount - quantity of entity being added +## @param current_year - current year in universe +func add_entity_to_batch(entity: OwniverseEntity, amount: float, current_year: float) -> void: + var batches = state.batches[entity] + + # 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 + entity.lifespan_delta, + "amount": amount + } + batches.append(last_batch) + else: + # Add to existing batch + last_batch["amount"] += amount + +#endregion + +#region ProductionLegacy #----- - -func produce(item_id: String, amount: float) -> void: - var _entity: OwniverseEntity = entities[item_id] +#func _produce_automatically(year_delta: float) -> void: + #var cycle_production = {"S": 0.0, "H": 0.0} + #for entity: OwniverseEntity in auto_production: + #if current_amount[entity.id] == 0: + #continue + #cycle_production[entity.product_per_year_type] += \ + #current_amount[entity.id] * entity.product_per_year_amount * year_delta - var entity_transaction = {} - - for elem in _entity.cost: - if current_amount[elem] < amount * _entity.cost[elem]: - return - if _entity.cost[elem] == 0: continue - - entity_transaction[elem] = - amount * _entity.cost[elem] - - for elem in entity_transaction: - _process_enitity(elem, entity_transaction[elem]) - _process_enitity(item_id, amount) - #state.add(_entity, current_year, amount) + #for item in cycle_production: + #_count_amount(item, cycle_production[item]) + # +#func automated_production(_delta: float) -> void: + #for entity_id in auto_production: + #var entity = entities[entity_id] + #var _amount_to_produce = int(entity.amount * entity.product_per_year_amount * _delta) - -func automated_production(_delta: float) -> void: - for entity_id in auto_production: - var _entity = entities[entity_id] - var _amount_to_produce = int(_entity.amount * _entity.product_per_year_amount * _delta) - - -func get_available_to_produce(item_id: String) -> float: - var entity: OwniverseEntity = entities[item_id] - if entity.cost == {}: return 0 - var amount: float = 1E50 - for elem in entity.cost: - amount = min(floorf(current_amount[elem] / entity.cost[elem]), amount) - return amount +#func get_available_to_produce(item_id: String) -> float: + #var entity: OwniverseEntity = entities[item_id] + #if entity.cost == {}: return 0 + #var amount: float = 1E50 + #for elem in entity.cost: + #amount = min(floorf(current_amount[elem] / entity.cost[elem]), amount) + #return amount # add to all lists +## Processes entity production/consumption +## @param entity_id - id of entity to process +## @param amount - amount to change (positive for production, negative for consumption) func _process_enitity(entity_id: String, amount: float) -> void: - if amount == 0: return - _count_amount(entity_id, amount) - - -func _count_amount(entity_id: String, amount: float) -> void: - if amount == 0: return + if amount == 0: + return - current_amount[entity_id] += amount - var entity: OwniverseEntity = entities[entity_id] - if entity.group_as != "": - current_amount[entity.group_as] += amount + # Update state amount + state.amount[entity_id] = state.amount.get(entity_id, 0.0) + amount - if amount < 0: return - - cumulative_amount[entity_id] += amount + # Update items to display + items_to_update[entity_id] = state.amount[entity_id] + + # Handle group updates if entity belongs to a group + var entity = entities[entity_id] if entity.group_as != "": - cumulative_amount[entity.group_as] += 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_id] = current_amount[entity_id] + +#func _count_amount(entity_id: String, amount: float) -> void: + #if amount == 0: return + # + #current_amount[entity_id] += amount + #var entity: OwniverseEntity = entities[entity_id] + #if entity.group_as != "": + #current_amount[entity.group_as] += amount + # + #if amount < 0: return + # + #cumulative_amount[entity_id] += amount + #if entity.group_as != "": + #cumulative_amount[entity.group_as] += amount +# + #items_to_update[entity_id] = current_amount[entity_id] #endregion @@ -198,10 +357,10 @@ func load_data() -> bool: return false #entity_batches = game_data["entity_batches"] - cumulative_amount = _game_data["cumulative_amount"] + #cumulative_amount = _game_data["cumulative_amount"] current_amount = _game_data["current_amount"] #print("loaded ", _game_data) - state = _game_data["state"] + #state = _game_data["state"] stats = _game_data["stats"] for item in current_amount: @@ -214,7 +373,7 @@ func load_data() -> bool: func save_data() -> void: var _game_data: Dictionary = {} - _game_data["cumulative_amount"] = cumulative_amount + #_game_data["cumulative_amount"] = cumulative_amount _game_data["current_amount"] = current_amount _game_data["state"] = state _game_data["stats"] = stats @@ -225,48 +384,44 @@ func save_data() -> void: func init_enitities() -> Dictionary: - var _result := { - 'locked_items' : [], + 'locked_items': [], } - var _auto_production := [] - for _entity_id in Global.OwniverseEntities: - var _entity = OwniverseEntity.new(Global.OwniverseEntities[_entity_id]) - entities[_entity_id] = _entity + #var _auto_production := [] + for entity_id in Global.OwniverseEntities: + var entity = OwniverseEntity.new(Global.OwniverseEntities[entity_id]) + entities[entity_id] = entity - current_amount[_entity_id] = 0 - state.amount[_entity_id] = 0 - cumulative_amount[_entity_id] = 0 - stats.total_amount[_entity_id] = 0 - if _entity.group_as != "": - current_amount[_entity.group_as] = 0 - state.amount[_entity.group_as] = 0 + current_amount[entity_id] = 0 + stats.total_amount[entity_id] = 0 + state.amount[entity_id] = 0 + if entity.lifespan > 0: + state.batches[entity] = [] + add_entity_to_batch(entity, 0, 0) + if entity.group_as != "": + current_amount[entity.group_as] = 0 + state.amount[entity.group_as] = 0 - cumulative_amount[_entity.group_as] = 0 - stats.total_amount[_entity.group_as] = 0 + stats.total_amount[entity.group_as] = 0 - if _entity.unlock != {}: - locked_entities.append(_entity) - _result.locked_items.append(_entity.id) + if entity.unlock != {}: + locked_entities.append(entity) + _result.locked_items.append(entity.id) - #state.init_entity(_entity) - - if _entity.product_per_year_type != "": - auto_production.append(_entity) - _auto_production.append(_entity.id) + if entity.product_per_year_type != "": + auto_production.append(entity) # groups - if _entity.group_as != "": - if !entity_groups.has(_entity.group_as): - entity_groups[_entity.group_as] = [] - entity_groups[_entity.group_as].append(_entity_id) + if entity.group_as != "": + if !entity_groups.has(entity.group_as): + entity_groups[entity.group_as] = [] + entity_groups[entity.group_as].append(entity_id) - if _entity.event_group != "": - if !entity_event_groups.has(_entity.event_group): - entity_event_groups[_entity.event_group] = [] - entity_event_groups[_entity.event_group].append(_entity_id) + if entity.event_group != "": + if !entity_event_groups.has(entity.event_group): + entity_event_groups[entity.event_group] = [] + entity_event_groups[entity.event_group].append(entity_id) - #evolution.init_batches(_auto_production) return _result #endregion diff --git a/world/owniverse/owniverse_entity.gd b/world/owniverse/owniverse_entity.gd index 3a39009..a5ccf67 100644 --- a/world/owniverse/owniverse_entity.gd +++ b/world/owniverse/owniverse_entity.gd @@ -38,10 +38,11 @@ func _init(values: Dictionary) -> void: unlock = _parse_and_string(values.unlock) cost = _parse_and_string(values.cost) bind = _parse_and_string(values.bind) + #print(id, " | ", cost, " | ", bind) mass = float(values.mass) space = int(values.space) lifespan = float(values.lifespan) - lifespan_delta = lifespan / Global.batch_count / 2 if lifespan > 10 else lifespan + lifespan_delta = lifespan / Global.batch_count if lifespan > 10 else lifespan auto = float(values.auto) gamma = float(values.gamma) blast = int(values.blast) diff --git a/world/world.tscn b/world/world.tscn index a8d1a0b..6be0fce 100644 --- a/world/world.tscn +++ b/world/world.tscn @@ -877,17 +877,5 @@ wait_time = 2.0 autostart = true [connection signal="timeout" from="Background/Timer" to="Background" method="randomize_parallax"] -[connection signal="pressed" from="Stage/StageRows/TopMenu/BurgerButton" to="Stage" method="_on_topmenu_button_pressed" binds= ["Burger"]] -[connection signal="pressed" from="Stage/StageRows/TopMenu/TimeMinus" to="Stage" method="_change_speed" binds= [-1]] -[connection signal="pressed" from="Stage/StageRows/TopMenu/TimePlus" to="Stage" method="_change_speed" binds= [1]] -[connection signal="pressed" from="Stage/StageRows/TopMenu/ReportButton" to="Stage" method="_on_topmenu_button_pressed" binds= ["Report"]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/ResourcePage/ResourceHeader/EnergyIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Energy", 1]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/ResourcePage/ResourceHeader/DensityIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Density", 1]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/StarPage/StarHeader/BurnabilityIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Burnability", 2]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/StarPage/StarHeader/ExplosivenessIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Explosiveness", 2]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/StructurePage/StructureHeader/EnergyIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Energy", 3]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/StructurePage/StructureHeader/DensityIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Density", 3]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/LifePage/LifeHeader/AggressionIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Aggression", 4]] -[connection signal="pressed" from="Stage/StageRows/PageScroller/HBoxContainer/LifePage/LifeHeader/TrendIndicator" to="Stage" method="_on_indicator_pressed" binds= ["Trends", 4]] [connection signal="timeout" from="Stage/ItemAmountUpdate" to="Stage" method="set_owniverse_item_value"] [connection signal="timeout" from="Stage/Unlock&Save" to="Stage" method="_on_unlock_and_save_timeout"]