extends Control const E = preload("res://world/owniverse/math/exact.gd") const OWNIVERSE_ITEM = preload("uid://d3xjfxwe3w16v") const SPEED_TEXTURE_NAME_TEMPLATE = "res://ui/topmenu/time&speed/time%s.png" const ItemDescription = preload("res://world/owniverse/item_description.gd") const WorldIndicators = preload("res://ui/indicators/world_indicators.gd") var world_indicators: RefCounted var indicator_elapsed: float = 0.0 var description_targets: Dictionary = {} var description_samples: Dictionary = {} var description_rates: Dictionary = {} var description_elapsed: float = 0.0 const PAGE_COUNT = 5 const RESOURCE_PAGE = 1 const PAGE_WIDTH = 1080 const DRAG_TOLERANCE = 360 const SWIPE_DURATION: float = 0.4 @onready var stage_page_scroller: ScrollContainer = $World/PageScroller @onready var item_pages: Array[Control] = [ $World/PageScroller/HBoxContainer/ForcePage/Forces, $World/PageScroller/HBoxContainer/ResourcePage/HBoxContainer/Resources, $World/PageScroller/HBoxContainer/StarPage/Stars, $World/PageScroller/HBoxContainer/StructurePage/Structures, $World/PageScroller/HBoxContainer/LifePage/Planets/Life, $World/PageScroller/HBoxContainer/LifePage/Planets/LivingPlanet, $World/PageScroller/HBoxContainer/LifePage/Planets/Civ0, $World/PageScroller/HBoxContainer/LifePage/Planets/Civ1, $World/PageScroller/HBoxContainer/LifePage/Planets/Civ2, $World/PageScroller/HBoxContainer/LifePage/Planets/Civ3, $World/PageScroller/HBoxContainer/LifePage/Planets/Civ4 ] @onready var page_actions: Dictionary[int, OwniverseAction] = { 2: $World/PageScroller/HBoxContainer/StarPage/Actions, 3: $World/PageScroller/HBoxContainer/StructurePage/Actions, } @onready var g_item_descriptions: Array = [ $World/PageScroller/HBoxContainer/ForcePage/Description, $World/PageScroller/HBoxContainer/ResourcePage/Description, $World/PageScroller/HBoxContainer/StarPage/Description, $World/PageScroller/HBoxContainer/StructurePage/Description, $World/PageScroller/HBoxContainer/LifePage/Description ] @onready var date_time: Label = $World/TopMenu/InfoPanel/DateTime @onready var g_speed_icons: Array = [ $World/TopMenu/InfoPanel/Speed/Speed1, $World/TopMenu/InfoPanel/Speed/Speed2, $World/TopMenu/InfoPanel/Speed/Speed3, $World/TopMenu/InfoPanel/Speed/Speed4, $World/TopMenu/InfoPanel/Speed/Speed5, $World/TopMenu/InfoPanel/Speed/Speed6, $World/TopMenu/InfoPanel/Speed/Speed7, $World/TopMenu/InfoPanel/Speed/Speed8 ] @onready var g_time_minus: TextureButton = $World/TopMenu/TimeMinus @onready var g_time_plus: TextureButton = $World/TopMenu/TimePlus @onready var screens: Dictionary = { "World": $World, "MainMenu": $MainMenu, "Report": $Report, "Achievements": $Achievements, "Credits": $Credits, } var g_tween: Tween = null var g_dragged_start: Vector2 = Vector2.ZERO var g_scroll_start: int = 0 #var g_current_page: int = 0 var g_last_scroll_updated: int = 0 var f_check_swipe: bool = false var g_swipe_time_delta: float = 0 var g_counters: Dictionary[String, Counter] = { "swipe": Counter.new(), } var g_active_items: Dictionary = {} var pending_page: int = -1 var active_actions_dirty: bool = true var active_actions_elapsed: float = 0.0 var owniverse:Owniverse = Owniverse.new() var owniverse_items: Dictionary[String, OwniverseItem] = {} var locked_items: Array = [] func set_datetime(value): # Simulation time advances in tenths of a year; displaying five decimal # places only adds zeroes and suggests precision the simulation does not # have. date_time.text = Global.format_number(value, 1) if not E.eq(value, 0) else "0.0" #region Item manipulations: update and visibility func set_owniverse_item_value() -> void: for item_id in owniverse.presentation.pending_amounts: if owniverse_items.has(item_id): owniverse_items[item_id].set_label(owniverse.presentation.pending_amounts[item_id]) owniverse.presentation.pending_amounts.clear() #endregion func _unlock_items(): var result: Dictionary = owniverse.presentation.unlocks(owniverse.state, owniverse.catalog, locked_items) for item_id in result.visible: if owniverse_items.has(item_id): owniverse_items[item_id].set_visible(true) locked_items = result.remaining func _on_indicator_pressed(indicator_name: String, page: int = 0): _set_description(indicator_name, page) func _set_item_description(item: OwniverseItem) -> void: var category: String = "Entity" if owniverse.catalog.entities.has(item.item_id) else "Force" var id: String = item.item_id if category == "Entity" else item.item_name.capitalize() var page: int = clampi(item.item_page, 0, 4) if description_targets.get(page, {}).get("id", "") != id: description_samples.erase(id) description_rates.erase(id) description_targets[page] = {"id": id, "category": category} _refresh_descriptions() func _set_description(item_name: String, page: int) -> void: description_targets[page] = {"id": item_name, "category": "Indicator"} _refresh_descriptions() func _refresh_descriptions() -> void: for page in description_targets: var target: Dictionary = description_targets[page] var label: RichTextLabel = g_item_descriptions[page] label.bbcode_enabled = true label.text = ItemDescription.render(owniverse, Global, target.id, target.category, description_rates.get(target.id, 0.0)) func _update_descriptions(delta: float) -> void: description_elapsed += delta if description_elapsed < 0.25: return for page in description_targets: var id: String = description_targets[page].id var total = ItemDescription.quantities(owniverse, id).total description_rates[id] = E.divide(E.subtract(total, description_samples[id]), E.of(description_elapsed)) if description_samples.has(id) else 0.0 description_samples[id] = total description_elapsed = 0.0 _refresh_descriptions() func _control_page(item: OwniverseItem) -> void: # TODO переписать это ↓ if !g_active_items.has(item.item_page): g_active_items[item.item_page] = null var current_item: OwniverseItem = g_active_items[item.item_page] if current_item == item: return g_active_items[item.item_page] = item active_actions_dirty = true if current_item != null and current_item.item_page == item.item_page: current_item.set_selection(false) item.set_selection(true) # subpages if item.item_sub_page != -1: item_pages[item.item_sub_page].visible = true if current_item == null \ or current_item.item_sub_page == -1 \ or current_item.item_sub_page == item.item_sub_page: return item_pages[current_item.item_sub_page].visible = false func _init_owniverse_items() -> bool: owniverse_items = {} for item_id in Global.OwniverseItems: var item_props: Dictionary = Global.OwniverseItems[item_id] var item: OwniverseItem = OWNIVERSE_ITEM.instantiate() if !item_props.has("page"): printerr("Item `", item_id, "` has no `page` key.") return false var page: int = int(item_props.page) item_pages[page].add_child(item) item.init_button(item_props) item.item_activated.connect(item_activated) owniverse_items[item_id] = item return false func item_activated(item: OwniverseItem) -> void: _control_page(item) _set_item_description(item) if item.item_id == "S" or item.item_id == "H": var _value = owniverse.state.amount[item.item_id] _value = E.multiply(9, _value) if E.gt(_value, 0) else 1 owniverse.request_production(item.item_id, _value, true) func _check_counters(delta: float) -> Array: var actions: Array = [] for k in g_counters: var cnt: Counter = g_counters[k] if not cnt.is_active: continue cnt.value += delta if cnt.value >= cnt.sec: actions.append(k) cnt.value = 0 cnt.is_active = false return actions #region Swipe and page func _do_actions(actions: Array) -> void: for action: String in actions: match action: "swipe": _swipe_to_nearest_page() func _swipe_to_nearest_page() -> void: if stage_page_scroller.scroll_horizontal % PAGE_WIDTH == 0: return var new_page: int = round(1.0 * stage_page_scroller.scroll_horizontal / PAGE_WIDTH) if g_tween != null: if g_tween.is_running(): return g_tween = stage_page_scroller.get_tree().create_tween() g_tween.tween_property(stage_page_scroller, "scroll_horizontal", new_page * PAGE_WIDTH, SWIPE_DURATION).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) func _input(event: InputEvent) -> void: if event is InputEventScreenDrag: stage_page_scroller.scroll_horizontal = clamp( stage_page_scroller.scroll_horizontal - PAGE_COUNT * event.screen_relative.x, 0, PAGE_WIDTH * (PAGE_COUNT - 1)) g_counters.swipe.is_active = true g_counters.swipe.value = 0 elif event is InputEventPanGesture: g_counters.swipe.is_active = true g_counters.swipe.value = 0 elif event is InputEventScreenTouch: pass func _set_active_page_actions() -> void: for pa in page_actions: if !g_active_items.has(pa): continue var item_id: String = g_active_items[pa].item_id var entity: OwniverseEntity = owniverse.catalog.entities[item_id] #var amount = owniverse.get_available_to_produce(item_id) #var amount = owniverse.get_available_to_produce(entity) var amount = owniverse.production.calculate_max_producible(entity) #print(item_id, " : ", amount) if item_id == "SMBH": var preview: Callable = func(requested): return owniverse.production.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) -> void: if !g_active_items.has(page_id): return var item_id: String = g_active_items[page_id].item_id if E.gt(action_amount, 0): owniverse.request_production(item_id, action_amount) #endregion func _ready() -> void: get_tree().auto_accept_quit = false world_indicators = WorldIndicators.new(self) create_new_universe() load_data() _refresh_universe_ui() g_counters.swipe.sec = 0.2 for pa in page_actions: page_actions[pa].action_pressed.connect(_page_action_pressed) page_actions[pa].page_id = pa ## Starts a fresh universe. Can be connected directly to a button's pressed signal. ## Saving continues through the normal autosave/close path. func create_new_universe() -> void: var previous: Owniverse = owniverse owniverse = Owniverse.new() var inited_result: Dictionary = init_enitities() locked_items = inited_result.locked_items for entity in previous.catalog.entities.values(): entity.free() previous.free() if is_instance_valid(g_tween): g_tween.kill() g_active_items.clear() description_targets.clear() description_samples.clear() description_rates.clear() description_elapsed = 0.0 active_actions_dirty = true active_actions_elapsed = 0.0 for label in g_item_descriptions: label.text = "" for item in owniverse_items.values(): item.get_parent().remove_child(item) item.queue_free() _init_owniverse_items() for page in range(5, item_pages.size()): item_pages[page].visible = false for actions in page_actions.values(): actions.set_actions("", 0.0) pending_page = RESOURCE_PAGE _restore_page.call_deferred() _refresh_universe_ui() _on_topmenu_button_pressed("World") func _refresh_universe_ui() -> void: owniverse.presentation.refresh(owniverse.state, owniverse.catalog) _change_speed(0) set_datetime(owniverse.state.time.year) set_owniverse_item_value() _unlock_items() if world_indicators != null: world_indicators.update(owniverse) func _process(delta: float) -> void: owniverse.evolute_frame(delta, get_tree()) indicator_elapsed += delta if indicator_elapsed >= 0.1: indicator_elapsed = 0.0 world_indicators.update(owniverse) _update_descriptions(delta) set_owniverse_item_value() set_datetime(owniverse.state.time.year) #owniverse.cycle(delta) #owniverse.cycle_legacy(year_delta) var actions = _check_counters(delta) _do_actions(actions) # Production limits are expensive (they walk the resource ledger). Refresh # them when selection changes and at a modest cadence instead of every # rendered frame. The simulation itself continues to run every frame. active_actions_elapsed += delta if active_actions_dirty or active_actions_elapsed >= 0.1: active_actions_elapsed = 0.0 active_actions_dirty = false _set_active_page_actions() func _change_speed(delta: int) -> void: owniverse.speed = owniverse.speed + delta var speed: int = owniverse.speed var ind: int = 1 for icon in g_speed_icons: if ind <= speed: icon.visible = true else: icon.visible = false ind += 1 if speed == 1: g_time_minus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % "x") g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % "2") elif speed == Global.SPEED_LIMIT: g_time_minus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(Global.SPEED_LIMIT - 1)) g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % "x") else: g_time_minus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed - 1)) g_time_plus.texture_normal = load(SPEED_TEXTURE_NAME_TEMPLATE % str(speed + 1)) func _on_topmenu_button_pressed(screen_name: String) -> void: for screen_id in screens: #print(screen_name, " ←→ ", screen_id) screens[screen_id].visible = screen_id == screen_name func _on_unlock_and_save_timeout() -> void: save_data() _unlock_items() #region Init save load routines 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. for id in owniverse.state.amount: if not restored.amount.has(id) or not restored.stats.total_amount.has(id): 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.restore_frame_checkpoint(game_data.get("simulation", {})) owniverse.evolution.initialize_life_batches() owniverse.presentation.refresh(owniverse.state, owniverse.catalog) pending_page = clampi(int(game_data.get("ui", {}).get("current_page", 0)), 0, PAGE_COUNT - 1) _restore_page.call_deferred() 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: var page: int = pending_page if page < 0: page = clampi(roundi(float(stage_page_scroller.scroll_horizontal) / PAGE_WIDTH), 0, PAGE_COUNT - 1) SaveManager.request_save("owniverse", "qwerty", { "state": owniverse.state, "simulation": owniverse.frame_checkpoint(), "ui": {"current_page": page}, }) var closing: bool = false func _notification(what: int) -> void: if not is_node_ready() or closing: return if what == NOTIFICATION_WM_CLOSE_REQUEST: _close_after_save() elif what == NOTIFICATION_APPLICATION_PAUSED: save_data() # The OS may suspend frames immediately; finish the queued write now. SaveManager.flush_saves() func _close_after_save() -> void: closing = true process_mode = Node.PROCESS_MODE_DISABLED save_data() if SaveManager.is_saving(): await SaveManager.saves_drained get_tree().quit() func init_enitities() -> Dictionary: var _result := { 'locked_items': [], } owniverse.catalog.load_definitions(Global.OwniverseEntities) for entity_id in owniverse.catalog.entities: var entity: OwniverseEntity = owniverse.catalog.entities[entity_id] owniverse.state.stats.total_amount[entity_id] = 0 owniverse.state.amount[entity_id] = 0 if E.gt(entity.lifespan, 0): owniverse.state.batches[entity.id] = [] owniverse.batches.add_entity_to_batch(entity_id, 0, 0) if entity.group_as != "": owniverse.state.amount[entity.group_as] = 0 owniverse.state.stats.total_amount[entity.group_as] = 0 if entity.unlock != {}: _result.locked_items.append(entity.id) return _result #endregion func _on_achievements_pressed(extra_arg_0: String) -> void: pass # Replace with function body. func _on_credits_pressed(extra_arg_0: String) -> void: pass # Replace with function body.